import greenwood as gw
import numpy as np
y = gw.Surv(
type=gw.CensoringType.RIGHT,
stop=np.array([5, 6, 4, 9]),
status=np.array([1, 0, 1, 0])
)
ySurv(type=right, n=4, events=2)
A validated time-to-event response for survival analysis.
Usage
Surv represents the outcome in survival models: a time at which each subject either experienced an event (observed) or was censored (did not experience the event during follow-up). Surv supports multiple censoring types:
Surv.right(time, event).Surv.left(time, event).start and exits at stop. Use Surv.counting(start, stop, event).[lower, upper). Use Surv.interval(lower, upper).Surv.multistate(time, event, states).Use the class methods (right, left, counting, interval, multistate) to construct Surv objects. They validate your input and set the censoring type appropriately. As such, direct instantiation is not recommended.
type: CensoringTypeThe CensoringType enum indicating the censoring mechanism.
stop: ArrayExit time (for interval censoring, the upper bound).
status: ArrayInteger event code per observation: 0 = censored, 1+ = event code (for multi-state, codes >= 1 index into states).
start: Array | NoneEntry time for the counting-process form (left truncation); None otherwise.
lower: Array | NoneLower bound for interval censoring; None otherwise.
states: tuple[str, …] | NoneEvent-state labels for multi-state/competing-risks endpoints; None for the single-event case.
weights: Array | NoneNone if no weights provided.
Here’s an example of direct instantiation of Surv:
import greenwood as gw
import numpy as np
y = gw.Surv(
type=gw.CensoringType.RIGHT,
stop=np.array([5, 6, 4, 9]),
status=np.array([1, 0, 1, 0])
)
ySurv(type=right, n=4, events=2)
While this is fine, the preferred approach is to use the class method constructors for each censoring type. They handle validation and conversion automatically.
Right-censored (the most common case): each subject has an exit time and an event indicator.
Counting-process form with left truncation (late entry):
Surv(type=counting, n=3, events=2, truncated)
Interval-censored (event known to occur in a time window):
Multi-state (competing risks, multiple mutually exclusive events):
| Name | Description |
|---|---|
| entry | Entry times for each observation (or \(-\infty\) if no left truncation). |
| event | Boolean event indicator: True if any event occurred, False if censored. |
| is_multistate | Whether the response has multiple competing event states. |
| is_truncated | Whether the response has left truncation (late entry). |
| n | Number of observations in the response. |
| n_censored | Count of censored observations. |
| n_events | Count of observations where an event occurred (any state in multi-state data). |
Entry times for each observation (or \(-\infty\) if no left truncation).
entry: Array
For counting-process data (late entry), this returns the start time when each subject became at risk. For standard right-censored data with no left truncation, all values are \(-\infty\), indicating subjects entered at the beginning of follow-up.
Right-censored data (no left truncation) has all \(-\infty\) entry times:
array([-inf, -inf, -inf])
Counting-process data shows each subject’s entry time:
import greenwood as gw
y_counting = gw.Surv.counting(start=[0, 2, 1], stop=[5, 6, 4], event=[1, 0, 1])
y_counting.entryarray([0., 2., 1.])
The entry property is primarily used internally by survival estimators to correctly compute risk sets. You rarely need it directly, but it’s available for custom analyses.
Boolean event indicator: True if any event occurred, False if censored.
event: Array
Converts the integer status codes to a simple boolean: 1 or more -> True (event), 0 -> False (censored). This is a convenient summary when you only care about event occurrence, not which specific state occurred in multi-state data.
array([ True, False, True, False])
The True/False values indicate which subjects experienced any event. This is useful for filtering, counting events, or checking data quality. For multi-state data, this collapses all states into a single “any event” indicator:
Whether the response has multiple competing event states.
is_multistate: bool
Multi-state responses track which of several competing outcomes occurred (e.g., “relapse” vs. “death”). When False, there is only one event type (censored or not). When True, the states property contains the outcome labels.
Right-censored data has a single outcome:
import greenwood as gw
y_right = gw.Surv.right(time=[5, 6, 4], event=[1, 0, 1])
y_right.is_multistateFalse
Multi-state data with competing risks:
y_multi = gw.Surv.multistate(
time=[5, 6, 7, 8],
event=[1, 2, 0, 1],
states=("relapse", "death")
)
y_multi.is_multistateTrue
This property is useful for determining how to interpret the event codes and what kind of survival estimation is needed.
Whether the response has left truncation (late entry).
is_truncated: bool
Left truncation occurs in counting-process data when subjects enter the risk set at different times (late entry). This is common in studies with age-based entry or complex follow-up patterns. When True, the entry() property contains the actual start times; when False, all subjects implicitly start at time 0.
Right-censored data has no left truncation:
import greenwood as gw
y_right = gw.Surv.right(time=[5, 6, 4], event=[1, 0, 1])
y_right.is_truncatedFalse
Counting-process data with late entry is truncated:
y_counting = gw.Surv.counting(start=[0, 2, 1], stop=[5, 6, 4], event=[1, 0, 1])
y_counting.is_truncatedTrue
This property is useful for understanding data structure and for conditional logic that handles truncated vs. non-truncated data differently.
Number of observations in the response.
n: int
Returns the total count of subjects/observations, regardless of event status. Equivalent to len(surv_object).
This is useful for loops, validation, or allocating arrays. Often used to determine sample size or for sanity checks on data shape.
Count of censored observations.
n_censored: int
Counts all observations where the event was not observed (status == 0). These are subjects whose true event time is unknown but exceeds their observation time.
Often used for descriptive summary: “We observed 2 events and 2 censored subjects out of 4 total.” Can validate data quality:
Higher censoring rates reduce the information available for estimation and may require larger sample sizes for stable inference.
Count of observations where an event occurred (any state in multi-state data).
n_events: int
Counts all observations with status >= 1. For multi-state responses, this counts all events regardless of which specific state occurred. For single-event data, this is the count of subjects who experienced the event.
This is useful for descriptive statistics, event rate calculations, or validating data: assert y.n_events + y.n_censored == y.n.
For multi-state data, this gives the total event count across all states:
| Name | Description |
|---|---|
| counting() | Counting-process response: track subjects entering and exiting the risk set at |
| from_dict() | Rebuild a response from to_dict output. |
| from_json() | Deserialize from to_json output. |
| interval() | Interval-censored response: event time is known to lie within a range. |
| left() | Left-censored response: event occurred before the observation time. |
| multistate() | Multi-state or competing-risks response: track which of multiple outcomes occurs. |
| right() | Right-censored response: the standard and most common form of survival data. |
| to_dict() | Return a JSON-ready mapping fully describing the response. |
| to_frame() | Return the response as a DataFrame (one row per observation). |
| to_json() | Serialize to a deterministic JSON string. |
Counting-process response: track subjects entering and exiting the risk set at
Usage
different times.
The counting-process form handles two important real-world complexities:
Late entry (left truncation): Not all subjects start being at risk at time 0. For example, a study might enroll subjects at different ages, or you might analyze a subset of follow-up time after some subjects are already older. The start time marks when each subject becomes eligible to experience the event.
Time-varying covariates: The counting-process form naturally accommodates covariates that change over time. Each row represents one interval of time for a subject, allowing you to track how covariate values change.
Each subject contributes one or more (start, stop] intervals. The subject is at risk only during their interval(s) and cannot experience the event before entering at start.
start: AnyEntry times (when each subject becomes at risk). Must be finite and non-negative. Represents when the subject enters the risk set. In standard studies, this is 0; in studies with late entry, it’s the age/time at enrollment.
stop: AnyExit times (when follow-up ends). Must be finite, non-negative, and strictly greater than the corresponding start. Represents when the subject leaves follow-up (event, censoring, or end of study).
event: Any = NoneEvent indicators (1 = event occurred, 0 = censored at stop time). If None, all subjects are treated as having experienced the event.
weights: Any = NoneNone (all weights = 1).
SurvHere we have 3 subjects with different entry times:
Surv(type=counting, n=3, events=2, truncated)
The display shows:
Only subjects 2 and 3 benefit from the late entry handling, but the counting-process form elegantly handles all cases uniformly. This representation is also essential for studies with time-varying covariates, where you create multiple rows per subject as their covariate values change.
Rebuild a response from to_dict output.
Usage
This is the inverse of to_dict(): it reconstructs an equivalent Surv object from a dictionary previously created by to_dict(). Useful for deserializing stored or transmitted data, or for round-tripping through storage formats.
data: dict[str, Any]type, stop, status, and optional keys for start, lower, states, weights.
SurvRebuild an equivalent response from its dictionary representation. Here we serialize a response and immediately deserialize it:
import greenwood as gw
y = gw.Surv.right(time=[5, 6, 4, 9], event=[1, 0, 1, 0])
reconstructed = gw.Surv.from_dict(y.to_dict())
print("Objects equal:", y.to_dict() == reconstructed.to_dict())Objects equal: True
The reconstructed object is equivalent to the original in every way.
Deserialize from to_json output.
Usage
This is the inverse of to_json(): it reconstructs a Surv object from a JSON string previously created by to_json(). Useful for loading data from stored files, API responses, or any other JSON source. The reconstructed object is guaranteed to be equivalent to the original.
Deserialize from JSON. Build a response, then round-trip through to_json() and back:
import greenwood as gw
y = gw.Surv.right(time=[5, 6, 4, 9], event=[1, 0, 1, 0])
json_text = y.to_json()
restored = gw.Surv.from_json(json_text)
print("Round-trip successful:", y.to_json() == restored.to_json())Round-trip successful: True
The restored object is an exact copy of the original Surv object.
Interval-censored response: event time is known to lie within a range.
Usage
Interval censoring occurs when you know the event happened sometime between two observation times, but not exactly when. Common in:
The interval-censored form captures this uncertainty. The event happened somewhere in the interval (lower, upper]. If lower == upper, it’s an exact (uncensored) event. Use infinity for upper to represent right-censoring, and 0 for lower to represent left-censoring.
lower: AnyInterval lower bounds (one per subject). Must be finite and non-negative. Event happened after this time (possibly at this time). Set to 0 to mark left-censored subjects (event happened before first observation).
upper: AnyInterval upper bounds (one per subject). Must be finite, non-negative, and >= lower. Event happened by this time. Set to numpy.inf to mark right-censored subjects (no event observed by end of study).
weights: Any = NoneNone (all weights = 1).
SurvHere we have 3 subjects with different levels of observation precision:
import greenwood as gw
import numpy as np
y = gw.Surv.interval(lower=[1, 2, 3], upper=[2, np.inf, 5])
ySurv(type=interval, n=3, events=2)
The display shows: - Subject 1: Exact event at time 2 (lower == upper) - Subject 2: Right-censored at time 2 (upper = infinity means event never observed) - Subject 3: Interval-censored between times 3 and 5 (event happened somewhere in that window)
Interval censoring gives you more information than right censoring alone. Rather than just knowing “no event by time X,” you may know “event was definitely before time Y but after time X,” which allows for more precise estimation when multiple observations bracket the event.
Event occurred before the observation time.
Event occurred after the observation time.
Track subjects entering and exiting at different times.
Left-censored response: event occurred before the observation time.
Usage
Left censoring occurs when all you know is that an event happened before you observed the subject. For example, an infection that must have occurred before a patient was tested, or a failure that was known to have happened sometime before equipment was inspected. The exact event time is unknown, but you know it was no later than the recorded time.
This is less common than right censoring, but important in scenarios where you cannot pinpoint when something happened, only that it already had.
time: AnyObservation times (the upper bound on when the event occurred). Must be finite and non-negative. Each value represents “the event happened by this time”.
event: Any = NoneEvent indicators:
time (left-censored)time (not censored)If None, all subjects are treated as having experienced the event.
weights: Any = NoneNone (all weights = 1).
SurvHere we have 3 subjects. Two experienced the event before the recorded time (event=1), and one was event-free at observation (event=0):
Surv(type=left, n=3, events=2)
The display shows the data structure:
< symbol indicates left-censored observations (event occurred before time)+ symbol indicates subjects who were still event-free at the observation time"left" is displayed at the topRight-censored response (event after the observation time).
Time intervals with late entry.
Event lies in a known interval.
Multi-state or competing-risks response: track which of multiple outcomes occurs.
Usage
Real-world studies often involve multiple competing outcomes. A patient in a cancer study might relapse, die from cancer, or die from other causes. Each subject can only experience one outcome, and once it happens, no other outcome is possible.
The multi-state framework elegantly handles this by:
This is essential for realistic survival modeling: accounting for competing risks often substantially changes the estimated risk curves compared to treating all non-events identically.
time: AnyEvent or censoring times (one per subject). Must be finite and non-negative. Represents when the subject experienced an outcome (or was censored).
event: AnyEvent codes indicating which state occurred:
Must be in range [0, len(states)].
states: tuple[str, …]Labels for the possible outcomes. Event codes index into this tuple. Example: states=(“relapse”, “death”) means:
Labels are arbitrary strings describing what the transition represents.
start: array - like = NoneOptional entry times (for late entry / left truncation). If provided, each subject is only at risk from start until time. Default is None (all subjects enter at time 0).
weights: array - like = NoneNone (all weights = 1).
SurvHere we have 4 subjects with 2 competing outcomes (relapse and death):
import greenwood as gw
y = gw.Surv.multistate(
time=[5, 6, 7, 8],
event=[1, 2, 0, 1],
states=("relapse", "death")
)
ySurv(type=right, n=4, events=3, states=('relapse', 'death'))
The display shows:
You can then estimate the probability of each outcome separately, capturing the full picture: not just “will something happen?” but “which specific outcome is most likely?” This avoids the bias of artificially grouping competing outcomes together.
Simple right-censored Surv response object (only one possible outcome).
Time intervals with late entry.
Event occurred before the observation time.
Right-censored response: the standard and most common form of survival data.
Usage
Right censoring is the default in survival analysis. It occurs when follow-up ends before the event happens (a subject is still event-free when we last observed them). This is the most common censoring mechanism in practice:
Right censoring is called “censoring from the right” because we know the event happened after the censoring time. We record that a subject was event-free at their last observation but don’t know how much longer they could have gone.
This simple form assumes all subjects enter follow-up at the same reference time (typically time 0). If subjects enter at different times or follow-up is complex, use counting() or interval() instead.
time: AnyExit times when follow-up ends (one per subject). Must be finite and non-negative. This is the time of either the event or censoring, whichever came first.
event: Any = NoneEvent indicators:
If None, all subjects are treated as having experienced the event (useful for testing or descriptive purposes).
weights: Any = NoneNone (all weights = 1).
SurvThe most common case: subjects have an exit time and an event indicator (1 if event occurred, 0 if censored):
Surv(type=right, n=4, events=2)
The display shows 4 observations with 2 events and 2 censored observations:
* depending on visualization)+ marker; still event-free at times 6 and 9)This is the default input format for nearly all survival analysis methods. Right-censored data is so ubiquitous that “survival data” often refers specifically to right-censored observations.
Event occurred before the observation time.
Event time is known to lie in a range.
Late entry and time-varying covariates.
Track multiple competing outcomes.
Return a JSON-ready mapping fully describing the response.
Usage
This method serializes the entire Surv object into a plain Python dictionary, making it suitable for JSON serialization, storage, or transmission. All array data is converted to plain Python lists. The dictionary captures the censoring type and every array (time, status, optional fields), with None for fields that a given censoring flavor does not use.
dict[str, Any]type (CensoringType as string), stop, status, and optional keys start, lower, states, weights (as lists or None).
The mapping structure varies by censoring type, but always includes type, stop, and status. Unused fields are None. Here we build a response and convert it to a dictionary:
{'type': 'right',
'stop': [5.0, 6.0, 4.0, 9.0],
'status': [1, 0, 1, 0],
'start': None,
'lower': None,
'states': None,
'weights': None}
This is the serialized form that underpins to_json() and enables round-tripping via from_dict().
Return the response as a DataFrame (one row per observation).
Usage
Exports the Surv object to a tidy table where each row represents one observation. The table includes the stop and status columns, plus optional columns for start (entry time in counting-process form), lower (lower bound for interval censoring), and weight (case weights). This is convenient for inspection, export to CSV, or integration with other DataFrame workflows.
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.Tablestop, status, and optional start, lower, weight columns.
ImportErrorBuild a right-censored response and export it as a Polars frame. Each row represents one observation with its event time and status:
import greenwood as gw
y = gw.Surv.right(time=[5, 6, 4, 9], event=[1, 0, 1, 0])
y.to_frame(format="polars")| stop | status |
|---|---|
| f64 | i64 |
| 5.0 | 1 |
| 6.0 | 0 |
| 4.0 | 1 |
| 9.0 | 0 |
Request a different backend with format=:
Serialize to a deterministic JSON string.
Usage
This method converts the entire Surv object to a compact, JSON-formatted string suitable for storage in files, databases, or transmission over APIs. By default, the output is human-readable with indentation; pass indent=None for a compact form. The serialization is deterministic: the same Surv object always produces the identical JSON string.
indent: int | None = 2None, produces compact JSON without whitespace. Default is 2 (human-readable).
strSerialize to JSON. By default, output is indented for readability. Here we build a response and show just the first 120 characters of compact JSON:
import greenwood as gw
y = gw.Surv.right(time=[5, 6, 4, 9], event=[1, 0, 1, 0])
json_compact = y.to_json(indent=None)
print(json_compact[:120]){"type": "right", "stop": [5.0, 6.0, 4.0, 9.0], "status": [1, 0, 1, 0], "start": null, "lower": null, "states": null, "w
The full JSON includes all data in a structured format that can be parsed back with from_json().