Piecewise exponential survival model.
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)| term | estimate | std_error | statistic | p_value | conf_low | conf_high |
|---|
| str | f64 | f64 | f64 | f64 | f64 | f64 |
| "age" | 0.01579 | 0.009175 | 1.720994 | 0.085252 | -0.002193 | 0.033773 |
| "sex" | -0.507743 | 0.167172 | -3.037246 | 0.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.
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)| start | stop | hazard | log_hazard |
|---|
| f64 | f64 | f64 | f64 |
| 0.0 | 180.0 | 0.001299 | -6.646077 |
| 180.0 | 365.0 | 0.002215 | -6.112317 |
| 365.0 | inf | 0.002392 | -6.035744 |
fit()
Fit the piecewise exponential model.
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)| term | estimate | std_error | statistic | p_value | conf_low | conf_high |
|---|
| str | f64 | f64 | f64 | f64 | f64 | f64 |
| "age" | 0.01579 | 0.009175 | 1.720994 | 0.085252 | -0.002193 | 0.033773 |
| "sex" | -0.507743 | 0.167172 | -3.037246 | 0.002388 | -0.835394 | -0.180091 |
predict()
Predict survival or cumulative hazard for new subjects.
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.
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)| term | estimate | std_error | statistic | p_value | conf_low | conf_high |
|---|
| str | f64 | f64 | f64 | f64 | f64 | f64 |
| "age" | 0.01579 | 0.009175 | 1.720994 | 0.085252 | -0.002193 | 0.033773 |
| "sex" | -0.507743 | 0.167172 | -3.037246 | 0.002388 | -0.835394 | -0.180091 |