CoxPH

Cox proportional hazards model.

Usage

Source

CoxPH(
    *,
    ties="efron",
    conf_level=0.95,
)

The Cox proportional hazards model is the most widely used regression method for survival data. It models the hazard (instantaneous risk of an event) as a multiplicative function of covariates: h(t \mid x) = h_0(t) \exp(\beta^\top x). The model is semi-parametric: the baseline hazard h_0(t) is left unspecified (estimated non-parametrically), while covariate effects are estimated parametrically through the log-hazard-ratio coefficients \beta.

To use this model, call fit() with a right-censored or counting-process Surv response and a design matrix of covariates (2-D array or DataFrame). The model automatically handles stratification (via by= in fit), tied event times (via configurable tie-handling methods), and can compute predictions, baseline hazards, and diagnostic residuals. Results include coefficient estimates with confidence intervals, hazard ratios, standard errors, and global significance tests.

The implementation uses maximum partial likelihood to estimate coefficients. Variance estimates use the observed information matrix (Hessian). The model assumes proportional hazards: the ratio of hazards between two subjects remains constant over time. This can be checked using the cox_zph() method for formal tests or diagnostic plots.

Parameters

ties: str = "efron"

Tie-handling method: "efron" (default, as in R) or "breslow".

conf_level: float = 0.95
Confidence level for coefficient and hazard-ratio intervals (default 0.95).

Returns

Fitted estimator
Call fit() to produce a fitted estimator with cached results (coef_, hazard_ratio_, std_error_, z_, p_value_, conf_low_, conf_high_, concordance_, lr_stat_, df_), accessible as arrays or exported to DataFrames.

Details

Call fit(surv, covariates) with a Surv response and a design (a 2-D array or a dataframe of covariates). Rows with missing values are dropped (complete-case, as in R’s default na.omit). Results are exposed as arrays (coef_, std_error_, hazard_ratio_, …) and as tidy frames via to_frame() (optionally format=) and greenwood.tidy.

Examples

Build a Surv response from the bundled lung dataset and fit the model on age and sex. Printing the fitted object reports the coefficient table and global tests in the style of R’s summary.coxph.

import greenwood as gw

# Load data and build a right-censored response
lung = gw.load_dataset("lung", backend="polars")
y = gw.Surv.right(lung["time"], event=(lung["status"] == 2))

# Fit a Cox model with age and sex as covariates
cox = gw.CoxPH().fit(y, lung[["age", "sex"]])
cox
CoxPH (Cox proportional hazards model, ties='efron')

        coef  exp(coef)  se(coef)       z         p
age  0.01705      1.017  0.009223   1.848   0.06459
sex  -0.5132     0.5986    0.1675  -3.065  0.002178

n = 228, events = 165
Likelihood ratio test = 14.12 on 2 df, p = 0.0008574

Hazard ratios (and their confidence limits) come from tidy with exponentiate=True; pass format= to choose the backend (here, Polars):

# Extract hazard ratios as a tidy Polars DataFrame
gw.tidy(cox, exponentiate=True, format="polars")
shape: (2, 7)
termestimatestd_errorstatisticp_valueconf_lowconf_high
strf64f64f64f64f64f64
"age"1.0171910.0092231.8480780.0645910.9989691.035747
"sex"0.5985660.167458-3.064760.0021780.4310940.831099

Methods

Name Description
baseline_hazard() Return the baseline cumulative hazard and survival as a frame, optionally with CIs.
concordance() Harrell’s concordance index (C-statistic) of the fitted risk scores.
cox_zph() Test the proportional-hazards assumption (Grambsch-Therneau).
fit() Fit the model to a Surv response and a covariate design.
frailty_test() Likelihood-ratio test for shared-frailty variance equal to zero.
predict() Predict from the fitted model.
residuals() Return diagnostic residuals from the fitted Cox model.
to_frame() Return a tidy coefficient table as a DataFrame (one row per term).

baseline_hazard()

Return the baseline cumulative hazard and survival as a frame, optionally with CIs.

Usage

Source

baseline_hazard(*, ci=False, conf_type="log-log", format=None)

The baseline hazard represents the hazard rate for a reference subject with all covariates at their mean values. It is useful for understanding the underlying time-to-event distribution estimated by the model, and can be combined with individual covariate values to compute predicted survival probabilities for specific subjects.

In Cox proportional hazards models, the hazard for an individual is modeled as: h(t \mid x) = h_0(t) \exp(x^\top \beta), where h_0(t) is the baseline hazard. This method returns the estimated cumulative baseline hazard H_0(t) at each observed event time, evaluated using the Breslow estimator (non-parametric).

Parameters
ci: bool = False

If True, include confidence interval columns for cumulative hazard and survival. Default is False.

conf_type: str = "log-log"

Confidence interval transform (used only if ci=True):

  • "log-log" (default): Log-log transform. Recommended because bounds respect the constraint that survival S(t) \in (0, 1) and cumulative hazard H(t) > 0.
  • "plain": Wald bounds without transform. Simple but may produce invalid bounds (negative cumulative hazard or survival > 1).
format: str | None = None

Output format: None (default), "pandas", "polars", or "pyarrow".

  • None (default): Auto-detects and tries Polars first, falls back to Pandas, then PyArrow. Raises an error if no DataFrame library is installed.
  • "pandas": returns pandas.DataFrame.
  • "polars": returns polars.DataFrame.
  • "pyarrow": returns pyarrow.Table.
Returns
pandas.DataFrame, polars.DataFrame, or pyarrow.Table

A DataFrame with one row per event time containing:

  • time: Event times at which the baseline hazard is evaluated.
  • cumhaz: Cumulative baseline hazard H_0(t) at each time.
  • survival: Baseline survival probability S_0(t) = \exp(-H_0(t)).
  • cumhaz_lower, cumhaz_upper (if ci=True): Confidence bounds for cumulative hazard.
  • survival_lower, survival_upper (if ci=True): Confidence bounds for survival.
  • strata (if stratified): Stratum label, one baseline hazard per stratum.
Details

The baseline hazard is evaluated only at the event times in the training data. The cumulative hazard is non-decreasing by construction. For stratified models, each stratum has its own baseline hazard while coefficients are shared across strata, allowing different baseline risks for different groups.

The baseline survival S_0(t) is computed from the cumulative hazard using the relationship S_0(t) = \exp(-H_0(t)).

When ci=True, confidence intervals are computed using the log-log transform (the default), which ensures bounds remain valid (cumulative hazard > 0, survival ∈ (0,1)).

Examples

The baseline cumulative hazard (and the implied baseline survival) is reported at every event time. Pass format= to choose the backend (here, Polars):

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"]])

# Export the baseline cumulative hazard as a Polars DataFrame
cox.baseline_hazard(format="polars")
shape: (186, 3)
timecumhazsurvival
f64f64f64
5.00.0029550.997049
11.00.0119060.988164
12.00.0149280.985183
13.00.0210290.97919
15.00.0241080.97618
840.01.8576790.156034
883.02.0130580.13358
965.02.0130580.13358
1010.02.0130580.13358
1022.02.0130580.13358

Add confidence intervals with ci=True:

# Include confidence intervals for the baseline hazard
cox.baseline_hazard(ci=True, format="polars")
shape: (186, 7)
timecumhazsurvivalcumhaz_lowercumhaz_uppersurvival_lowersurvival_upper
f64f64f64f64f64f64f64
5.00.0029550.9970490.0002890.0302550.9701980.999711
11.00.0119060.9881640.0024460.0579590.9436890.997557
12.00.0149280.9851830.0032570.0684180.933870.996748
13.00.0210290.979190.004940.0895260.9143650.995072
15.00.0241080.976180.0057970.1002570.9046050.99422
840.01.8576790.1560340.5272016.5458380.0014360.590255
883.02.0130580.133580.5675597.1400630.0007930.566908
965.02.0130580.133580.5675597.1400630.0007930.566908
1010.02.0130580.133580.5675597.1400630.0007930.566908
1022.02.0130580.133580.5675597.1400630.0007930.566908

The returned DataFrame shows the estimated hazard and survival trajectory for the reference population (covariates at their means). For stratified models, a separate baseline is provided for each stratum:

# Fit a stratified model and get per-stratum baselines
cox_stratified = gw.CoxPH().fit(y, lung[["age", "ph.ecog"]], strata=lung["sex"])
cox_stratified.baseline_hazard(ci=True, format="polars")
shape: (205, 8)
timecumhazsurvivalcumhaz_lowercumhaz_uppersurvival_lowersurvival_upperstrata
f64f64f64f64f64f64f64i64
11.00.0068050.9932180.0020140.0229950.9772670.9979881
12.00.0091120.9909290.0030610.0271220.9732420.9969431
13.00.013810.9862850.0052990.0359920.9646480.9947151
15.00.0161960.9839350.0064620.0405940.9602190.9935591
26.00.0185930.9815790.0076410.0452440.9557640.9923881
735.00.6733030.5100210.1792052.5297120.0796820.8359352
740.00.6733030.5100210.1792052.5297120.0796820.8359352
765.00.8007080.4490110.2491572.5732050.0762910.7794572
821.00.8007080.4490110.2491572.964960.0515630.8055452
965.00.8007080.4490110.2491573.470890.0310890.8313392

The baseline hazard can be combined with individual predictions to compute personalized survival curves (see predict(type="survival")).


concordance()

Harrell’s concordance index (C-statistic) of the fitted risk scores.

Usage

Source

concordance()

The concordance index measures how well the model’s predicted risk scores order subjects by their survival times. It ranges from 0 to 1, where 0.5 indicates predictions are no better than random (coin flip), and 1.0 indicates perfect discrimination (the model always assigns higher risk to subjects who die first).

Pairs of subjects are compared: a subject who experiences an event at time t is considered to have “failed before” another subject still under observation at t (including one censored exactly at t). If the model assigns higher risk to the subject who failed first, the pair is concordant. Ties in predicted risk are treated as half-concordant.

For stratified models, only within-stratum pairs are compared.

Returns
float

The concordance index, a value between 0 and 1. Typical interpretation:

  • 0.5: Random predictions.
  • 0.6-0.7: Acceptable discrimination.
  • 0.7-0.8: Excellent discrimination.
  • 0.8+: Outstanding discrimination.
Details

The concordance index is equivalent to the Area Under the Receiver Operating Characteristic curve (AUC) for binary classification problems. It is computed as the fraction of concordant pairs out of all comparable pairs.

Comparable pairs are those where:

  • One subject has an event (event=True) and exits at time t.
  • The other subject exits at time > t, OR exits at time = t with event=False (censored).

Tied event times within the same outcome (both events or both censored at the same time) are excluded from comparison.

Examples

Harrell’s C is returned as a single number between 0 and 1. A value of 0.5 means the model is not better than random guessing. A value of 1.0 means perfect discrimination:

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"]])
cox.concordance()
0.6028530028979714

cox_zph()

Test the proportional-hazards assumption (Grambsch-Therneau).

Usage

Source

cox_zph(*, transform="identity")

The Cox model assumes that the hazard ratio between any two subjects is constant over time (proportional hazards). If this assumption is violated (for example, if a treatment effect diminishes over time) the Cox estimates may be biased. This test checks for violations by regressing scaled Schoenfeld residuals on time.

Large test statistics or small p-values (typically p < 0.05) suggest the proportional-hazards assumption is violated for that covariate. When violated, consider stratified analysis (separate baseline hazards per stratum), time-dependent covariates, or time-varying coefficients.

Parameters
transform: str = "identity"

Transformation to apply to time when computing the test. Options are:

  • "identity" (default): Use time as-is. Regression on raw time.
  • "log": Use log(time). Regression on log-transformed time.
Returns
ZPHResult
An object containing per-term test results (per_term dict) and a global test (global_test dict) across all covariates. Each includes chi-squared statistic, degrees of freedom, and p-value. Access results via .to_frame() or dictionary keys.
Details

The test uses scaled Schoenfeld residuals, which under the null hypothesis (proportional hazards) have a known asymptotic distribution. The test statistic is approximately chi-squared with 1 df for each term, and chi-squared with degrees of freedom equal to the number of terms for the global test.

Schoenfeld residuals are weighted by the variance-covariance matrix of the risk set at each event time. The regression accounts for the constraint that Schoenfeld residuals sum to zero.

Examples

The test returns a ZPHResult summarizing per-term and global p-values:

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"]])

# Test the proportional-hazards assumption
zph = cox.cox_zph()
zph
ZPHResult(transform='identity', age: p=0.7065, sex: p=0.0992, GLOBAL p=0.2425)

The full statistics are available as a tidy frame, one row per term plus a GLOBAL row. Pass format= to choose the backend (here, Polars):

# Export the test statistics as a Polars DataFrame
zph.to_frame(format="polars")
shape: (3, 4)
termchisqdfp_value
strf64i64f64
"age"0.14174810.70655
"sex"2.71840110.099197
"GLOBAL"2.83339120.242514

fit()

Fit the model to a Surv response and a covariate design.

Usage

Source

fit(
    surv,
    covariates,
    *,
    data=None,
    strata=None,
    robust=False,
    cluster=None,
    frailty=None,
    frailty_cluster=None,
    frailty_theta=0.5,
    frailty_max_iter=30,
    max_iter=30,
    tol=1e-09
)

covariates is a dataframe or 2-D array, or a right-hand-side formula string (for example "age + sex + C(ph.ecog)") evaluated against data. strata gives per-stratum baseline hazards with shared coefficients. robust=True (or providing cluster ids) reports the Lin-Wei sandwich variance. The cluster option sums the score residuals within groups before forming the sandwich.

Parameters
surv: Surv

A Surv object representing the response (censoring type must be right-censored or counting-process).

covariates: Any

Covariate design, either a 2-D array, dataframe, or formula string.

data: Any = None

DataFrame to evaluate formula strings against (required if covariates= is a formula string).

strata: Any = None

Optional stratification variable, giving each stratum its own baseline hazard while sharing coefficients. Can be a 1-D array or series.

robust: bool = False

If True, report Lin-Wei sandwich variance (robust standard errors). Default is False.

cluster: Any = None

Optional cluster labels for grouped robust variance estimation. Sums score residuals within groups before forming the sandwich.

frailty: str | None = None

Optional shared frailty distribution. Supports "gamma" (multiplicative gamma random effect, closed-form EM) and "lognormal" (additive normal random effect on the log-hazard, fitted via penalized partial likelihood with Laplace approximation, analogous to coxme in R). Both require frailty_cluster= and ties="breslow".

frailty_cluster: Any = None

Cluster labels for shared frailty random effects (one frailty per cluster).

frailty_theta: float = 0.5

Initial value for the frailty variance parameter (must be > 0). For gamma, this is the variance of the multiplicative frailty. For log-normal, this is the initial sigma^2 (variance of the log-hazard random effect).

frailty_max_iter: int = 30

Maximum number of outer iterations for shared frailty fitting (EM steps for gamma, REML loops for log-normal).

max_iter: int = 30

Maximum number of iterations for the Newton-Raphson solver. The default is 30.

tol: float = 1e-09
Convergence tolerance for the optimization. The default is 1e-9.
Returns
CoxPH
Returns self with fitted attributes including coef_, std_error_, hazard_ratio_, z_, p_value_, and other model diagnostics.
Examples

Passing strata= gives each stratum its own baseline hazard while sharing the coefficients. Here we fit age and ph.ecog stratified by sex:

import greenwood as gw

# Load data and build a right-censored response
lung = gw.load_dataset("lung", backend="polars")
y = gw.Surv.right(lung["time"], event=(lung["status"] == 2))

# Fit a stratified Cox model and export the coefficients
gw.CoxPH().fit(y, lung[["age", "ph.ecog"]], strata=lung["sex"]).to_frame(
    format="polars"
)
shape: (2, 7)
termestimatestd_errorstatisticp_valueconf_lowconf_high
strf64f64f64f64f64f64
"age"0.0105660.0092411.1433640.252887-0.0075470.028679
"ph.ecog"0.4624240.1147614.0294530.0000560.2374970.687352

The covariates argument also accepts a right-hand-side formula string (for example "age + sex + C(ph.ecog)"), and robust=True reports the Lin-Wei sandwich variance.


frailty_test()

Likelihood-ratio test for shared-frailty variance equal to zero.

Usage

Source

frailty_test()

Tests whether the frailty (random-effect) variance is significantly different from zero. A significant result indicates that there is unexplained heterogeneity across clusters (e.g., centres, families) beyond what the fixed-effect covariates capture.

For gamma frailty the LRT uses the profile marginal likelihood (integrating out the random effects analytically). For log-normal frailty it uses the Laplace approximation to the marginal likelihood compared against the regular Cox partial likelihood.

The model must have been fitted with frailty="gamma" or frailty="lognormal" for this test to be available.

Returns
dict
A dictionary with keys "theta" (estimated frailty variance), "lr_statistic" (likelihood-ratio test statistic), "df" (degrees of freedom, always 1), and "p_value".
Raises
ValueError
If the model was not fitted with a frailty term.
Examples

Fit a Cox model with a gamma frailty term grouped by institution, then test whether the frailty variance is significant:

import greenwood as gw

# Load data and build a right-censored response
lung = gw.load_dataset("lung", backend="polars")
y = gw.Surv.right(lung["time"], event=(lung["status"] == 2))

# Fit a Cox model with shared gamma frailty by institution
cox = gw.CoxPH(ties="breslow").fit(
    y, covariates=lung[["age", "sex"]],
    frailty="gamma", frailty_cluster=lung["inst"],
)

# Test whether the frailty variance is significant
cox.frailty_test()
{'theta': 1.3779723598335543e-08,
 'lr_statistic': 0.00043770764023065567,
 'df': 1.0,
 'p_value': 0.49165415243657906}

predict()

Predict from the fitted model.

Usage

Source

predict(
    newdata=None,
    *,
    type="lp",
    times=None,
    strata=None,
    conditional_after=None,
    ci=False,
    trajectory=None,
    format=None
)

type is one of "lp" (centered linear predictor), "risk" (exp(lp)), or "survival". For "survival", returns a frame of survival probabilities at times (defaulting to the union of all event times), one column per row of newdata.

For stratified models, each subject must be assigned to a stratum. When newdata is provided, pass strata= with one label per row matching a stratum seen at fit time. When newdata is None, stratum assignments are taken from the fitted data.

conditional_after (a scalar or one value per subject) predicts survival conditional on having already survived to that time: the returned value at time t is P(T > t \mid T > c) = S(t) / S(c), and is 1 for t \le c.

With ci=True (survival only), the frame also carries _lower and _upper columns per subject: a pointwise confidence band from the cumulative-hazard standard error (the log transform used by R’s survfit), at the model’s conf_level.

trajectory predicts survival for a single subject whose covariates change over time. It is a DataFrame (any backend) with columns tstart, tstop, and one column per covariate in the fitted model. Each row defines a constant-covariate interval; the covariate values in interval k apply to (t_k^{\text{start}}, t_k^{\text{stop}}]. The survival at time t is accumulated as

S(t) = \exp\!\Bigl(-\sum_k r_k \cdot \bigl[H_0(\min(t,\, t_k^{\text{stop}})) - H_0(t_k^{\text{start}})\bigr]\Bigr)

where r_k = \exp(\hat{\beta}^\top x_k) and the sum runs over all intervals where t_k^{\text{start}} < t. For times beyond the last interval the last covariate value is carried forward (the baseline hazard keeps accumulating). Mutually exclusive with newdata; requires type="survival".

Parameters
newdata: Any = None

Covariate design for prediction. If None, predictions are made on the fitted data. Can be a 2-D array or dataframe. Mutually exclusive with trajectory.

type: str = "lp"

Type of prediction: "lp" (centered linear predictor, default), "risk" (exp of linear predictor), or "survival" (survival probability).

times: Any = None

Time points at which to compute survival probabilities (for type="survival"). Defaults to the union of all event times from the fitted model.

strata: Any = None

Stratum labels for new subjects (required when newdata= is provided and the model was fitted with strata=). Must have one label per row of newdata=, matching a stratum label seen at fit time. For trajectory=, a single stratum label for the subject. Ignored for non-stratified models.

conditional_after: Any = None

Optional scalar or per-subject time for conditional survival prediction. Computes P(T > t \mid T > c) where c is the conditional_after time.

ci: bool = False

If True (survival only), include confidence intervals (_lower and _upper columns). The default is False. Not supported with trajectory.

trajectory: Any = None

DataFrame with columns tstart, tstop, and covariates, describing one subject’s time-varying covariate path. See the description above. Mutually exclusive with newdata; requires type="survival".

format: str | None = None

Output format (for type="survival" only): None (default), "pandas", "polars", or "pyarrow".

  • None (default): Auto-detects and tries Polars first, falls back to Pandas, then Pyarrow. Raises an error if no DataFrame library is installed.
  • "pandas": returns pandas.DataFrame.
  • "polars": returns polars.DataFrame.
  • "pyarrow": returns pyarrow.Table.
Returns
ndarray or DataFrame
For type="lp" or "risk", returns a 1-D array with one prediction per row. For type="survival", returns a DataFrame with rows for each time point and columns for each subject (named subject_1, subject_2, etc.), optionally with _lower and _upper columns for confidence intervals. With trajectory, returns a DataFrame with a single subject_1 column.
Examples

The default type="lp" returns the centered linear predictor as a NumPy array, one value per fitted subject:

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"]])

# Predict the centered linear predictor for the first five subjects
cox.predict(type="lp")[:5]
array([0.3995047 , 0.2972327 , 0.09268872, 0.10973405, 0.16087005])

With type="survival" and newdata, the result is a frame of survival probabilities at the requested times, one column per new subject. Pass format= to choose the backend (here, Polars):

# Predict survival probabilities for three subjects at 180 and 365 days
cox.predict(
    lung[["age", "sex"]][:3], type="survival", times=[180, 365], format="polars"
)
shape: (2, 4)
timesubject_1subject_2subject_3
f64f64f64f64
180.00.622340.6517050.705421
365.00.2687570.3053760.380304

Passing ci=True adds pointwise confidence bands, and conditional_after= gives survival conditional on having already survived to a landmark time.

With trajectory, survival is computed along a covariate path for a single subject:

import pandas as pd

pbcseq = gw.load_dataset("pbcseq", backend="pandas")
base = (pbcseq.drop_duplicates("id")[["id", "futime", "status"]]
              .rename(columns={"futime": "time"}))
long = gw.split_episodes(
    base, pbcseq[["id", "day", "bili", "albumin", "protime"]],
    id="id", time="time", event="status", visit_time="day", format="pandas",
)
long = long.dropna(subset=["bili", "albumin", "protime"])
long["event_bin"] = (long["status"] == 2).astype(int)

y = gw.Surv.counting(long["tstart"], long["tstop"], long["event_bin"])
cox = gw.CoxPH().fit(y, long[["bili", "albumin", "protime"]])

# Subject 1's covariate path (two visits)
tvc_path = pd.DataFrame({
    "tstart":  [0.0, 192.0],
    "tstop":   [192.0, 400.0],
    "bili":    [14.5, 21.3],
    "albumin": [2.60, 2.94],
    "protime": [12.2, 11.2],
})
cox.predict(trajectory=tvc_path, times=[100, 200, 300, 400], format="pandas")
/var/folders/s8/bj_jsx3d7jqd2btw7bwm6yx80000gp/T/ipykernel_11078/1120825321.py:14: UserWarning: Subjects in counting-process data have different start times, some much larger than 0. This may indicate that start/stop times are calendar time rather than subject-relative time. Each subject's timeline should begin at 0. If you have calendar dates, subtract each subject's entry date from their start/stop times before fitting.
  cox = gw.CoxPH().fit(y, long[["bili", "albumin", "protime"]])
time subject_1
0 100.0 0.851568
1 200.0 0.654596
2 300.0 0.562411
3 400.0 0.395678

residuals()

Return diagnostic residuals from the fitted Cox model.

Usage

Source

residuals(type="martingale", *, format=None)
Parameters
type: str = "martingale"

Type of residuals to return:

  • "martingale" (default): One per observation, range (-\infty, 1]. Positive values indicate underestimated risk.
  • "deviance": Normalized martingale residuals, more symmetrically distributed. Useful for detecting outliers.
  • "score": Efficient score residuals, one row per observation and one column per covariate. Used to construct the robust (sandwich) variance estimator.
  • "schoenfeld": One row per event, one column per covariate. Useful for checking the proportional-hazards assumption.
  • "scaledsch": Scaled Schoenfeld residuals (Grambsch-Therneau), one row per event. Under the PH assumption, the expected value at each event time equals the true coefficient.
  • "dfbeta": Approximate change in each coefficient if observation i were deleted. One row per observation, one column per covariate.
  • "dfbetas": Standardized dfbeta (dfbeta divided by the coefficient SE). Comparable across covariates on different scales.
format: str | None = None
Output format for multi-column residual types: None (auto-detect), "pandas", "polars", or "pyarrow". Returns a numpy array for "martingale" and "deviance".
Returns
ndarray or DataFrame
For "martingale" and "deviance": a 1-D array (one value per observation). For all other types: a DataFrame with one column per covariate.
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"]])

cox.residuals("martingale")[:5]
array([ 0.00438999, -0.50576203, -3.12981924,  0.53275397, -2.35065745])
cox.residuals("dfbeta", format="polars")
shape: (228, 2)
agesex
f64f64
-0.000007-0.001313
-0.0001380.004039
0.0019160.028972
-0.00032-0.005218
0.0005010.024671
-0.0006180.003966
0.0006140.003063
-0.000138-0.002783
-0.0000710.002948
0.000082-0.003918

to_frame()

Return a tidy coefficient table as a DataFrame (one row per term).

Usage

Source

to_frame(*, format=None, exponentiate=False)

The table contains coefficient estimates, standard errors, test statistics, p-values, and confidence limits. If exponentiate=True, returns hazard ratios instead of log-hazards.

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).

exponentiate: bool = False
If True, return hazard ratios (exp of coefficients). Default is False.
Returns
pandas.DataFrame, polars.DataFrame, or pyarrow.Table
One row per term with columns: term, estimate, std_error, statistic, p_value, conf_low, conf_high.
Raises
ImportError
If the requested (or, when auto-detecting, any) DataFrame library is not installed.
Examples

Fit a Cox model and export the coefficient table as a Polars frame:

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"]])

# Export the coefficient table as a Polars DataFrame
cox.to_frame(format="polars")
shape: (2, 7)
termestimatestd_errorstatisticp_valueconf_lowconf_high
strf64f64f64f64f64f64
"age"0.0170450.0092231.8480780.064591-0.0010320.035123
"sex"-0.5132190.167458-3.064760.002178-0.84143-0.185007

With exponentiate=True, estimates become hazard ratios:

# Export hazard ratios instead of log-hazard coefficients
cox.to_frame(format="polars", exponentiate=True)
shape: (2, 7)
termestimatestd_errorstatisticp_valueconf_lowconf_high
strf64f64f64f64f64f64
"age"1.0171910.0092231.8480780.0645910.9989691.035747
"sex"0.5985660.167458-3.064760.0021780.4310940.831099