Compare cumulative incidence functions across groups using Gray’s test.
grays_test(
surv,
group=None,
*,
data=None,
cause=1,
)
Gray’s test (1988) is the competing-risks analogue of the log-rank test. While the log-rank test compares overall survival across groups, Gray’s test compares the cumulative incidence function (CIF) for a specific cause across groups, properly accounting for competing events.
The test uses the subdistribution hazard framework: subjects who experience a competing event remain in the risk set with inverse-probability-of-censoring weights, reflecting the fact that they can no longer experience the target cause but still contribute information about its cumulative incidence.
Parameters
surv: Surv | Outcome | str
-
A multi-state Surv response (a Surv with a categorical event). Must have at least two causes. Event codes are 0 for censoring and 1..K for causes. An Outcome (such as gw.Outcome.first_event(...)) or a formula string such as "Surv(etime, factor(cause, c(0, 1, 2), c('censor', 'pcm', 'death'))) ~ sex" is also accepted, with its columns read from data. The right-hand side names the group column(s).
group: Any = None
-
Group labels, one per observation. Can be a Narwhals series, 1-D array, or Python sequence. Must have the same length as surv. At least two groups are required.
cause: int | str = 1
-
The cause of interest to compare across groups. Can be a state label (string) or an integer cause code (1-indexed). Default is 1 (the first cause).
data: Any = None
-
A data frame (pandas, Polars, PyArrow, DuckDB, a lazy frame, …) holding the columns named by the response and
group. When surv is an Outcome or a formula, rows with a missing value in any column used are dropped first, along with the matching rows of any arrays passed alongside.
Returns
TestResult
-
A result object with attributes:
statistic: Chi-square test statistic.
df: Degrees of freedom (number of groups minus one).
p_value: Upper-tail chi-square p-value.
method: "Gray's test" with the cause label.
observed: Dictionary mapping group labels to observed event counts.
expected: Dictionary mapping group labels to expected event counts.
Details
Gray’s test differs from the log-rank test applied to cause-specific hazards. The log-rank test on cause-specific hazards answers “do the cause-specific hazard rates differ?” while Gray’s test answers “do the cumulative incidence functions differ?” These can give different conclusions because the CIF depends on all cause-specific hazards, not just the target one.
The test uses the same IPCW weighting as the Fine-Gray model: subjects with competing events before time t contribute weight G(t-)/G(Ti-), where G is the Kaplan-Meier estimate of the censoring distribution.
Examples
Test whether the cumulative incidence of plasma-cell malignancy (pcm) differs between sexes in the mgus2 dataset:
import greenwood as gw
mg = gw.load_dataset("mgus2", backend="pandas")
cr = gw.Outcome.first_event(
endpoints={"pcm": ("ptime", "pstat"), "death": ("futime", "death")}
)
gw.grays_test(cr, group="sex", cause="pcm", data=mg)
TestResult(method="Gray's test (cause='pcm')", statistic=1.1945, df=1, p_value=0.2744)
Test the other cause (death) across groups:
gw.grays_test(cr, group="sex", cause="death", data=mg)
TestResult(method="Gray's test (cause='death')", statistic=11.6513, df=1, p_value=0.0006416)