import greenwood as gw
gw.Surv(time=[5, 6, 4, 9], event=[1, 0, 1, 0])Surv(type=right, n=4, events=2)
A survival response, built exactly as R’s survival::Surv() builds one.
Usage
Surv() records a time-to-event outcome in one of R’s forms. The type is inferred from the arguments, as in R:
Surv(time, event) is right-censored. event= may be logical, 0/1, or 1/2 (as in the lung data, where 2 means died).Surv(start, stop, event) is a counting-process response, for late entry (left truncation) and time-varying covariates.event= (a pandas or Polars categorical, or an Arrow dictionary) gives a multi-state response, whose first level means censored.type="left", type="interval", and type="interval2" must be asked for.Status values R doesn’t accept become missing, with a warning. Missing values are kept, and rows with any missing value are dropped when a model is fitted. For exact, right-, left-, and interval-censored data, event_time() is often clearer, and as_surv() converts it.
Surv() takes values: lists, arrays, or series already in hand. To start from a data frame, name the columns instead, in a formula such as "Surv(time, status == 2) ~ age" or with Outcome.surv(time="time", event="status == 2"), and pass data= when fitting. That form is evaluated on the frame’s own backend, so it works the same on pandas, Polars, PyArrow, DuckDB, and lazy frames.
time: AnyFollow-up time for right-censored data, or the start time for counting-process data, or the lower bound for interval data. Numeric, with None or nan for missing values.
time2: Any = NoneStop time for counting-process data, or the upper bound for interval data. When event= is not given and type= is not an interval type, a second argument is the status (R’s rule), so Surv(time, status) works positionally.
event: Any = NoneThe status. Logical, 0/1, or 1/2 for right, left, and counting data. Codes 0 (right), 1 (exact), 2 (left), and 3 (interval) for type="interval". A categorical for a multi-state response.
type: str | None = NoneOne of "right", "left", "interval", "counting", "interval2", or "mstate". The default infers "right" or "counting" from the number of arguments.
origin: float = 0.0 TypeErrorIf a time or the status isn’t of a type R accepts. The messages match R’s.
ValueError UserWarningFour subjects: events at times 5 and 4, censored at 6 and 9:
Surv(type=right, n=4, events=2)
1/2 status coding works as it does in R, where 2 means an event:
A start time gives a counting-process response:
A categorical status gives a multi-state response, with the first level meaning censored:
import pandas as pd
outcome = pd.Categorical(
["death", "censor", "relapse", "death"], categories=["censor", "relapse", "death"]
)
gw.Surv(time=[5, 6, 7, 8], event=outcome)Surv(type=mright, n=4, events=3, states=('relapse', 'death'))
Interval data uses R’s coding, or lower and upper bounds with type="interval2":
import numpy as np
# The event happened in (1, 2], after 2, and in (3, 5]
gw.Surv(time=[1, 2, 3], time2=[2, np.inf, 5], type="interval2")Surv(type=interval, n=3, events=2)
Starting from a data frame, name the columns and let the model read them. We’ll use the bundled pbc dataset, from a Mayo Clinic trial in primary biliary cholangitis, where status is 0 (censored), 1 (transplant), or 2 (died):
pbc = gw.load_dataset("pbc")
# The same as Surv(time, status == 2) in R, evaluated on the frame's own backend
gw.CoxPH().fit("Surv(time, status == 2) ~ age + bili", data=pbc)CoxPH (Cox proportional hazards model, ties='efron')
coef exp(coef) se(coef) z p
age 0.04313 1.044 0.008055 5.354 8.594e-08
bili 0.1436 1.154 0.01142 12.578 2.8e-36
n = 418, events = 161
Likelihood ratio test = 126.1 on 2 df, p = 4.097e-28
| Name | Description |
|---|---|
| entry | The time each row enters the risk set, defined for every type. |
| event | Whether each row ended in an event, as a boolean array. |
| is_multistate | Whether the response has more than one kind of event. |
| is_truncated | Whether the response has start times (late entry or time-varying data). |
| lower |
The first time column of interval data (R’s time1), otherwise None.
|
| n | The number of rows, including any with missing values. |
| n_censored |
The number of censored rows (status 0).
|
| n_events | The number of rows that ended in an event. |
| start | The time each row starts being at risk, for counting-process data. |
| states |
The names of the event states of a multi-state response, otherwise None.
|
| status | The status column, one code per row, stored as floats as R stores it. |
| stop | The main time column, one value per row. |
| type | The kind of response, using R’s type names. |
The time each row enters the risk set, defined for every type.
entry: FloatArray
For counting-process data this is the start time of each interval, the same as start. For every other type, subjects are at risk from the beginning, so every value is minus infinity (-inf). Estimators use entry to build risk sets without having to check whether the response has start times.
Whether each row ended in an event, as a boolean array.
event: npt.NDArray[np.bool_]
A row counts as an event when its status is 1 or more. For multi-state data that means any state, not a particular one. For interval data it means the event is known to have happened (exactly, before a time, or within an interval), so only right-censored rows are False. Rows with a missing status are False.
Whether the response has more than one kind of event.
is_multistate: bool
True for multi-state and competing-risks responses (types MRIGHT and MCOUNTING), which come from a categorical event= or from first_event(). Their state names are in states. Estimators for a single event type, such as KaplanMeier and CoxPH, reject these responses. Competing-risks estimators such as AalenJohansen require them.
Whether the response has start times (late entry or time-varying data).
is_truncated: bool
True for counting-process responses, built as Surv(time=start, time2=stop, event=...), including multi-state ones. Estimators that can’t handle delayed entry check this and raise an error rather than silently ignoring the start times.
The first time column of interval data (R’s time1), otherwise None.
lower: FloatArray | None
For interval data this is the time that goes with each row’s status: the censoring time for right-censored rows, the event time for exact rows, the time by which the event had happened for left-censored rows, and the lower bound for interval-censored rows (whose upper bound is in stop). For every other type it is None.
The number of rows, including any with missing values.
n: int
Each row is one observation: one subject for most data, or one interval of a subject’s follow-up for counting-process data. Rows with missing values are counted here but dropped when a model is fitted, so a fit may use fewer rows. len(y) gives the same number.
The number of censored rows (status 0).
n_censored: int
For right, counting, and multi-state data these are the rows that were event-free when last seen. For left data, status 0 means left-censored, and for interval data it means right-censored. Rows with a missing status are not counted, so n_events + n_censored is less than n when some statuses are missing.
The number of rows that ended in an event.
n_events: int
This counts the True values of event: rows with status 1 or more, in any state for multi-state data. Rows with a missing status are not counted.
The time each row starts being at risk, for counting-process data.
start: FloatArray | None
A counting-process response, Surv(time=start, time2=stop, event=...), describes each row as an interval (start, stop]. This is how late entry (left truncation) and time-varying covariates are expressed. A row whose start is not before its stop has its start set to nan, with a warning, as in R. For every other type there are no start times and this is None. Use entry for a value that is defined for every type.
The names of the event states of a multi-state response, otherwise None.
states: tuple[str, …] | None
A multi-state response comes from a categorical event= (or first_event()). Its first category means censored, and the remaining categories, in order, are the states. A row with status k reached states[k - 1]. For single-event responses this is None.
The status column, one code per row, stored as floats as R stores it.
status: FloatArray
The codes depend on the type:
1 for an event, 0 for censored. Logical status and R’s 1/2 coding are converted to these codes when the response is built.1 for an exact event, 0 for left-censored.0 right-censored, 1 exact, 2 left-censored, 3 interval-censored.0 for censored, k for the k-th entry of states.Codes that R doesn’t accept become missing (nan) when the response is built, with a warning, as do backwards intervals. Use int(y.status[i]) when a code is used as an index.
R’s 1/2 coding (as in the lung data) becomes 0/1:
The main time column, one value per row.
stop: FloatArray
What it holds depends on the type, following R’s Surv matrix:
3) hold the placeholder 1, and their time is in lower.Any origin offset given to Surv(origin=...) has already been subtracted. Missing times are nan.
The kind of response, using R’s type names.
type: CensoringType
One of CensoringType.RIGHT, LEFT, INTERVAL, COUNTING, MRIGHT (multi-state), or MCOUNTING (multi-state with start times). The type is chosen when the response is built: inferred from the arguments, as in R, or set with type=. A type="interval2" response reports INTERVAL, because R converts it to interval coding. Because CensoringType is a str enum, the value also compares equal to R’s type strings.
| Name | Description |
|---|---|
| __getitem__() | Select rows, returning a new response of the same type. |
| from_dict() | Rebuild a response from the output of to_dict(). |
| from_json() | Rebuild a response from the output of to_json(). |
| is_na() |
Return which rows have a missing value in any column, as R’s is.na() does.
|
| to_dict() | Return a JSON-ready mapping that fully describes the response. |
| to_frame() | Return the response as a table with the same columns as R’s Surv matrix. |
| to_json() | Serialize the response to a JSON string. |
Select rows, returning a new response of the same type.
Usage
Indexing works like indexing a NumPy array and always returns a Surv, even for a single row (as subsetting an R Surv with y[i] does). Every column is subset together, and the type and state names are kept. Negative integers count from the end, as usual in Python.
key: Any Surv IndexErrorIf an integer position is out of range.
TypeErrorkey is not an integer, a slice, an integer array, or a boolean mask.
Surv(type=right, n=2, events=1)
A boolean mask keeps the rows where it is True, here the events:
Rebuild a response from the output of to_dict().
Usage
The mapping is read as it was written, without applying Surv()’s rules again: the stored type, columns, and state names are used directly. The result compares equal to the response that produced the mapping.
data: Mapping[str, Any] Surv ValueErrorRebuild a response from the output of to_json().
Usage
The text is parsed with json.loads() and passed to from_dict(), so the same rules apply: the stored type, columns, and state names are used as they are, and the result compares equal to the response that was serialized.
text: str Surv ValueErrorReturn which rows have a missing value in any column, as R’s is.na() does.
Usage
A row is missing when any of its columns is nan: a missing time, a missing status, or (for counting data) a missing start time. Surv() keeps such rows rather than dropping them, as R does. They come from missing input values, and also from values R turns into missing when the response is built: an invalid status code, a start time that is not before its stop time, or an interval that runs backwards. Estimators drop these rows when fitting and report how many in n_dropped_.
numpy.ndarrayTrue where the row has a missing value.
The second subject’s time is missing:
array([False, True, False])
Select the complete rows with the inverted mask:
Return a JSON-ready mapping that fully describes the response.
Usage
The mapping holds only plain Python values (strings, floats, lists, and None), so it can be written with json.dumps(), stored, or sent to another process, and turned back into an equal response with from_dict(). Missing values become None. The layout is versioned: version 2 stores R’s Surv columns.
dict"version" (the layout version), "type" (the response type, as one of R’s type names), "states" (the state names, or None), and "columns" (R’s column names mapped to lists of values).
Return the response as a table with the same columns as R’s Surv matrix.
Usage
Each row of the table is one row of the response. The columns depend on the type, and match the columns of the matrix R’s Surv() returns:
time and status.time1, time2, and status. As in R, time2 is 1 for rows that are not interval-censored.Status is stored as a float, as in R. Missing values are nulls. Use this table to inspect a response, to join it back onto other data, or to export it.
format: str | None = NoneNone (default), "pandas", "polars", or "pyarrow". When None, a backend is auto-detected (Polars, then Pandas, then PyArrow).
pandas.DataFrame, polars.DataFrame, or pyarrow.Table ImportErrorformat=None, any of them) is not installed.
import greenwood as gw
gw.Surv(time=[0, 2, 1], time2=[5, 6, 4], event=[1, 0, 1]).to_frame(format="polars")| start | stop | status |
|---|---|---|
| f64 | f64 | f64 |
| 0.0 | 5.0 | 1.0 |
| 2.0 | 6.0 | 0.0 |
| 1.0 | 4.0 | 1.0 |
Interval data uses R’s interval columns:
Serialize the response to a JSON string.
Usage
The string is the JSON encoding of to_dict(): the layout version, the type, the state names, and R’s columns, with missing values as null. Turn it back into a response with from_json().
indent: int | None = 2json.dumps(). The default 2 is readable, and None gives a compact single-line string.
str