R/z-score-helper.R

Defines functions anthro_zscore_adjusted adjust_lenhei flag_zscore apply_zscore_and_growthstandards compute_zscore_adjusted compute_zscore

#' Implements the non adjusted z-score
#'
#' @param y y
#' @param m m
#' @param l l
#' @param s s
#'
#' @references
#' http://www.who.int/childgrowth/standards/Chap_7.pdf
#'
#' @noRd
compute_zscore <- function(y, m, l, s) {
  stopifnot(is.numeric(y), is.numeric(m), is.numeric(l), is.numeric(s))
  ((y / m)^l - 1) / (s * l)
}

#' Implements the adjusted z-score
#'
#' @param y y
#' @param m m
#' @param l l
#' @param s s
#'
#' @references
#' http://www.who.int/childgrowth/standards/Chap_7.pdf
#'
#' @noRd
compute_zscore_adjusted <- function(y, m, l, s) {
  stopifnot(is.numeric(y), is.numeric(m), is.numeric(l), is.numeric(s))
  calc_sd <- function(sd) m * ((1 + l * s * sd)^(1 / l))

  zscore <- compute_zscore(y, m, l, s)
  SD3pos <- calc_sd(3)
  SD3neg <- calc_sd(-3)
  SD23pos <- SD3pos - calc_sd(2)
  SD23neg <- calc_sd(-2) - SD3neg

  # a type-safe way to use ifelse
  not_zscore_na <- !is.na(zscore)
  zscore_gt_3 <- not_zscore_na & zscore > 3
  zscore[zscore_gt_3] <- (3 + ((y - SD3pos) / SD23pos))[zscore_gt_3]
  zscore_lt_3 <- not_zscore_na & zscore < -3
  zscore[zscore_lt_3] <- (-3 + ((y - SD3neg) / SD23neg))[zscore_lt_3]

  zscore
}

apply_zscore_and_growthstandards <- function(zscore_fun, growthstandards,
                                             age_in_days, sex, measure) {
  n <- length(measure)
  age_in_days[!is.na(age_in_days) & age_in_days < 0] <- NA_real_
  age_in_days <- as.integer(round_up(age_in_days))
  input_df <- data.frame(measure, age_in_days, sex, ordering = seq_len(n))
  merged_df <- merge(input_df,
    growthstandards,
    by.x = c("age_in_days", "sex"), by.y = c("age", "sex"),
    all.x = TRUE, sort = FALSE
  )
  merged_df <- merged_df[order(merged_df$ordering), , drop = FALSE]

  y <- merged_df[["measure"]]
  m <- merged_df[["m"]]
  l <- merged_df[["l"]]
  s <- merged_df[["s"]]
  zscore <- zscore_fun(y, m, l, s)
  round(zscore, digits = 2L)
}

flag_zscore <- function(flag_threshold, score_name, zscore, valid_zscore) {
  stopifnot(length(flag_threshold) == 1L || length(flag_threshold) == 2L)

  zscore[!valid_zscore] <- NA_real_

  fzscore <- if (length(flag_threshold) == 1L) {
    abs(zscore) > flag_threshold
  } else {
    zscore < flag_threshold[1L] | zscore > flag_threshold[2L]
  }

  # we convert it to integer because of historical reasons
  fzscore <- as.integer(fzscore)

  result_df <- data.frame(score = zscore, fzscore = fzscore)
  colnames(result_df) <- paste0(c("z", "f"), score_name)
  result_df
}

#' standardise lenhei
#' if child is <= 730 days, lenhei_unit var should be 'L'.
#' If lenhei_unit var is 'H', must add 0.7cm to standardise
#' if child is > 730 days, lenhei_unit var should be 'H'.
#' If lenhei_unit var is 'L', must subtract 0.7cm to standardise
#' @noRd
adjust_lenhei <- function(age_in_days, measure, lenhei) {
  stopifnot(is.character(measure), is.numeric(age_in_days), is.numeric(lenhei))
  age_in_days <- round_up(age_in_days)
  lenhei <-
    ifelse(
      !is.na(age_in_days) &
        age_in_days < 731 & !is.na(measure) & measure == "h",
      lenhei + 0.7,
      lenhei
    )

  lenhei <-
    ifelse(
      !is.na(age_in_days) &
        age_in_days >= 731 & !is.na(measure) & measure == "l",
      lenhei - 0.7,
      lenhei
    )
  lenhei
}

anthro_zscore_adjusted <-
  function(name, measure, age_in_days, age_in_months, sex, growthstandards, flag_threshold,
           allowed_age_range = c(0, 1856),
           zscore_is_valid = rep.int(TRUE, length(measure)),
           zscore_fun = compute_zscore_adjusted) {
    stopifnot(is.character(name), length(name) == 1L, !is.na(name))
    stopifnot(is.numeric(measure))
    stopifnot(
      is.numeric(allowed_age_range), length(allowed_age_range) == 2L,
      !any(is.na(allowed_age_range))
    )
    stopifnot(is.numeric(age_in_months))
    stopifnot(
      is.logical(zscore_is_valid),
      length(zscore_is_valid) == length(measure)
    )
    stopifnot(is.function(zscore_fun))
    assert_valid_sex(sex)
    age_in_days <- assert_valid_age_in_days(age_in_days)
    assert_growthstandards(growthstandards)

    # for all indicators a measure <= 0 should result in zscores being NA
    measure[measure <= 0] <- NA_real_

    # we convert the input parameter to a data frame and
    # join that with the growthstandards
    # then we have everything to compute the zscores
    zscore <- apply_zscore_and_growthstandards(
      zscore_fun, growthstandards,
      age_in_days, sex, measure
    )

    # we only compute zscores for children age < 60 months
    # the age in months is unrouned
    valid_age <- age_in_months < 60

    # at last we set certain zscores to NA
    valid_zscore <- !is.na(age_in_days) &
      age_in_days >= allowed_age_range[1L] &
      age_in_days <= allowed_age_range[2L] &
      zscore_is_valid &
      valid_age
    flag_zscore(flag_threshold, name, zscore, valid_zscore)
  }
dirkschumacher/anthro documentation built on Sept. 18, 2023, 5:34 a.m.