ZPHResult

Proportional-hazards test results (Grambsch-Therneau).

Usage

Source

ZPHResult(
    transform,
    per_term,
    global_test,
)

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

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
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 (one row per term plus GLOBAL).

to_frame()

Return the test table as a DataFrame (one row per term plus GLOBAL).

Usage

Source

to_frame(*, format=None)

The table contains proportional hazards test statistics for each covariate plus a global test across all terms. One row represents one term in the model.

Parameters
format: str | None = None
Output format: None (default), "pandas", "polars", or "pyarrow". When None, a backend is auto-detected (Polars, then Pandas, then PyArrow).
Returns
pandas.DataFrame, polars.DataFrame, or pyarrow.Table
A table with columns for term, test statistic, p-value, and other diagnostics. Includes a GLOBAL row.
Raises
ImportError
If the requested (or, when auto-detecting, any) DataFrame library is not installed.
Examples

Fit a Cox model, run the proportional-hazards test, and export the test table as a Polars frame:

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)
termchisqdfp_value
strf64i64f64
"age"0.14174810.70655
"sex"2.71840110.099197
"GLOBAL"2.83339120.242514

The table shows the proportional hazards assumption test results for each term, with the GLOBAL row testing the overall assumption. Request a different backend with format=:

zph.to_frame(format="pandas")
term chisq df p_value
0 age 0.141748 1 0.706550
1 sex 2.718401 1 0.099197
2 GLOBAL 2.833391 2 0.242514