The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.

tanner: stage line diagrams and SDS for Tanner pubertal stages

This vignette covers material the README doesn’t have room for: a fuller plot_stadia() walkthrough across its main argument combinations, how raw testicular volume (tv) readings in ml are converted to a stage for plotting and SDS, and the statistical method behind calculate_sds() itself. For a quick installation and first-example walkthrough, see the README.

plot_stadia(): argument combinations

plot_stadia() draws one or more patients’ pubertal stage trajectories against the Dutch 1997 reference percentiles. Its main arguments are:

A multi-timepoint boy, followed on genital stage, pubic hair, and testicular volume from age 9 to 15:

boy <- data.frame(
  id = 1,
  age = c(9.1, 10.2, 11.3, 12.1, 12.9, 13.8, 14.6, 15.2),
  sex = "M",
  gen = c(1, 1, 2, 3, 3, 4, 4, 5),
  phb = c(1, 1, 1, 2, 3, 4, 4, 5),
  tv = c(2, 3, 4, 8, 10, 12, 15, 20)
)

plot_stadia(
  data = boy,
  persons = 1,
  type = c(TRUE, TRUE, TRUE),
  plotline = c(FALSE, FALSE, FALSE),
  colors = c("#0060A0", "#00A000", "#A00000"),
  title = "Boy 1: genital, pubic hair & testicular volume",
  padid = FALSE
)

Here plotline is all FALSE, so only the patient’s own trajectories are drawn, without the background reference percentile curves — cleaner when several trajectories share one plot. The background curves (plotline) are independent of which trajectories are actually drawn (type): you can turn them back on to show, say, the tv trajectory alone against all three reference curves for context, with type = c(FALSE, FALSE, TRUE) and plotline = c(TRUE, TRUE, TRUE).

Comparing several patients with overlay

overlay = TRUE draws every person named in persons on one shared plot instead of a separate plot per person. Because a single plot only has one set of reference curves, overlay also takes ovsex to pick which sex’s percentiles to draw in the background — mixing sexes on one overlay plot only makes sense if you’re comparing trajectories against a single reference (or turning plotline off entirely):

boys <- data.frame(
  id = rep(c(1, 2), each = 4),
  age = c(9, 11, 13, 15, 10, 12, 14, 16),
  sex = "M",
  gen = c(1, 2, 3, 5, 1, 1, 3, 4),
  phb = c(1, 2, 3, 5, 1, 2, 3, 4),
  tv = NA
)

plot_stadia(
  data = boys,
  persons = c(1, 2),
  type = c(TRUE, FALSE, FALSE),
  plotline = c(TRUE, FALSE, FALSE),
  overlay = TRUE,
  ovsex = "M",
  title = "Genital stage, two boys overlaid",
  padid = FALSE
)

Testicular volume: a raw ml reading, not a bead index

tv (testicular volume) is recorded as a raw reading in ml, typically from a 12-point Prader orchidometer (1, 2, 3, 4, 5, 6, 8, 10, 12, 15, 20, 25 ml), but neither calculate_sds() nor plot_stadia() require the reading to land exactly on one of those 12 bead volumes — any plausible ml value works directly, with no recoding step.

The two functions handle a between-bead reading differently, because they answer different questions:

# 11ml is strictly between the T10 and T12 Prader bead curves; age must
# be supplied per stage value (no recycling), so it's repeated here
calculate_sds(age = c(13, 13, 13), stage = c(10, 11, 12), type = "tv")
## [1] 0.3163986 0.4806011 0.6589934

The three SDS values increase smoothly and strictly monotonically with ml — 11ml’s SDS sits properly between 10ml’s and 12ml’s, rather than jumping or repeating a neighbor’s value.

The SDS methodology: mid-P values, not LMS

calculate_sds() implements the mid-P value method of van Buuren & Ooms (2009), which is worth understanding explicitly because it is not the LMS/BCCG/BCPE method most R users associate with growth-reference SDS (e.g. centile::y2z(), or the WHO/CDC growth charts). The two methods solve different problems:

Concretely, nl1997$gen holds, for each age, the fraction of boys who have not yet reached each genital stage:

head(tanner::nl1997$gen)
##    age     G2     G3 G4 G5
## 1 8.00 0.1152 0.0112  0  0
## 2 8.25 0.1317 0.0128  0  0
## 3 8.50 0.1494 0.0147  0  0
## 4 8.75 0.1681 0.0167  0  0
## 5 9.00 0.1875 0.0190  0  0
## 6 9.25 0.2079 0.0216  0  0

Column T3 at age 12, for example, is \(P(\text{stage} < 3 \mid \text{age} = 12)\) — read directly off the reference table (interpolated between the nearest tabulated ages by calculate_sds()).

A patient observed at stage \(c\) is treated as a coarsened observation of an unobserved, continuous latent variable — the “true” underlying developmental progress — that is uniformly distributed within the probability interval the observed stage covers:

\[ [P(Y < c \mid X),\ P(Y < c + 1 \mid X)) \]

The mid-P value is the mean of that latent variable:

\[ \pi_c = P(Y < c \mid X) + \frac{P(Y = c \mid X)}{2} \]

which is equivalent to the average of the two surrounding “at least” percentiles:

\[ \pi_c = \frac{P(Y \geq c \mid X) + P(Y \geq c + 1 \mid X)}{2} \]

and converting it to a Z-score is a single qnorm() call:

\[ Z = \Phi^{-1}(1 - \pi_c) \]

This is why calculate_sds() averages the surrounding-stage percentiles rather than looking up a single stage’s percentile directly — the averaging is the mid-P value itself, derived from treating the stage as a coarsened continuous variable, not an ad hoc smoothing step bolted on afterward. The same formula applies unchanged to tv’s continuous ml scale (see above): the only difference is that the two percentiles being averaged come from continuous interpolation along the bead-index scale rather than a lookup at two adjacent integer stages.

Reference

van Buuren, S. and Ooms, J.C.L. (2009). Stage line diagram: An age-conditional reference diagram for tracking development. Statistics in Medicine, 28(11), 1569–1579. https://doi.org/10.1002/sim.3567


  1. calculate_sds()’s nl1997$tv reference starts at the T2 curve (no T1 column); plot_stadia()’s nl1997_lines$tv reference (used to draw the percentile lines) additionally has a T1 curve. Both handle this transparently — it only matters if you’re reading the reference data directly.↩︎

These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.