PiecewiseExponential

Piecewise exponential survival model.

Usage

Source

PiecewiseExponential(
    *,
    breaks=None,
    knot_strategy="aic",
    max_knots=10,
    conf_level=0.95,
)

Assumes the hazard is constant within each time interval but can differ between intervals. Covariates enter multiplicatively (proportional hazards within each interval). The model is fit by maximum likelihood, equivalent to a Poisson GLM on the interval-expanded dataset with log(exposure) as offset.

This sits between the fully nonparametric Cox model and the fully parametric AFT: it estimates the baseline hazard but restricts it to a step function. Fewer intervals give a smoother hazard. More intervals approach the Cox model’s flexibility.

Parameters

breaks: list[float] | tuple[float, …] | None = None

Time points at which the hazard is allowed to change. For example, breaks=[180, 365] creates three intervals: (0, 180], (180, 365], and (365, inf]. When None (default), knots are chosen automatically by minimizing AIC.

knot_strategy: str = "aic"

Strategy for automatic knot selection when breaks is None. "aic" (default) minimizes the Akaike information criterion. "bic" minimizes the Bayesian information criterion. Ignored when breaks is provided.

max_knots: int = 10

Maximum number of interior knots to consider during automatic selection (the default is 10). Ignored when breaks is provided.

conf_level: float = 0.95
Confidence level for coefficient intervals (the default is 0.95).

Examples

import greenwood as gw

lung = gw.load_dataset("lung", backend="polars")
y = gw.Surv.right(lung["time"], event=(lung["status"] == 2))

pem = gw.PiecewiseExponential().fit(y, lung[["age", "sex"]])
pem
PiecewiseExponential (1 intervals)

        coef  se(coef)       z         p
age  0.01562  0.009106   1.715    0.0863
sex  -0.4809    0.1671  -2.878  0.003999

Intervals:
  (0, inf]

n = 228, events = 165
Log-likelihood = -281.9
AIC = 569.7

With manual break points:

pem_manual = gw.PiecewiseExponential(breaks=[180, 365]).fit(y, lung[["age", "sex"]])
pem_manual.to_frame(format="polars")
shape: (2, 7)
termestimatestd_errorstatisticp_valueconf_lowconf_high
strf64f64f64f64f64f64
"age"0.015790.0091751.7209940.085252-0.0021930.033773
"sex"-0.5077430.167172-3.0372460.002388-0.835394-0.180091

Methods

Name Description
baseline_hazard() Return the piecewise-constant baseline hazard as a DataFrame.
fit() Fit the piecewise exponential model.
predict() Predict survival or cumulative hazard for new subjects.
to_frame() Return the coefficient table as a DataFrame.

baseline_hazard()

Return the piecewise-constant baseline hazard as a DataFrame.

Usage

Source

baseline_hazard(
    *,
    format=None,
)

Each row gives the interval boundaries and the constant hazard rate within that interval.

Parameters

format: str | None = None
Output format: None, "pandas", "polars", or "pyarrow".

Returns

pandas.DataFrame, polars.DataFrame, or pyarrow.Table

Examples

import greenwood as gw

lung = gw.load_dataset("lung", backend="polars")
y = gw.Surv.right(lung["time"], event=(lung["status"] == 2))
pem = gw.PiecewiseExponential(breaks=[180, 365]).fit(y, lung[["age", "sex"]])
pem.baseline_hazard(format="polars")
shape: (3, 4)
startstophazardlog_hazard
f64f64f64f64
0.0180.00.001299-6.646077
180.0365.00.002215-6.112317
365.0inf0.002392-6.035744

fit()

Fit the piecewise exponential model.

Usage

Source

fit(
    surv,
    covariates,
    *,
    data=None,
    max_iter=50,
    tol=1e-09,
)

Parameters

surv: Surv

A right-censored or counting-process Surv response.

covariates: Any

A dataframe (pandas or polars), a 2-D array, or a formula string.

data: Any = None

A dataframe for formula evaluation (ignored otherwise).

max_iter: int = 50

Maximum IRLS iterations (default 50).

tol: float = 1e-09
Convergence tolerance on the step size (default 1e-9).

Returns

PiecewiseExponential
The fitted model (for method chaining).

Examples

import greenwood as gw

lung = gw.load_dataset("lung", backend="polars")
y = gw.Surv.right(lung["time"], event=(lung["status"] == 2))
pem = gw.PiecewiseExponential(breaks=[180, 365]).fit(y, lung[["age", "sex"]])
pem.to_frame(format="polars")
shape: (2, 7)
termestimatestd_errorstatisticp_valueconf_lowconf_high
strf64f64f64f64f64f64
"age"0.015790.0091751.7209940.085252-0.0021930.033773
"sex"-0.5077430.167172-3.0372460.002388-0.835394-0.180091

predict()

Predict survival or cumulative hazard for new subjects.

Usage

Source

predict(
    newdata=None,
    *,
    type="survival",
    times=None,
    format=None,
)

Parameters

newdata: Any = None

Covariate values for new subjects. If None, uses the training data.

type: str = "survival"

"survival" (default), "cumhaz" (cumulative hazard), "lp" (linear predictor), or "risk" (exp of linear predictor).

times: list[float] | Array | None = None

Times at which to evaluate survival or cumulative hazard. Required for "survival" and "cumhaz".

format: str | None = None
Output format for tabular results.

Returns

Array or DataFrame
For "lp" and "risk", a 1-D array. For "survival" and "cumhaz", a DataFrame with one column per subject and one row per time.

to_frame()

Return the coefficient table as a DataFrame.

Usage

Source

to_frame(
    *,
    format=None,
)

One row per covariate (excluding interval parameters). Includes coefficient estimates, standard errors, Wald statistics, p-values, and confidence limits.

Parameters

format: str | None = None
Output format: None, "pandas", "polars", or "pyarrow".

Returns

pandas.DataFrame, polars.DataFrame, or pyarrow.Table

Examples

import greenwood as gw

lung = gw.load_dataset("lung", backend="polars")
y = gw.Surv.right(lung["time"], event=(lung["status"] == 2))
pem = gw.PiecewiseExponential(breaks=[180, 365]).fit(y, lung[["age", "sex"]])
pem.to_frame(format="polars")
shape: (2, 7)
termestimatestd_errorstatisticp_valueconf_lowconf_high
strf64f64f64f64f64f64
"age"0.015790.0091751.7209940.085252-0.0021930.033773
"sex"-0.5077430.167172-3.0372460.002388-0.835394-0.180091