Quick start

This is a short tour that goes from raw data to a survival curve, a plot, and a fitted model. Each step is only a few lines. The rest of the guide explains every piece in depth, and links are collected at the end.

Represent the data

Survival data pairs a follow-up time with an indicator of whether the event was actually observed or the subject was censored (still event-free when we last saw them). Greenwood keeps the two together in a Surv object. We use the bundled lung dataset, the survival times of patients with advanced lung cancer.

import greenwood as gw

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

The summary reports 228 patients, of whom 165 died during the study; the rest were censored. One detail to note: this dataset codes status as 1 for censored and 2 for dead, so we turn it into a plain event indicator with status == 2.

Estimate the survival curve

The Kaplan-Meier estimator turns those times into a survival curve, the probability of surviving past each point in time. Printing the fitted estimator reports the median survival.

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

km = gw.KaplanMeier().fit(y)
km
KaplanMeier (Kaplan-Meier survival estimate)

    n  events  median  0.95LCL  0.95UCL
  228     165     310      285      363

The median is 310 days: half of the patients are still alive at 310 days. The two columns after it give a 95 percent confidence interval for that median, 285 to 363 days.

Plot the curve

plot_survival draws the curve as an interactive chart, with a shaded confidence band and a notch wherever a patient was censored.

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

km = gw.KaplanMeier().fit(y)

gw.plot_survival(km)

The curve starts at 1.0 and steps down at each death. It does not reach 0 because some patients were still alive at the end of follow-up, so their survival time is only known to be at least that long.

Model a covariate effect

To ask how a characteristic changes the risk of death, fit a Cox proportional hazards model. Here we include age and sex. Printing the model shows a coefficient table.

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

The exp(coef) column holds hazard ratios. For sex (coded 1 for male, 2 for female) the hazard ratio is about 0.60, which means women had roughly 40 percent lower risk of death than men over the study, holding age fixed. A ratio below 1 is lower risk, above 1 is higher risk.

Next steps

That is the core loop: represent the data, estimate a curve, visualize it, and model an effect. Each part of the guide takes one of these steps further.