ZPHResult

Proportional-hazards test results (Grambsch-Therneau).

Usage

Source

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.

Usage

Source

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