The 2025 Fenton third-generation growth charts for preterm infants was released recently and I’ve received a few requests to update tools, including an electronic growth chart that plots points on old school paper chart images. In practice, I use the charts built into the electronic health record, but since there will probably be some delay before they are released, it seemed worth the effort to update the tool.
While I’m confident in the numeric calculations (i.e., for Z-scores), when plotting points onto an image, it’s a good idea to do a bit of quality assurance testing to make sure it’s working properly. This post just has some code snippets that I used to create test points for QI.
Load packages
library(tidyverse)
library(knitr)
library(peditools)
library(ggh4x) # facet_grid2(), for multiple free scales using `independent`Retrieve LMS parameters
First a bit of setup, including retrieving the table of LMS parameters. The peditools R package includes many chart LMS parameters, but exclude the Fenton 2013 or 2025 chart parameters, which are available here.
The LMS data looks like this (using WHO infant chart parameters as an eample):
lmsdata <- get_lmsdata()
lmsdata |> filter(chart == "who_2006_infant") |> head() |> knitr::kable()| chart | age | age_units | gender | measure | measure_units | L | M | S | 
|---|---|---|---|---|---|---|---|---|
| who_2006_infant | 0 | months | f | head_circ | cm | 1 | 33.8787 | 0.03496 | 
| who_2006_infant | 1 | months | f | head_circ | cm | 1 | 36.5463 | 0.03210 | 
| who_2006_infant | 2 | months | f | head_circ | cm | 1 | 38.2521 | 0.03168 | 
| who_2006_infant | 3 | months | f | head_circ | cm | 1 | 39.5328 | 0.03140 | 
| who_2006_infant | 4 | months | f | head_circ | cm | 1 | 40.5817 | 0.03119 | 
| who_2006_infant | 5 | months | f | head_circ | cm | 1 | 41.4590 | 0.03102 | 
Calculate anthropometric measurements
The goal is to calculate expected measurement values from a set of charts, anthropometric measures, ages, genders, and percentiles.
percentiles <- c(3, 10, 50, 90, 97)
measures <- c("weight", "head_circ", "length")
charts <- "fenton_2025" # not included in `peditools` package
df_list <- list()
for (c in charts) {
  for (s in c("m", "f")) {
    for (m in measures) {
      lms <- lmsdata |>
        filter(chart == c, gender == s, measure == m) |>
        unique() |> # in case of duplicate rows; shouldn't be present
        arrange(age)
      # cartesian product, expanding each age of lms to have percentiles
      d <- crossing(lms, data.frame(percentile = percentiles)) |>
        mutate (x = z_lms_to_x(qnorm(percentile / 100.0), L, M, S)) |> 
        # min, max, and even integer ages
        filter((age == min(lms$age)) | (age == round(age)) | age == max(lms$age))
      df_list[[paste0(c, "_", s, "_", m)]] <- d
    }
  }
}
df <- bind_rows(df_list) |> 
  transmute(
    chart, gender, measure, age,
    percentile = as.character(percentile),
    x = round(x, 1)
  )Plot the generated measures
Test to see whether measure calculations look sensible.
df |> ggplot(aes(age, x, color = percentile)) +
  geom_point(size = 0.2) +
  geom_line() +
  facet_grid2(gender ~ measure, scales = "free_y", independent = "y") +
  theme_bw()
Plot points on “paper” chart
The electronic growth chart calculator I created for the Fenton 2013 chart uses growth data for each patient copied from an Excel spreadsheet and pasted into a web page input form field in a particular format. The following code recreates that format.
I updated the old Fenton 2013 PHP scripts to use new Fenton 2025 data and plotted the measures generated above onto the “paper” chart.
df_wide <- df |> 
  mutate(x = as.character(round(x, 1))) |> 
  pivot_wider(names_from = "measure", values_from = "x", values_fill = "") |> 
  select(chart, gender, age, weight, head_circ, length, percentile) |>
  arrange(chart, gender, percentile, age)
write.csv(df_wide, file = "~/Desktop/universal_test.csv", row.names = FALSE)Excel spreadsheet to copy from

Paste into PHP web tool

Output generated by updated Electronic Growth Chart PHP script

 Looking good: the points generated by calculation appear to overly the curves perfectly. Should be ready to deploy soon.