Exemple #1
0
        /// <summary>
        /// Calculates a z-score for the given indicator, age in months, measurement value, and gender.
        /// </summary>
        /// <param name="indicator">The indicator to use for computing the z-score (e.g. BMI-for-age, Height-for-Age, Weight-for-Age, etc.)</param>
        /// <param name="measurement1">
        /// The first measurement value. Must be in metric units. For example, if the indicator is Height-for-Age,
        /// then measurement1 represents the child's height in centimeters.
        /// </param>
        /// <param name="measurement2">
        /// The second measurement. Typically age of the child in months. For example, if the indicator is
        /// 'Height-for-Age', then measurement2 represents the child's age. If  the indicator is instead
        /// 'Weight-for-Length' or 'Weight-for-Height' then measurement2 represents the child's length or
        /// height (respectively) and must be a non-zero value provided in centimeters. Automatically
        /// rounded to 5 decimal values.
        /// </param>
        /// <param name="sex">Whether the child is male or female</param>
        /// <returns>double; the calculated z-score for the given inputs</return>
        internal double CalculateZScore(Indicator indicator, double measurement1, double measurement2, Sex sex)
        {
            if (measurement1 < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(measurement1));
            }
            if (!IsValidMeasurement(indicator, measurement2))
            {
                throw new ArgumentOutOfRangeException(nameof(measurement2));
            }

            measurement2 = Math.Round(measurement2, 5);

            IDictionary <int, Lookup> reference = null;

            switch (indicator)
            {
            case Indicator.BodyMassIndexForAge:
                reference = CDC2000_BMI;
                break;

            case Indicator.HeadCircumferenceForAge:
                reference = CDC2000_HeadCircumference;
                break;

            case Indicator.LengthForAge:
                reference = CDC2000_LengthForAge;
                break;

            case Indicator.HeightForAge:
                reference = CDC2000_HeightForAge;
                break;

            case Indicator.WeightForAge:
                reference = CDC2000_WeightForAge;
                break;

            case Indicator.WeightForLength:
                reference = CDC2000_WeightForLength;
                break;

            case Indicator.WeightForHeight:
                reference = CDC2000_WeightForHeight;
                break;

            default:
                throw new ArgumentOutOfRangeException(nameof(indicator));
            }

            int    key    = BuildKey(sex, measurement2);
            Lookup lookup = null;
            bool   found  = reference.TryGetValue(key, out lookup);

            if (found)
            {
                return(StatisticsHelper.CalculateZScore(measurement1, lookup.L, lookup.M, lookup.S, false));
            }
            else
            {
                var interpolatedValues = InterpolateLMS(sex, measurement2, reference);
                return(StatisticsHelper.CalculateZScore(measurement1, interpolatedValues.Item1, interpolatedValues.Item2, interpolatedValues.Item3, false));
            }
        }
Exemple #2
0
        /// <summary>
        /// Interpolates the L, M, and S values for a given measurement using the closest neighbors to that measurement. For
        /// example, if the lookup table has LMS entries for 24.5 and 25.5, and the measurement provided is 24.7, then the
        /// lower LMS values will be multiplied by 0.8 and added to the upper LMS values, which will be multiplied by 0.2.
        /// The interpolated LMS values are then returned in a 3-tuple.
        /// </summary>
        /// <remarks>
        /// The CDC 2000 Growth Chart data points are mostly spaced at 1.0 intervals, e.g. 24.5 to 25.5 to 26.5, etc.
        /// However, a small handful of points are spaced 0.5 apart such as the BMI-for-age data values at 24.0 and
        /// 24.5. Some extra logic is included to find the proper neighbor in this case (since one cannot assume that
        /// the neighbors will always be +/- 1 away) and then to adjust the modifiers applied to the upper and lower
        /// LMS values. This means a 24.1 measurement for age will see the lower LMS values multiplied by 0.8, since
        /// 0.1 is 20% of the distance to 0.5. If the measurement were instead 25.1, then the lower LMS values would
        /// be multiplied by 0.1 since 0.1 is 10% of the distance to 1.0.
        /// </remarks>
        /// <param name="sex">Whether the child is male or female</param>
        /// <param name="measurement">The measurement in metric units</param>
        /// <param name="reference">The lookup table to use to find the closest neighbors to the measurement value</param>
        /// <returns>3-tuple of double representing the interpolated L, M, and S values</return>
        internal Tuple <double, double, double> InterpolateLMS(Sex sex, double measurement, IDictionary <int, Lookup> reference)
        {
            double rounded = Math.Round(measurement);

            double nextUpper = -1;
            double nextLower = -1;

            Lookup lookupUpper = null;
            Lookup lookupLower = null;

            int initialKeyAttempt = BuildKey(sex, rounded);

            if (reference.ContainsKey(initialKeyAttempt))
            {
                if (rounded > measurement)
                {
                    nextUpper   = rounded;
                    lookupUpper = reference[initialKeyAttempt];
                }
                else
                {
                    nextLower   = rounded;
                    lookupLower = reference[initialKeyAttempt];
                }
            }

            var keyUpper = -1;
            var keyLower = -1;

            if (nextUpper == -1)
            {
                nextUpper = Math.Round(measurement, 0) + 0.5;
                keyUpper  = BuildKey(sex, nextUpper);
            }
            if (nextLower == -1)
            {
                nextLower = Math.Round(measurement, 0) - 0.5;
                keyLower  = BuildKey(sex, nextLower);
            }

            if (reference.ContainsKey(keyUpper))
            {
                lookupUpper = reference[keyUpper];
                nextUpper   = lookupUpper.Measurement;
            }
            if (reference.ContainsKey(keyLower))
            {
                lookupLower = reference[keyLower];
                nextLower   = lookupUpper.Measurement;
            }

            if (nextUpper == -1)
            {
                nextUpper = Math.Round(measurement, 0) + 1;
                keyUpper  = BuildKey(sex, nextUpper);

                if (reference.ContainsKey(keyUpper))
                {
                    lookupUpper = reference[keyUpper];
                    nextUpper   = lookupUpper.Measurement;
                }
            }
            if (nextLower == -1)
            {
                nextLower = Math.Round(measurement, 0) - 1;
                keyLower  = BuildKey(sex, nextLower);

                if (reference.ContainsKey(keyLower))
                {
                    lookupLower = reference[keyLower];
                    nextLower   = lookupUpper.Measurement;
                }
            }

            double upperL = lookupUpper.L;
            double upperM = lookupUpper.M;
            double upperS = lookupUpper.S;

            double lowerL = lookupLower.L;
            double lowerM = lookupLower.M;
            double lowerS = lookupLower.S;

            double diff           = lookupUpper.Measurement - lookupLower.Measurement;
            double globalModifier = 100 / (100 * diff);

            double lowerModifier = Math.Round((lookupUpper.Measurement - measurement), 5);
            double upperModifier = (diff - lowerModifier);

            lowerModifier *= globalModifier;
            upperModifier *= globalModifier;

            double L = ((lookupLower.L * lowerModifier) + (lookupUpper.L * upperModifier));
            double M = ((lookupLower.M * lowerModifier) + (lookupUpper.M * upperModifier));
            double S = ((lookupLower.S * lowerModifier) + (lookupUpper.S * upperModifier));

            return(new Tuple <double, double, double>(L, M, S));
        }
        /// <summary>
        /// Calculates a z-score for a given indicator, pair of measurements (measurement1-for-measurement2, as
        /// in "BMI-for-Age"), and gender.
        /// </summary>
        /// <param name="indicator">The indicator to use for computing the z-score (e.g. BMI, Height-for-Age, Weight-for-Age, etc.)</param>
        /// <param name="measurement1">
        /// The first measurement value. Must be in metric units and must be greater than or equal to zero. For
        /// example, if the indicator is 'Height-for-Age', then measurement1 represents the child's height in
        /// centimeters. Note that subscapular skinfold and triceps skinfold require measurement1 be provided
        /// in millimeters.
        /// </param>
        /// <param name="measurement2">
        /// The second measurement. Typically age of the child in days. For example, if the indicator is
        /// 'Height-for-Age', then measurement2 represents the child's age. If the indicator is instead
        /// 'Weight-for-Length' or 'Weight-for-Height' then measurement2 represents the child's length or
        /// height (respectively) and must be a non-zero value provided in centimeters. Automatically
        /// rounded to 5 decimal values if measuring height or length and automatically rounded to a whole
        /// number if measuring age in days.
        /// </param>
        /// <param name="sex">Whether the child is male or female</param>
        /// <returns>double; the z-score for the given inputs</return>
        internal double CalculateZScore(Indicator indicator, double measurement1, double measurement2, Sex sex)
        {
            if (measurement1 < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(measurement1));
            }
            if (!IsValidMeasurement(indicator, measurement2))
            {
                throw new ArgumentOutOfRangeException(nameof(measurement2));
            }

            measurement2 = Math.Round(measurement2, 5);

            Dictionary <int, Lookup> reference = null;
            bool shouldRound = true;

            switch (indicator)
            {
            case Indicator.BodyMassIndexForAge:
                reference = WHO2006_BMI;
                break;

            case Indicator.WeightForLength:
                reference   = WHO2006_WeightForLength;
                shouldRound = false;
                break;

            case Indicator.WeightForHeight:
                reference   = WHO2006_WeightForHeight;
                shouldRound = false;
                break;

            case Indicator.WeightForAge:
                reference = WHO2006_WeightForAge;
                break;

            case Indicator.ArmCircumferenceForAge:
                reference = WHO2006_ArmCircumference;
                break;

            case Indicator.HeadCircumferenceForAge:
                reference = WHO2006_HeadCircumference;
                break;

            case Indicator.HeightForAge:
            case Indicator.LengthForAge:
                reference = WHO2006_LengthHeightForAge;
                break;

            case Indicator.SubscapularSkinfoldForAge:
                reference = WHO2006_SubscapularSkinfoldForAge;
                break;

            case Indicator.TricepsSkinfoldForAge:
                reference = WHO2006_TricepsSkinfoldForAge;
                break;

            default:
                throw new ArgumentOutOfRangeException(nameof(indicator));
            }

            if (shouldRound && !StatisticsHelper.IsWholeNumber(measurement2))
            {
                measurement2 = Math.Round(measurement2, 0);
            }

            int    key    = BuildKey(sex, measurement2);
            Lookup lookup = null;
            bool   found  = reference.TryGetValue(key, out lookup);

            if (found)
            {
                return(StatisticsHelper.CalculateZScore(measurement1, lookup.L, lookup.M, lookup.S, true));
            }
            else if (indicator == Indicator.WeightForLength || indicator == Indicator.WeightForHeight)
            {
                var interpolatedLMS = InterpolateLMS(sex, measurement2, reference);
                return(StatisticsHelper.CalculateZScore(measurement1, interpolatedLMS.Item1, interpolatedLMS.Item2, interpolatedLMS.Item3, true));
            }
            else
            {
                throw new InvalidOperationException($"Could not find a lookup match for value {Math.Round(measurement2, 2).ToString("N2")}");
            }
        }