Skip to contents

A nomogram turns a regression model into something a clinician can use with a pencil: one axis per predictor, scaled in points, a total, and an axis that reads the prediction off the total. ggnomogram() draws one from a fitted model and gives every predictor a handle. Drag a handle to a patient’s value, click a category or use the arrow keys, and the points, the total and the prediction with its 95% confidence interval follow, computed in the page from the model’s coefficients and their covariance.

A logistic model

Hosmer and Lemeshow’s low birth weight data ship with MASS. This model has a spline for the mother’s age and an interaction between smoking and hypertension, which a nomogram has to handle:

bw <- MASS::birthwt
bw$race <- factor(bw$race, labels = c("White", "Black", "Other"))
bw$smoke <- factor(bw$smoke, labels = c("No", "Yes"))
bw$ht <- factor(bw$ht, labels = c("No", "Yes"))
fit <- glm(low ~ splines::ns(age, 3) + lwt + race + smoke * ht,
           family = binomial, data = bw)

ggnomogram(
  fit,
  outcome = "Risk of low birth weight",
  labels = c(age = "Mother's age (years)", lwt = "Weight at last period (lb)",
             race = "Race", smoke = "Smoked in pregnancy", ht = "Hypertension"),
  title = "Low birth weight"
)

The age effect falls, rises and falls again across its range, so its axis is folded onto three lines, the way rms draws a nonlinear term: drag the handle along whichever line holds the age you want. Hypertension’s effect depends on smoking, so its axis is drawn for the current smoking status and redraws when that changes; the note under its label says which. The prediction is the same one predict() gives:

n <- ggnomogram(fit)
nomogram_predict(n, list(age = 25, lwt = 120, race = "Black", smoke = "Yes", ht = "No"))
#> $points
#> [1] 240.5572
#> 
#> $lp
#> [1] 1.140621
#> 
#> $se
#> [1] 0.6097766
#> 
#> $outputs
#>               output  estimate     lower     upper
#> 1 Probability of low 0.7577936 0.4863736 0.9117958

nomogram_predict() computes predictions the way the widget does, so it is a way to check a nomogram or to read predictions for a list of patients.

A Cox model

For a Cox model the nomogram predicts survival at the times given, with the same interval survival::survfit() gives for the patient. A stratified model, here by sex, gets a choice of stratum under the plot, since each stratum has its own baseline:

lung <- survival::lung
lung$sex <- factor(lung$sex, labels = c("Male", "Female"))
cox <- survival::coxph(survival::Surv(time, status) ~ age + ph.ecog + survival::strata(sex),
                       data = lung)
ggnomogram(cox, times = c("6 months" = 182, "1 year" = 365),
           labels = c(age = "Age (years)", ph.ecog = "ECOG performance status"),
           title = "Survival in advanced lung cancer")

Ordinal and mixed models

An ordinal model gets one axis for the probability of each category or above, and the probability of every category beside the plot:

bw$visits <- factor(pmin(bw$ftv, 2), labels = c("None", "One", "Two or more"))
ggnomogram(MASS::polr(visits ~ age + race, data = bw, Hess = TRUE),
           labels = c(age = "Mother's age (years)"),
           title = "Physician visits in the first trimester")

A mixed model predicts for a typical cluster, with the random effects at zero. On a nonlinear link, as in a logistic mixed model, that is the prediction for an average cluster, not the average over clusters, and the interval reflects the fixed effects only; the section under the plot says so.

sleep <- lme4::lmer(Reaction ~ Days + (Days | Subject), data = lme4::sleepstudy)
ggnomogram(sleep, outcome = "Reaction time (ms)",
           labels = c(Days = "Days of sleep deprivation"))

Models it reads

model prediction
lm(), nlme::gls(), quantreg::rq(), rms::ols() the mean, with a prediction interval for lm()
glm(), MASS::glm.nb(), geepack::geeglm(), logistf::logistf(), rms::lrm(), mgcv::gam() the mean on the response scale through the link
lme4::lmer(), lme4::glmer(), nlme::lme(), glmmTMB::glmmTMB() the prediction for a typical cluster
survival::coxph(), rms::cph() survival at times, by stratum
survival::survreg(), rms::psm() the median survival time and survival at times
MASS::polr(), ordinal::clm(), rms::orm() the probability of each category or above
nnet::multinom() the odds of each category against the reference, and every probability

Every class is checked against its package’s own predict() in the package’s tests. Transformations, polynomials and splines work because the points come from the model’s design matrix. Interactions between two numeric predictors are drawn with the second axis for the current value of the first; three or more interacting numeric predictors cannot be drawn.

Options

argument effect
values the patient the widget starts at, and the static copy’s conditions
labels axis labels for the predictors
ranges ranges of numeric axes; the default runs from the 2.5th to the 97.5th percentile
outcome labels of the prediction axes
times times for the survival axes of a survival model
level the category a static copy of a multinomial nomogram is scaled on

A static copy from graph_plot() or graph_save() is the classic printed nomogram, without the handles; on a dark page the widget takes a dark palette of its own.