| Title: | Generalized Competing Event Models with Lunn-McNeil Testing |
|---|---|
| Description: | Fits generalized competing event (GCE) models and estimates covariate effects on omega-plus, the ratio of the hazard for an event of interest to the hazard for a competing event, on both the cause-specific (Cox) and subdistribution (Fine-Gray) hazard scales. Confidence intervals and p-values are obtained from the Lunn-McNeil (1995) stacked (augmented) data approach. The package builds GCE risk scores from the model linear predictor, identifies risk-score cutpoints that maximize the separation in omega-plus between groups, and produces cumulative incidence ("alligator") plots by risk group and calibration plots of predicted versus observed omega-plus. It also compares covariate effects across the primary, competing, and total (composite) events, and estimates covariate effects on the ratio of cumulative incidence functions (a cumulative-incidence-scale GCE model). Methods follow Carmona et al. (2014) <doi:10.1016/j.ijrobp.2014.03.047>, Mell et al. (2024) <doi:10.1016/j.eururo.2023.01.020>, and Lunn and McNeil (1995) <doi:10.2307/2532940>. |
| Authors: | Loren Mell [aut, cre] |
| Maintainer: | Loren Mell <[email protected]> |
| License: | GPL (>= 2) |
| Version: | 0.3.0 |
| Built: | 2026-09-24 10:20:18 UTC |
| Source: | https://github.com/cran/gcemod |
Plots the cause-specific cumulative incidence functions (CIFs) of the event of interest and the competing event over time on a single panel, with risk groups distinguished by color and event types by line type (event of interest solid, competing event dashed). In higher-risk groups the event-of-interest CIF rises above the competing-event CIF and the curves separate like an alligator's open jaws.
The x-axis is truncated at the model's readout time t, with tick marks
at 1-unit (e.g. 1-year) intervals. The y-axis auto-scales its cap to the
smallest of 25/50/75/100% that covers the peak cumulative incidence (so
low-incidence settings are not compressed), with ticks at 10% (or 5% when
capped at 25%). Numbers at risk for each group are shown in an aligned panel
below the plot. Gray's test across groups is attached.
gce_alligator( object, groups = 2, method = c("optimal", "quantile"), criterion = c("omegaplus", "cif"), xlab = "Time (years)", ylab = "Cumulative incidence", group.lab = "Risk group", risk.table = TRUE, ymax = NULL )gce_alligator( object, groups = 2, method = c("optimal", "quantile"), criterion = c("omegaplus", "cif"), xlab = "Time (years)", ylab = "Cumulative incidence", group.lab = "Risk group", risk.table = TRUE, ymax = NULL )
object |
|
groups |
a |
method |
grouping rule used when |
criterion |
optimal-search criterion when |
xlab, ylab
|
axis labels. |
group.lab |
legend title for the risk groups (default "Risk group"). |
risk.table |
logical; show numbers at risk in an aligned panel below the
plot (default |
ymax |
optional numeric upper cap for the y-axis (0-1). If |
A ggplot/patchwork object. The Gray's-test result, the
plotted CIF data, the numbers-at-risk table, and the cutpoints are attached
as attributes "grays_test", "data", "n.risk", and
"cutpoints".
data(hn) Ind <- data.frame(event = as.integer(hn$status == 1), competing = as.integer(hn$status == 2)) fit <- gcecox(hn$time, Ind, hn[, c("age", "t_cat", "p16")], M = 5, t = 5) gce_alligator(fit, groups = 2)data(hn) Ind <- data.frame(event = as.integer(hn$status == 1), competing = as.integer(hn$status == 2)) fit <- gcecox(hn$time, Ind, hn[, c("age", "t_cat", "p16")], M = 5, t = 5) gce_alligator(fit, groups = 2)
Groups subjects into equal-sized rank groups on the GCE risk score and
compares the model-predicted quantity with an observed estimate in each group.
For omega+ models (gcecox, gcefg) the predicted
(or ) is compared with observed cause-specific
Nelson-Aalen cumulative hazards at time t:
and
. For a gcecif fit the
predicted cumulative-incidence ratio (or share ) is
compared with the observed Aalen-Johansen cumulative incidence within the
group: and
. A calibration plot (predicted vs.
observed with the identity line) and the underlying table are returned.
gce_calibration(object, which = NULL, groups = 5)gce_calibration(object, which = NULL, groups = 5)
object |
|
which |
quantity to calibrate. For omega+ models |
groups |
number of rank groups (default |
A ggplot object (predicted vs. observed). The calibration
table is attached as attribute "data".
data(hn) Ind <- data.frame(event = as.integer(hn$status == 1), competing = as.integer(hn$status == 2)) fit <- gcecox(hn$time, Ind, hn[, c("age", "t_cat", "p16")], M = 5, t = 5) gce_calibration(fit, which = "omegaplus", groups = 5)data(hn) Ind <- data.frame(event = as.integer(hn$status == 1), competing = as.integer(hn$status == 2)) fit <- gcecox(hn$time, Ind, hn[, c("age", "t_cat", "p16")], M = 5, t = 5) gce_calibration(fit, which = "omegaplus", groups = 5)
Splits subjects into ordered risk groups on the GCE risk score. With
method = "optimal" the cutpoint(s) maximize a separation criterion by a
greedy forward search; with method = "quantile" equal-sized groups are
used.
The default criterion, "omegaplus", maximizes the between-group
separation in log (equivalently, the risk score /
linear predictor). This is the additive scale on which the joint
event-of-interest-vs-competing contrast is defined, and –
unlike separation measured on the exponential scale – it is
not sensitive to a few extreme high-risk subjects, so the search does
not chase high-risk tails and tends to return balanced, well-separated groups.
The alternative "cif" maximizes a joint Gray's test statistic (summed
across both competing events) on the observed cumulative incidence.
gce_cutpoints( object, groups = 2, method = c("optimal", "quantile"), criterion = c("omegaplus", "cif"), min.prop = 0.1, nperm = 0, labels = NULL )gce_cutpoints( object, groups = 2, method = c("optimal", "quantile"), criterion = c("omegaplus", "cif"), min.prop = 0.1, nperm = 0, labels = NULL )
object |
a |
groups |
number of groups (default |
method |
|
criterion |
optimal-search criterion: |
min.prop |
minimum proportion of subjects per group (default |
nperm |
number of permutations for the selection-adjusted p-value
(default |
labels |
optional character labels for the groups (low to high risk). |
A list with: groups (ordered factor), cutpoints (on the
risk-score scale), summary (per-group n, events, mean
/ ), and test. The test element
reports the Lunn-McNeil contrast between groups – a test
that the ratio of cause-specific (or subdistribution) hazards differs across
groups – with the univariable ratio estimate and CI
($omegaplus), the joint test ($statistic, $df,
$p.value), the per-event Gray tests for comparison
($individual_gray), and a selection-adjusted $p.perm when
nperm > 0.
data(hn) Ind <- data.frame(event = as.integer(hn$status == 1), competing = as.integer(hn$status == 2)) fit <- gcecox(hn$time, Ind, hn[, c("age", "t_cat", "p16")], M = 5, t = 5) gce_cutpoints(fit, groups = 2) # with a selection-adjusted permutation p-value (slower) gce_cutpoints(fit, groups = 2, nperm = 200)data(hn) Ind <- data.frame(event = as.integer(hn$status == 1), competing = as.integer(hn$status == 2)) fit <- gcecox(hn$time, Ind, hn[, c("age", "t_cat", "p16")], M = 5, t = 5) gce_cutpoints(fit, groups = 2) # with a selection-adjusted permutation p-value (slower) gce_cutpoints(fit, groups = 2, nperm = 200)
Computes the cause-specific concordance (C-index) of the GCE risk score for
the event of interest. For calibration (predicted vs. observed
), see gce_calibration.
gce_performance(object, conf.level = 0.95)gce_performance(object, conf.level = 0.95)
object |
a |
conf.level |
confidence level for the C-index interval (default
|
The C-index is computed with concordance using the
event of interest as the outcome and the GCE risk score as the predictor.
Higher risk scores denote higher risk; the statistic is oriented so that
values above 0.5 indicate discrimination in the expected direction, and the
"flipped" attribute of the result records whether the orientation was
reversed.
An object of class "gce_performance": a named numeric vector
with elements estimate, se, lower, and upper,
carrying attributes conf.level and flipped. Printing the
object displays the C-index with its confidence interval.
data(hn) Ind <- data.frame(event = as.integer(hn$status == 1), competing = as.integer(hn$status == 2)) fit <- gcecox(hn$time, Ind, hn[, c("age", "t_cat", "p16")], M = 5, t = 5) gce_performance(fit)data(hn) Ind <- data.frame(event = as.integer(hn$status == 1), competing = as.integer(hn$status == 2)) fit <- gcecox(hn$time, Ind, hn[, c("age", "t_cat", "p16")], M = 5, t = 5) gce_performance(fit)
Computes the GCE risk score for new subjects using the covariate means/SDs
and coefficients stored in a fitted model. The score is the normalized
(mean-centered) linear predictor, so exp(riskscore) is the subject's
omega+ relative to the average subject in the fitting data (a value of
1 = same as average, 2 = twice the average omega+). For cause-specific
(gcecox) models the absolute predicted omega+ and omega at the
model's time t are also returned.
gce_riskscore(object, newdata)gce_riskscore(object, newdata)
object |
|
newdata |
a data frame containing the covariates used to fit
|
A data frame with riskscore and exp(riskscore) relative
to the average subject: omegaplus_rel for omega+ models (with
absolute omegaplus and omega at time t for
cause-specific fits), or rho_rel for a gcecif fit.
data(prostate) Ind <- data.frame(as.integer(prostate$status == 1), as.integer(prostate$status == 2)) fit <- gcecox(prostate$time, Ind, prostate[, c("age", "psa", "gleason")], M = 5, t = 10) gce_riskscore(fit, newdata = prostate[1:5, ])data(prostate) Ind <- data.frame(as.integer(prostate$status == 1), as.integer(prostate$status == 2)) fit <- gcecox(prostate$time, Ind, prostate[, c("age", "psa", "gleason")], M = 5, t = 10) gce_riskscore(fit, newdata = prostate[1:5, ])
Returns each covariate's mean and standard deviation (from the data used to
fit the model), together with the per-SD log-omega+ coefficient and omega+
ratio. These are the quantities needed to reproduce the normalized risk score
for new subjects (see gce_riskscore) and to interpret how far a
subject sits from the average patient.
gce_scaling(object)gce_scaling(object)
object |
A data frame with columns term, mean, sd,
coef_perSD (log ratio per 1 SD) and the per-SD ratio
(omegaplus_ratio_perSD for omega+ models,
rho_ratio_perSD for a gcecif fit).
data(prostate) Ind <- data.frame(as.integer(prostate$status == 1), as.integer(prostate$status == 2)) fit <- gcecox(prostate$time, Ind, prostate[, c("age", "psa", "gleason")], M = 5, t = 10) gce_scaling(fit)data(prostate) Ind <- data.frame(as.integer(prostate$status == 1), as.integer(prostate$status == 2)) fit <- gcecox(prostate$time, Ind, prostate[, c("age", "psa", "gleason")], M = 5, t = 10) gce_scaling(fit)
With effects = FALSE (the default) the omega+ ratio estimates are
returned. With effects = TRUE a covariate-effects comparison table is
returned that juxtaposes, for each covariate, the hazard ratio for the primary
(event-of-interest) hazard, the competing-event hazard, the total (composite,
any-event) hazard, and the relative hazard ratio (the omega+ ratio) from the
GCE model, each with a confidence interval. This lets the investigator see
whether a covariate acts mainly on the event of interest, the competing event,
both, or on their balance.
gce_tidy(object, effects = FALSE, ...)gce_tidy(object, effects = FALSE, ...)
object |
a |
effects |
logical; if |
... |
unused. |
The individual- and total-event columns are estimated on the same hazard scale
as the fitted model: cause-specific Cox models for a gcecox fit,
and Fine-Gray subdistribution models for a gcefg fit. The total
(composite) event has no competing event, so its subdistribution and
cause-specific hazards coincide and a Cox model on the composite endpoint is
used in both cases.
If effects = FALSE, a data frame with columns term,
log_ratio, ratio, conf.low, conf.high,
p.value. If effects = TRUE, a data frame with columns
term, primary, competing, total, and
relative, each a formatted "HR (low, high)" string.
data(hn) Ind <- data.frame(event = as.integer(hn$status == 1), competing = as.integer(hn$status == 2)) fit <- gcecox(hn$time, Ind, hn[, c("age", "t_cat", "p16")], M = 5, t = 5) gce_tidy(fit) gce_tidy(fit, effects = TRUE)data(hn) Ind <- data.frame(event = as.integer(hn$status == 1), competing = as.integer(hn$status == 2)) fit <- gcecox(hn$time, Ind, hn[, c("age", "t_cat", "p16")], M = 5, t = 5) gce_tidy(fit) gce_tidy(fit, effects = TRUE)
Fits a generalized competing event (GCE) model on the cumulative
incidence scale, estimating covariate effects on the ratio of cumulative
incidences at a landmark time t, where
and are the cumulative incidence functions of the event of
interest and the competing event. is the odds that a subject's
realized event by time t is the event of interest rather than the
competing event, so gcecif() is a cumulative-incidence-scale companion
to the cause-specific (gcecox) and subdistribution
(gcefg) omega+ models and returns a compatible "gcemod"
object.
Effects are estimated by jackknife pseudo-observation regression. Two
equivalent parameterizations are provided (both estimate the effect on
): method = "logit" (default) regresses pseudo-values
of the bounded share by a logit-link GEE, and
method = "logratio" regresses pseudo-values of directly
by an identity-link GEE (ordinary least squares). Both use a robust (sandwich)
variance. A GCE risk score is formed from the model linear predictor, so
exp(riskscore) is a subject's relative to the average subject
(see gce_riskscore, gce_scaling).
gcecif( Time, Ind, Cov, M = 5, t, method = c("logit", "logratio"), ridge = 0, conf.level = 0.95 )gcecif( Time, Ind, Cov, M = 5, t, method = c("logit", "logratio"), ridge = 0, conf.level = 0.95 )
Time |
numeric vector of follow-up times. |
Ind |
a data frame of event indicators: column 1 = event of interest,
column 2 = competing event ( |
Cov |
a data frame of covariates (factors are expanded automatically). |
M |
number of bins for the |
t |
landmark time at which |
method |
estimation scale: |
ridge |
non-negative ridge penalty on the covariate coefficients (not the
intercept), used only with |
conf.level |
confidence level (default |
Because with the all-cause
survivor, is a survival-weighted time-average of the
instantaneous cause-specific hazard ratio ; under a
time-constant it equals exactly. The ratio
of cumulative incidences is a non-collapsible, marginal-over-time measure, so
exp(coef) is interpreted as a conditional association with the realized
event mix, not a mechanism-level per-cause effect.
The estimand is defined for a genuine competing-risks setting, in which both
and are appreciable at the landmark t. If the
competing event is effectively absent, or t precedes appreciable
competing-event incidence, is weakly identified; the optional
ridge penalty stabilizes such fits.
An object of class "gcemod" (subclass "gcecif") with:
coef (log ratios), result (ratio table with CIs and
p-values), omnibus (Wald test that all ratios equal 1),
riskscore (linear predictor), scaling (covariate means/SDs and
per-SD effects), F1, F2, rho0 (baseline at the
mean covariate profile), rho (per-subject fitted ),
pseudo (per-subject pseudo-values), and the design matrix, time, and
cause coding used by gce_tidy.
Andersen PK, Klein JP, Rosthoj S (2003). Generalized linear models for correlated pseudo-observations, with applications to multi-state models. Biometrika 90:15-27.
gcecox, gcefg, gce_tidy,
gce_riskscore
data(prostate) Ind <- data.frame(event = as.integer(prostate$status == 1), competing = as.integer(prostate$status == 2)) Cov <- prostate[, c("age", "psa", "gleason", "t2b", "comorbidity")] fit <- gcecif(prostate$time, Ind, Cov, t = 10) summary(fit) gce_tidy(fit)data(prostate) Ind <- data.frame(event = as.integer(prostate$status == 1), competing = as.integer(prostate$status == 2)) Cov <- prostate[, c("age", "psa", "gleason", "t2b", "comorbidity")] fit <- gcecif(prostate$time, Ind, Cov, t = 10) summary(fit) gce_tidy(fit)
Fits a generalized competing event (GCE) model on the cause-specific hazard
scale and estimates covariate effects on (event of interest
vs. competing event) using the Lunn-McNeil stacked model (see
lunnmcneil), with 95% confidence intervals, Wald p-values, and
an omnibus test. A GCE risk score is formed from the model linear predictor,
and per-subject is returned for
comparison with observed values.
gcecox( Time, Ind, Cov, M = 5, t, conf.level = 0.95, cluster.se = TRUE, standardize = FALSE )gcecox( Time, Ind, Cov, M = 5, t, conf.level = 0.95, cluster.se = TRUE, standardize = FALSE )
Time |
numeric vector of follow-up times. |
Ind |
a data frame of event indicators. Column 1 = event of interest,
column 2 = competing event ( |
Cov |
a data frame of covariates. |
M |
number of bins for the |
t |
time point at which per-subject |
conf.level |
confidence level (default |
cluster.se |
logical; cluster-robust SEs in the Lunn-McNeil model
(default |
standardize |
logical; controls only the reported omega+ ratio
scale (default |
An object of class "gcemod" (subclass "gcecox") with:
coef (log ratios), result (ratio table with
CIs and p-values), omnibus (Lunn-McNeil global test),
riskscore (linear predictor), omega / omegaplus
(per-subject values at t), omegaplot and omegatimeplot
(ggplot objects), and the fitted Lunn-McNeil model.
gcefg, lunnmcneil, gce_cutpoints
data(hn) Ind <- data.frame(event = as.integer(hn$status == 1), competing = as.integer(hn$status == 2)) Cov <- hn[, c("age", "smoker", "t_cat", "n_cat", "p16")] fit <- gcecox(hn$time, Ind, Cov, M = 5, t = 5) summary(fit)data(hn) Ind <- data.frame(event = as.integer(hn$status == 1), competing = as.integer(hn$status == 2)) Cov <- hn[, c("age", "smoker", "t_cat", "n_cat", "p16")] fit <- gcecox(hn$time, Ind, Cov, M = 5, t = 5) summary(fit)
Fits a generalized competing event (GCE) model on the subdistribution hazard
scale and estimates covariate effects on using a
Lunn-McNeil-style stacked Fine-Gray model (see lunnmcneil with
type = "finegray"), giving 95% confidence intervals, Wald p-values,
and an omnibus test from one joint model. A GCE risk score is formed from the
model linear predictor, and per-subject / at
time t are returned for comparison with observed values.
gcefg(Time, Ind, Cov, M = 5, t, conf.level = 0.95, standardize = FALSE)gcefg(Time, Ind, Cov, M = 5, t, conf.level = 0.95, standardize = FALSE)
Time |
numeric vector of follow-up times. |
Ind |
a data frame of event indicators. Column 1 = event of interest,
column 2 = competing event ( |
Cov |
a data frame of covariates (factors are expanded to indicators). |
M |
number of bins for the |
t |
time point at which per-subject |
conf.level |
confidence level (default |
standardize |
logical; controls only the reported omega+ ratio
scale (default |
An object of class "gcemod" (subclass "gcefg") with the
same components as gcecox on the subdistribution scale.
data(hn) Ind <- data.frame(event = as.integer(hn$status == 1), competing = as.integer(hn$status == 2)) Cov <- hn[, c("age", "smoker", "t_cat", "n_cat", "p16")] fit <- gcefg(hn$time, Ind, Cov, M = 5, t = 5) summary(fit)data(hn) Ind <- data.frame(event = as.integer(hn$status == 1), competing = as.integer(hn$status == 2)) Cov <- hn[, c("age", "smoker", "t_cat", "n_cat", "p16")] fit <- gcefg(hn$time, Ind, Cov, M = 5, t = 5) summary(fit)
An example head-and-neck cancer competing-risks dataset used to illustrate the
gcemod functions. The data are provided for illustration only and do not
represent real patients. The event of interest is cancer recurrence and the
competing event is death without recurrence; recurrence is the more frequent
event (recurrence-dominant, ). Contrast with
prostate.
hnhn
A data frame with 1000 rows and 11 variables:
sequential subject identifier
follow-up time in years
competing-risks status: 0 = censored, 1 = cancer recurrence (event of interest), 2 = death without recurrence (competing event)
age at diagnosis (years)
ECOG performance status (factor: 0, 1, 2)
sex indicator (1 = female)
smoking indicator (1 = more than 10 pack-years)
T category (factor: 1-4)
N category (factor: 0-3)
p16 status (1 = positive)
primary site (factor: oropharynx, larynx, hypopharynx, oralcavity)
Estimates covariate effects on (the ratio of the hazard for
the event of interest to the hazard for the competing event) from a single
stacked ("augmented") model, following Lunn and McNeil (1995). Each subject
contributes one record per event type; the model is stratified by event type
and the covariate-by-event-type interaction estimates
with model-based standard errors,
per-covariate Wald tests, and an omnibus multivariate Wald test.
lunnmcneil( Time, cause, Cov, event = 1, competing = 2, type = c("coxph", "finegray"), cluster.se = TRUE, conf.level = 0.95, standardize = FALSE, ... )lunnmcneil( Time, cause, Cov, event = 1, competing = 2, type = c("coxph", "finegray"), cluster.se = TRUE, conf.level = 0.95, standardize = FALSE, ... )
Time |
numeric vector of follow-up times. |
cause |
event indicator per subject: |
Cov |
a data frame of covariates (factors are expanded to indicators). |
event |
code marking the event of interest (default |
competing |
code marking the competing event (default |
type |
|
cluster.se |
logical; cluster-robust (subject) SEs (default |
conf.level |
confidence level (default |
standardize |
logical; controls only the reported coefficient
scale (default |
... |
passed to |
Two hazard scales are supported:
type = "coxph"Cause-specific hazards. The augmented data set
duplicates each subject into an "event" stratum and a "competing" stratum;
the interaction coefficient is exactly the difference in cause-specific
log-hazards, i.e. . This is the original Lunn-McNeil
construction and the interaction MLE equals the difference of two separate
cause-specific Cox fits.
type = "finegray"Subdistribution hazards. Risk-set-weighted
data are generated for each event type with
finegray, stacked, and fit with a single weighted,
event-type-stratified coxph. The interaction
estimates the difference in subdistribution log-hazards. Because subjects
recur across the weighted risk sets, cluster-robust (subject) standard
errors are used. This is an extension of Lunn-McNeil to the
subdistribution scale.
Subject records are duplicated in both cases, so cluster-robust standard errors (clustered on subject) are the default.
An object of class "gce_lunnmcneil": a list with the fitted
model ($fit), the omega+ coefficient table ($omegaplus), the
reference (competing-event) coefficients ($beta_competing), the
omnibus test ($omnibus), the design column names ($terms),
and the hazard scale ($type).
Lunn M, McNeil D (1995) Applying Cox regression to competing risks. Biometrics 51:524-32.
data(hn) Cov <- hn[, c("age", "smoker", "t_cat", "n_cat", "p16")] lunnmcneil(hn$time, hn$status, Cov, type = "coxph") lunnmcneil(hn$time, hn$status, Cov, type = "finegray")data(hn) Cov <- hn[, c("age", "smoker", "t_cat", "n_cat", "p16")] lunnmcneil(hn$time, hn$status, Cov, type = "coxph") lunnmcneil(hn$time, hn$status, Cov, type = "finegray")
An example prostate cancer competing-risks dataset used to illustrate the
gcemod functions. The data are provided for illustration only and do not
represent real patients. The event of interest is distant metastasis or
prostate-cancer death and the competing event is death from other causes; the
competing event is the more frequent (competing-death-dominant,
). Contrast with hn.
prostateprostate
A data frame with 1000 rows and 9 variables:
sequential subject identifier
follow-up time in years
competing-risks status: 0 = censored, 1 = distant metastasis or prostate-cancer death (event of interest), 2 = death from other causes (competing event)
age at diagnosis (years)
performance status (factor: 0, 1)
pre-treatment PSA (ng/mL, capped at 20)
Gleason grade group (factor: "<=6", "3+4", "4+3", ">=8")
stage indicator (1 = T2b or higher)
significant comorbidity indicator (1 = yes)