import greenwood as gw
lung = gw.load_dataset("lung", backend="polars")
y = gw.Surv.right(lung["time"], event=(lung["status"] == 2))
ySurv(type=right, n=228, events=165)
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.
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))
ySurv(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.
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)
kmKaplanMeier (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_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.
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"]])
coxCoxPH (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.
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.