split_episodes()

Convert repeated-measurement data into counting-process (episode-split) format.

Usage

Source

split_episodes(
    baseline,
    visits,
    *,
    id,
    time,
    event,
    visit_time,
    covariates=None,
    baseline_covariates=None,
    carry_forward=True,
    format=None,
)

Takes a subject-level baseline table and a long-format visits table, and merges them into the interval-per-row counting-process layout required by a counting-process response ("Surv(tstart, tstop, event)") and CoxPH. Each row in the output represents a constant-covariate interval (tstart, tstop] for one subject.

The covariate value measured at visit time v applies to the interval [v, next_v). The event indicator is 1 only on the final interval for subjects who experienced the event; all earlier intervals carry 0. A visit at the follow-up end does not create a new interval: the interval ending there retains the event and the covariate value measured before the endpoint.

Parameters

baseline: Any

The subject-level table. Must contain the id, time, and event columns. It may have one row per subject, or it may be the same long table as visits when that table repeats the follow-up time and event on every row. In that case the first row of each subject is used, after checking that time, event, and any baseline_covariates are constant within the subject.

visits: Any

One row per (subject, visit). Must contain the id and visit_time columns.

id: str

Column name linking baseline and visits.

time: str

Column in baseline giving the end of follow-up (right-censored exit time).

event: str

Column in baseline giving the event indicator.

visit_time: str

Column in visits giving the measurement time for each row.

covariates: Sequence[str] | None = None

The columns of visits to carry as time-varying covariates. The default (None) uses every column other than id and visit_time.

baseline_covariates: Sequence[str] | None = None

The columns of baseline to carry as time-fixed covariates. The default (None) uses every column other than id, time, and event when baseline has one row per subject, and none when it repeats subjects (a long table usually holds per-visit columns that are not fixed in time).

carry_forward: bool = True

If True (default), the last observed covariate value is carried forward to the end of follow-up (LOCF), producing a final interval (last_visit, time]. If False, follow-up is truncated at the last visit; the trailing interval is dropped and the subject is effectively administratively censored there.

format: str | None = None

Output format: None (default), "pandas", "polars", or "pyarrow".

  • None (default): auto-detect, trying Polars first, then Pandas, then PyArrow.
  • "pandas": return a pandas.DataFrame.
  • "polars": return a polars.DataFrame.
  • "pyarrow": return a pyarrow.Table.

Returns

pandas.DataFrame, polars.DataFrame, or pyarrow.Table
Counting-process dataset with columns (id, tstart, tstop, event, [time-fixed covariates], [time-varying covariates]). Ready to pass directly to CoxPH.fit() with a formula such as "Surv(tstart, tstop, event) ~ bili".

Raises

ValueError
If required columns are missing, if any visit time exceeds the subject’s follow-up end, or if the resulting dataset is empty.

Examples

Build a minimal TVC dataset and fit a Cox model:

import pandas as pd
import greenwood as gw

baseline = pd.DataFrame({
    "id":    [1, 2, 3],
    "time":  [10.0, 8.0, 12.0],
    "event": [1, 0, 1],
})
visits = pd.DataFrame({
    "id":       [1, 1, 2, 3, 3, 3],
    "day":      [0.0, 5.0, 0.0, 0.0, 4.0, 8.0],
    "bili":     [1.2, 2.4, 0.8, 3.1, 2.9, 4.0],
})

long = gw.split_episodes(
    baseline=baseline, visits=visits, id="id", time="time", event="event", visit_time="day"
)
cox = gw.CoxPH().fit("Surv(tstart, tstop, event) ~ bili", data=long)