| Title: | Generalized Competing Event Models with Lunn-McNeil Testing |
|---|---|
| Description: | Tools for fitting generalized competing event (GCE) models and estimating 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. 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-25 09:54:22 UTC |
| Source: | https://github.com/lmell-source/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 colour 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 (or ) with an
observed estimate in each group. Observed values use cause-specific
Nelson-Aalen cumulative hazards within the group at time t:
and
. A calibration plot (predicted
vs. observed with the identity line) and the underlying table are returned.
gce_calibration(object, which = c("omegaplus", "omega"), groups = 5)gce_calibration(object, which = c("omegaplus", "omega"), groups = 5)
object |
|
which |
|
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 |
|
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, 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, nperm = 200)
Reports 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.
Invisibly, a named numeric vector cindex (estimate, se, lower,
upper). The value is also printed.
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 |
a |
newdata |
a data frame containing the covariates used to fit
|
A data frame with riskscore, omegaplus_rel
(= exp(riskscore)), and, for cause-specific models, omegaplus
and omega (absolute, at time t).
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-omega+ ratio per 1 SD) and
omegaplus_ratio_perSD.
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 |
|
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 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)