| Title: | Age, Sex and Race Matched US Reference Survival |
|---|---|
| Description: | Age, sex and race matched United States reference survival, as consumed by clinical outcomes reports within The Heart and Vascular Institute at the Cleveland Clinic. Reproduces the 'SAS' macro 'usmatchd', which evaluates a stored three-phase parametric hazard fit of Blackstone, Naftel and Turner (1986) <doi:10.1080/01621459.1986.10478314> on the age axis rather than interpolating a life table. Ships the fitted parameter blocks, one set per vintage per stratum, together with a thin evaluator over the 'TemporalHazard' package. The reference vintage is never defaulted, so an analysis must state which reference population it used. |
| Authors: | John Ehrlinger [aut, cre] (ORCID: <https://orcid.org/0000-0002-5340-5154>), The Cleveland Clinic Foundation [cph] (fitted US life-table hazard model parameters) |
| Maintainer: | John Ehrlinger <[email protected]> |
| License: | GPL-3 |
| Version: | 0.1.5 |
| Built: | 2026-10-20 19:48:00 UTC |
| Source: | https://github.com/ehrlinger/hvtiRlifetables |
Reduces the per-patient output of us_matched to the curve a
figure actually carries: the cohort's expected survival at each time, and
its hazard. With by, one such curve per group.
us_cohort_curve(x, by = NULL)us_cohort_curve(x, by = NULL)
x |
A data frame as returned by |
by |
How to group the result. One of:
It carries values, not column names: group by a column of |
A data frame with columns time, smatched and
hmatched, one row per time – or, with by, the grouping
columns first and one row per group per time. Groups and times appear in
order of first appearance in x, so a factor grouping does not
reorder to its levels.
The reduction follows the SAS macro %usmatchd (lines 338-350): the
hazard is converted to a density (hmatched * smatched), the
unweighted cohort means of survival and of the density are taken at
each time, and the mean hazard is recovered by division. It is deliberately
not the mean of the individual hazards; in a heterogeneous cohort
the two differ, and the difference is not cosmetic.
The mean is over survival, not over cumulative hazard, and no
patient is weighted by follow-up. Missing values are not dropped: every
value us_matched returns is finite by construction, so an
NA arriving here is a defect rather than a datum, and averaging
around it would hide it.
us_matched's table argument chooses which strata the
life table is built from – "sexrace", "race",
"sex", "overall". by chooses how the resulting curves
are grouped for reporting – age band, treatment arm, era. The two
axes are independent: a report broken down by age band is normally still
matched on "sexrace".
us_matched, whose individual = FALSE is the
ungrouped case of this function.
x <- us_matched(age = c(55, 60, 78, 82), male = c(1, 0, 1, 0), other = c(0, 0, 1, 1), times = c(0, 5, 10), vintage = "table84") # The cohort curve. us_cohort_curve(x) # The same, broken down by an age band of the study's own choosing. The # grouping is per patient, so it is expanded to x's one-row-per-patient- # per-time shape by id. band <- c("55-79", "55-79", "55-79", "80+") us_cohort_curve(x, by = factor(band[x$id], levels = c("55-79", "80+")))x <- us_matched(age = c(55, 60, 78, 82), male = c(1, 0, 1, 0), other = c(0, 0, 1, 1), times = c(0, 5, 10), vintage = "table84") # The cohort curve. us_cohort_curve(x) # The same, broken down by an age band of the study's own choosing. The # grouping is per patient, so it is expanded to x's one-row-per-patient- # per-time shape by id. band <- c("55-79", "55-79", "55-79", "80+") us_cohort_curve(x, by = factor(band[x$id], levels = c("55-79", "80+")))
Returns the raw fitted model for a single vintage and stratum, for
inspection. us_matched() is the function most callers want.
us_lifetable_model(vintage, stratum)us_lifetable_model(vintage, stratum)
vintage |
Character scalar. One of the identifiers returned by
|
stratum |
Character scalar. One of |
A list with elements vintage (character), stratum (character),
params (named numeric of length 11), status (named integer of length
11, the _STATUS_ gate), flags (named numeric of length 6, metadata
only), and vcov (11 by 11 numeric matrix, unused in this version).
m <- us_lifetable_model("table84", "wm") m$params[["THALF"]] # The _STATUS_ gate: table84's non-white male stratum has no constant # phase. Its C0 is 0 *and absent*, not 0 *and meaning exp(0) = 1*. om <- us_lifetable_model("table84", "om") om$params[["C0"]] om$status[["C0"]]m <- us_lifetable_model("table84", "wm") m$params[["THALF"]] # The _STATUS_ gate: table84's non-white male stratum has no constant # phase. Its C0 is 0 *and absent*, not 0 *and meaning exp(0) = 1*. om <- us_lifetable_model("table84", "om") om$params[["C0"]] om$status[["C0"]]
The fitted three-phase parametric hazard models that the Cleveland Clinic
SAS macro %usmatchd evaluates, one record per vintage per stratum. These
are fitted models, not life tables: %usmatchd runs PROC HAZPRED against
a stored fit on the age axis, with time origin at birth.
us_lifetable_modelsus_lifetable_models
A data frame with 27 rows and 6 columns:
character. One of "table84", "table2008",
"table2023". Vintages are not interchangeable: model structure
differs, not only fitted values.
character. One of "all", "f", "m", "w", "wf",
"wm", plus the vintage's non-white codes – "o", "of", "om" for
table84; "b", "bf", "bm" for table2008 and table2023.
list column of named numeric vectors, length 11:
DELTA, THALF, NU, M, TAU, GAMMA, ALPHA, ETA, E0,
C0, L0.
list column of named integer vectors, length 11. The
_STATUS_ gate. mu = exp(E0 | C0 | L0) only where the
corresponding status is 1; status 0 means the phase is absent
from the model, not that log(mu) is zero.
list column of named numeric vectors, length 6:
G1FLAG, FIXDEL0, FIXMNU1, G3FLAG, FIXGE2, FIXGAE2. These
record which sub-form was fitted. They are metadata only and are never
read during evaluation.
list column of 11 by 11 numeric matrices, the parameter covariance blocks. Nothing in this version reads them; they ship because they are the only route to a confidence band later.
table84 names its non-white strata o, honestly "other". table2008
and table2023 name the same strata b, and the macro's own comment
asserts these are Black-race estimates. For 2023 that assertion is
false: the category is a risk-weighted average of Black, Asian,
American Indian and Hispanic death rates, weighted by number at risk,
stored under B only to keep the macro's naming consistent. See
us_lifetable_vintages(), which reports this per vintage.
Fitted by The Cleveland Clinic Foundation. Built into this package
by data-raw/build-models.R, which is not run at install time. That
script's header names the share location to restore the inputs from —
deliberately kept out of R/, which must carry no literal share path.
Reports the fitted-model vintages this package ships, with the provenance and race semantics needed to choose one. Vintages are not interchangeable — model structure differs, not only fitted values.
us_lifetable_vintages()us_lifetable_vintages()
A data frame with one row per usable vintage and columns:
vintage (character identifier), n_strata (integer, always 9),
nonwhite_code (character, the stratum code this vintage uses for its
non-white category), nonwhite_meaning (character, what that category
actually contains), added (character date the fits were added, where
known, otherwise NA).
There is deliberately no source column: R/ carries no literal share
path. data-raw/build-models.R names the location the fits are restored
from.
nonwhite_meaning before citing a stratumThe table2023 non-white category is stored under code b but is a
risk-weighted average of Black, Asian, American Indian and Hispanic death
rates. It is not Black. The macro's own comment says otherwise and is
wrong.
us_lifetable_vintages()us_lifetable_vintages()
Reproduces the Cleveland Clinic SAS macro %usmatchd: for each patient, a
US reference survival curve matched on age, sex and race, together with its
hazard. This is the dashed comparison line on a clinical survival figure.
us_matched( age, male, other, times, id = seq_along(age), vintage, table = c("sexrace", "race", "sex", "overall"), scale = c("years", "months", "days"), individual = TRUE )us_matched( age, male, other, times, id = seq_along(age), vintage, table = c("sexrace", "race", "sex", "overall"), scale = c("years", "months", "days"), individual = TRUE )
age |
Numeric vector of ages in years, always in years regardless
of |
male |
Numeric vector, |
other |
Numeric vector, |
times |
Numeric vector of follow-up times in the units of |
id |
Optional vector of patient identifiers, recycled into the output.
Defaults to |
vintage |
Character scalar naming the fitted-model vintage. There is no default. Omitting it is an error. See Details. |
table |
How finely to stratify patients, mirroring the macro's
|
scale |
Units of |
individual |
If |
%usmatchd is not a life-table lookup. It evaluates a stored three-phase
parametric hazard fit on the age axis, time origin birth, and reads
conditional survival off that one smooth curve twice. That is why the
resulting hazard is smooth within a one-year age bin where a life table
would be flat.
hmatched is per year regardless of scale. This matches the macro's
source, which assigns _HAZARD without applying SCALEF, notwithstanding
the macro's own header comment to the contrary.
When individual = FALSE, the cohort mean follows the macro's arithmetic:
the hazard is converted to a density (hmatched * smatched), the cohort
means of survival and of the density are taken at each time, and the mean
hazard is recovered by division. It is deliberately not the mean of
the individual hazards.
A data frame. When individual = TRUE, columns id, time,
agesurv (survival from birth to the patient's current age),
smatched (reference survival over times, conditional on having
reached age) and hmatched (the reference hazard), with one row per
patient per time. When individual = FALSE, columns time, smatched
and hmatched only, with one row per time.
vintage has no defaultThe macro's default silently moved twice: from table84 to table2008
between 2010 and 2024, then to table2023 on 2025-12-23. Every job re-run
across either move got different numbers with no signal. An
analysis that does not state its reference vintage is not reproducible, so
this package refuses to guess. State it literally in analysis code.
us_cohort_curve to reduce the individual output to a
cohort curve, or to one curve per group;
us_lifetable_vintages() for the available vintages and what
their non-white stratum actually contains; us_lifetable_model() for the
raw fitted parameters.
# A 70-year-old white male, ten years of follow-up, 1984 fits. r <- us_matched(age = 70, male = 1, other = 0, times = seq(0, 10, by = 2), vintage = "table84") r[, c("time", "smatched", "hmatched")]# A 70-year-old white male, ten years of follow-up, 1984 fits. r <- us_matched(age = 70, male = 1, other = 0, times = seq(0, 10, by = 2), vintage = "table84") r[, c("time", "smatched", "hmatched")]