Recipes

Executable patterns for common polarstate analyses.

Every example on this page is executed when the documentation builds. If an API change breaks a recipe, the documentation check fails with it.

import pandas as pd
import polars as pl
from polarstate import prepare_event_table, predict_aj_estimates

Choose the event structure

The workflow stays the same; only the outcome coding changes.

Use 0 for right-censoring and 1 for the event.

single_event_data = pl.DataFrame({
    "times": [2.0, 4.0, 5.0, 8.0, 10.0],
    "reals": [1, 0, 1, 0, 1],
})
single_table = prepare_event_table(single_event_data)
single_result = predict_aj_estimates(
    single_table, pl.Series([3.0, 6.0, 12.0])
)
single_result
shape: (3, 5)
timesstate_occupancy_probability_0state_occupancy_probability_1state_occupancy_probability_2estimate_origin
f64f64f64f64enum
3.00.80.20.0"fixed_time_horizons"
6.00.5333330.4666670.0"fixed_time_horizons"
12.00.01.00.0"fixed_time_horizons"

State 2 remains zero, while state 0 is the event-free probability.

Encode the event of interest as 1 and the competing event as 2.

competing_data = pl.DataFrame({
    "times": [2.0, 4.0, 5.0, 8.0, 10.0, 12.0],
    "reals": [1, 2, 0, 1, 2, 1],
})
competing_table = prepare_event_table(competing_data)
competing_result = predict_aj_estimates(
    competing_table, pl.Series([3.0, 6.0, 9.0, 12.0])
)
competing_result
shape: (4, 5)
timesstate_occupancy_probability_0state_occupancy_probability_1state_occupancy_probability_2estimate_origin
f64f64f64f64enum
3.00.8333330.1666670.0"fixed_time_horizons"
6.00.6666670.1666670.166667"fixed_time_horizons"
9.00.4444440.3888890.166667"fixed_time_horizons"
12.0-1.1102e-160.6111110.388889"fixed_time_horizons"

Do not censor competing events: coding them as 0 changes the estimand.

Return horizons and event times together

combined_result = predict_aj_estimates(
    competing_table,
    pl.Series([3.0, 6.0, 9.0, 12.0]),
    full_event_table=True,
)
combined_result.select("times", "estimate_origin")
shape: (10, 2)
timesestimate_origin
f64enum
3.0"fixed_time_horizons"
6.0"fixed_time_horizons"
9.0"fixed_time_horizons"
12.0"fixed_time_horizons"
2.0"event_table"
4.0"event_table"
5.0"event_table"
8.0"event_table"
10.0"event_table"
12.0"event_table"

Use estimate_origin to distinguish requested horizons from observed times.

Estimate within groups

The public functions operate on one cohort at a time.

data_with_groups = competing_data.with_columns(
    pl.Series("group", ["A", "A", "A", "B", "B", "B"])
)
estimates_by_group = {
    group[0]: predict_aj_estimates(
        prepare_event_table(group_data.select("times", "reals")),
        pl.Series([6.0, 12.0]),
    )
    for group, group_data in data_with_groups.group_by("group")
}
{k: value.shape for k, value in estimates_by_group.items()}
{'A': (2, 5), 'B': (2, 5)}

These are descriptive within-group estimates, not an adjusted regression model.

Convert pandas input

pandas_data = pd.DataFrame({
    "time": [2, 4, 6, 8],
    "event": [1, 0, 2, 1],
})
polars_data = pl.from_pandas(pandas_data).select(
    pl.col("time").cast(pl.Float64).alias("times"),
    pl.col("event").cast(pl.Int64).alias("reals"),
)
prepare_event_table(polars_data).select("times", "at_risk")
shape: (4, 2)
timesat_risk
f64i64
2.04
4.03
6.02
8.01

Recode outcomes before estimation and confirm that only 0, 1, and optional 2 are present.

Next steps