Proportional-hazards test results (Grambsch-Therneau).
ZPHResult(
transform,
per_term,
global_test,
windows=None,
)
A key assumption of the Cox proportional hazards model is that the hazard ratio between any two subjects is constant over time (hence “proportional”). When this assumption is violated (for example, if a treatment effect diminishes over time) the Cox model may produce biased estimates. The Grambsch-Therneau proportional hazards test checks this assumption by testing whether scaled residuals are correlated with time.
ZPHResult holds the test results obtained from a fitted Cox model’s cox_zph() method. It provides both per-term tests (one for each covariate) and a global test (jointly across all terms). Each test includes a chi-squared test statistic, degrees of freedom, and p-value. Results can be printed, accessed via dictionary keys, or exported to pandas/polars/ pyarrow DataFrames for further analysis or visualization.
The test uses scaled Schoenfeld residuals, which have a known asymptotic distribution under the proportional hazards assumption. Large chi-squared values or small p-values (typically p < 0.05) suggest violation of the assumption. When the assumption is violated, stratified analysis or time-dependent covariate models may be more appropriate.
Attributes
transform: str
-
The transformation applied to time when computing the test (e.g., identity, log, rank).
per_term: dict[str, dict[str, float]]
-
Dictionary mapping each covariate name to {chisq, df, p_value} dict.
global_test: dict[str, float]
-
Dictionary with
{chisq, df, p_value} for the joint test across all terms.
Examples
A ZPHResult comes from a fitted model’s cox_zph method. Fit a Cox model to the bundled lung dataset, run the proportional-hazards test, and print the result:
import greenwood as gw
# Load data and fit a Cox model
lung = gw.load_dataset("lung", backend="polars")
y = gw.Surv.right(lung["time"], event=(lung["status"] == 2))
cox = gw.CoxPH().fit(y, lung[["age", "sex"]])
# Run the proportional-hazards test
zph = cox.cox_zph()
zph
ZPHResult(transform='identity', age: p=0.7065, sex: p=0.0992, GLOBAL p=0.2425)
Methods
|
Name
|
Description
|
|
to_frame()
|
Return the test table as a DataFrame.
|
to_frame()
Return the test table as a DataFrame.
to_frame(
*,
detail="global",
format=None,
)
Parameters
detail: str = "global"
-
Level of detail. "global" (default) returns the overall test (one row per term plus GLOBAL). "windows" returns per-window results when breaks was used in cox_zph(). Falls back to "global" when no windows are available.
format: str | None = None
-
Output format:
None (default), "pandas", "polars", or "pyarrow".
Returns
pandas.DataFrame, polars.DataFrame, or pyarrow.Table
-
A table with test statistics. With
detail="windows", includes window and n_events columns.
Examples
import greenwood as gw
lung = gw.load_dataset("lung", backend="polars")
y = gw.Surv.right(lung["time"], event=(lung["status"] == 2))
cox = gw.CoxPH().fit(y, lung[["age", "sex"]])
zph = cox.cox_zph()
zph.to_frame(format="polars")
shape: (3, 4)| term | chisq | df | p_value |
|---|
| str | f64 | i64 | f64 |
| "age" | 0.141748 | 1 | 0.70655 |
| "sex" | 2.718401 | 1 | 0.099197 |
| "GLOBAL" | 2.833391 | 2 | 0.242514 |