Bits & Flames bitsandflames/fyron

Survival Analysis

fyron.survival supports standard time-to-event workflows for clinical cohorts: Kaplan-Meier curves, log-rank tests, restricted mean survival time, Cox proportional hazards models, proportional hazards diagnostics, Weibull accelerated failure time models, and optional gradient survival boosting.

Install

bash
uv add "fyron[survival]"

For gradient survival boosting:

bash
uv add "fyron[survival-ml]"

Expected Cohort Format

Survival functions expect a patient-level DataFrame with:

ColumnMeaning
timepositive follow-up duration
eventevent indicator, where 1 means event and 0 means censored
covariatesclinical, imaging, biomarker, or model-score features
group columnoptional categorical group for KM/log-rank/RMST

You can build time and event with fyron.cohort.build_survival_columns.

python
from fyron.cohort import build_survival_columns

df = build_survival_columns(
    df,
    start_col="diagnosis_date",
    end_col="last_followup_or_event_date",
    event_col="death_event",
    time_col="time",
)

Choosing A Survival Method

Use Kaplan-Meier curves to describe survival experience and compare groups visually. Use RMST when proportional hazards are questionable or when an absolute time-based summary is easier to communicate. Use Cox models when you need adjusted hazard ratios and can assess proportional hazards. Use Weibull AFT when an acceleration-time interpretation is more natural. Use gradient survival boosting when you need non-linear risk modeling, but keep it separate from classical inference.

Always inspect censoring, follow-up time, and event definitions before modeling. A technically valid model can still be clinically misleading if time starts at the wrong index date or if censoring differs systematically between groups.

Research Decision: Endpoint And Method

DecisionPractical guidance
Index dateUse the date where follow-up truly starts, such as diagnosis, baseline CT, treatment, or enrollment.
Event codingevent=1 means the event occurred; event=0 means censored.
Time unitKeep one unit across KM, Cox, RMST, calibration, and fixed-horizon prediction.
KM vs CoxUse KM for descriptive curves; use Cox for adjusted hazard ratios when PH assumptions are plausible.
RMSTPrefer RMST when absolute survival time is easier to communicate or PH is questionable.
PH diagnosticsRun check_ph_assumptions before presenting adjusted Cox results as stable hazard ratios.

API Contract

TopicContract
Input shapePatient-level DataFrame with one row per analysis unit.
Required columnsPositive duration column, binary event column where 1 means event, and named group/covariate columns.
Return shapeKaplan-Meier result objects, DataFrames with estimates/tests, fitted lifelines objects, or Matplotlib figures.
Saved artifactsKM figures, at-risk tables, Cox/HR tables, RMST tables, Schoenfeld diagnostics, and survival prediction tables.
Failure modesMissing/invalid duration or event columns, nonpositive follow-up, no events, convergence warnings, violated PH assumptions, or missing optional survival extras.

API Summary

FunctionPurposeMain output
plot_kaplan_meierKM curves, log-rank tests, optional risk counts/RMST annotationKaplanMeierPlotResult
create_survival_groupscategorical, median, tertile, quartile, custom splitsDataFrame copy
calculate_rmstrestricted mean survival time by groupDataFrame
compare_rmstpairwise RMST contrastsDataFrame
fit_univariate_coxone Cox model per covariateresult table and fitters
fit_multivariate_coxmultivariable Cox PH modelresult table and fitter
cox_forest_tablereporting table for Cox forest plotsDataFrame
survival_prediction_tablefixed-time survival/risk predictions from supported modelsDataFrame
check_ph_assumptionsSchoenfeld-style PH diagnosticssummary/violations
schoenfeld_residuals_tablescaled Schoenfeld residuals for Cox PH diagnosticsDataFrame
schoenfeld_test_tablePH test table with optional adjusted p-valuesDataFrame
plot_schoenfeld_residualsresidual scatter plot by covariate(fig, ax)
fit_weibull_aftparametric accelerated failure time modelresult table and fitter
plot_aft_survival_curvespredicted survival curves for covariate profilesfigure/axes
train_gradient_survival_boostingnon-linear survival risk modelingmodel, risk scores, concordance

Function Parameter Reference

FunctionRequiredOptionalReturns
plot_kaplan_meierdf, duration_col, event_colgroup_col, split options, CI/risk count/style/save/RMST optionsKaplanMeierPlotResult
create_survival_groupsdf, column, method, group_colbins, labels, duplicatesDataFrame copy
default_tau_from_observed_timesdf, duration_colgroup_colfloat tau
calculate_rmstdf, duration_col, event_colgroup_col, tauRMST DataFrame
compare_rmstdf, duration_col, event_col, group_col, referencetaucomparison DataFrame
fit_univariate_coxdf, duration_col, event_col, covariatesstrata, robustresult table and fitters
fit_multivariate_coxdf, duration_col, event_col, one of covariates/formulastrata, robustresult table and fitter
cox_forest_tableCox result dict, fitter, or DataFramecovariate_colreporting DataFrame
survival_prediction_tablefitted model, feature DataFrame, timesid_collong prediction DataFrame
check_ph_assumptionstraining_df, fitted Cox fitterp_value_threshold, show_plots, columns, advicesummary/violations dict
schoenfeld_residuals_tablefitted Cox fitter, training DataFramescaled/raw residualsresidual DataFrame
schoenfeld_test_tablefitted Cox fitter, training DataFrametime transform, FDR correctiontest DataFrame
plot_schoenfeld_residualsresidual tablecovariates, save options(fig, ax)
fit_weibull_aftdf, duration_col, event_col, one of covariates/formularobustresult table and fitter
plot_aft_survival_curvesfitter, covariates, profilesplot_baseline, figsize, ax, plot_kwargsfigure/axes/fitter dict
train_gradient_survival_boostingX_train, duration_train, event_traintest data, random_state, model paramsmodel, risk scores, concordance dict

Kaplan-Meier Curves

python
from fyron.survival import plot_kaplan_meier

km = plot_kaplan_meier(
    df,
    duration_col="time",
    event_col="event",
    group_col="stage",
    at_risk_counts=True,
    title="Overall survival by stage",
    figsize=(7, 4),
)

km.logrank
km.n_per_group

Use Kaplan-Meier plots for descriptive group comparisons. They are especially helpful before fitting adjusted models because they expose censoring patterns and group sizes.

Automatic Risk Groups

You can derive groups from a continuous score:

python
km = plot_kaplan_meier(
    df,
    duration_col="time",
    event_col="event",
    split_column="risk_score",
    split_method="quartile",
    at_risk_counts=True,
)

Survival Group Construction

python
from fyron.survival import create_survival_groups

df_q = create_survival_groups(
    df,
    column="risk_score",
    method="quartile",
    out_col="risk_quartile",
)

Supported methods include:

MethodUse
categoricalpreserve an existing categorical column
medianhigh/low split
tertilethree risk bands
quartilefour risk bands
customuser-defined bins and labels

Restricted Mean Survival Time

RMST is often easier to interpret clinically than a hazard ratio because it estimates average event-free time up to a fixed horizon tau.

python
from fyron.survival import calculate_rmst, compare_rmst

rmst = calculate_rmst(
    df,
    duration_col="time",
    event_col="event",
    group_col="treatment",
    tau=365,
)

contrast = compare_rmst(
    df,
    duration_col="time",
    event_col="event",
    group_col="treatment",
    reference="standard",
    tau=365,
)

Choose tau before looking at results when possible. It should be within the range where the survival curve is still supported by enough patients.

Cox Regression

python
from fyron.survival import fit_univariate_cox, fit_multivariate_cox

uni = fit_univariate_cox(
    df,
    duration_col="time",
    event_col="event",
    covariates=["age", "risk_score", "tumor_volume"],
)

multi = fit_multivariate_cox(
    df,
    duration_col="time",
    event_col="event",
    covariates=["age", "risk_score", "tumor_volume"],
)

multi["results"]

The results table includes hazard ratios, confidence intervals, p-values, row counts, and concordance information when available.

For reporting and forest plots:

python
from fyron.survival import cox_forest_table

forest = cox_forest_table(multi)

For fixed-time survival or event-risk tables:

python
from fyron.survival import survival_prediction_table

predictions = survival_prediction_table(
    multi["fitter"],
    df[["age", "risk_score", "tumor_volume"]],
    times=[365, 730],
)

Formula Interface

Use formulas for categorical terms or interactions supported by lifelines:

python
multi = fit_multivariate_cox(
    df,
    duration_col="time",
    event_col="event",
    formula="age + risk_score + C(stage)",
)

Proportional Hazards Diagnostics

python
from fyron.survival import check_ph_assumptions

diagnostics = check_ph_assumptions(
    df.dropna(),
    multi["fitter"],
    show_plots=False,
)

diagnostics["summary"]
diagnostics["violations"]

Use these checks before interpreting Cox coefficients as time-constant hazard ratios.

For manuscript supplements and reviewer-facing diagnostics, export the residual and test tables explicitly:

python
from fyron.survival import (
    plot_schoenfeld_residuals,
    schoenfeld_residuals_table,
    schoenfeld_test_table,
)

residuals = schoenfeld_residuals_table(multi["fitter"], df.dropna())
ph_tests = schoenfeld_test_table(
    multi["fitter"],
    df.dropna(),
    method="rank",
    p_adjust="fdr_bh",
)

fig, ax = plot_schoenfeld_residuals(
    residuals,
    covariates=["age", "risk_score"],
)

Low p-values suggest possible time-varying effects. They do not automatically invalidate a study; they indicate that the model interpretation needs review, such as stratification, time interactions, RMST, or AFT modeling.

Weibull AFT

AFT models describe covariate effects on survival time rather than instantaneous hazard.

python
from fyron.survival import fit_weibull_aft, plot_aft_survival_curves

aft = fit_weibull_aft(
    df,
    duration_col="time",
    event_col="event",
    covariates=["age", "risk_score"],
)

plot_aft_survival_curves(
    aft["fitter"],
    covariates="risk_score",
    profiles=[{"risk_score": -1.0}, {"risk_score": 1.0}],
    figsize=(6, 4),
)

Gradient Survival Boosting

Gradient survival boosting is useful when you expect non-linear or interaction effects and want a risk ranking for censored outcomes.

python
from fyron.survival import train_gradient_survival_boosting

boosted = train_gradient_survival_boosting(
    X_train,
    duration_train=train_df["time"],
    event_train=train_df["event"],
    X_test=X_test,
    duration_test=test_df["time"],
    event_test=test_df["event"],
    n_estimators=300,
    learning_rate=0.05,
    max_depth=2,
    random_state=42,
)

boosted["concordance_index"]
boosted["risk_score"]

Returned keys include:

KeyMeaning
modelfitted GradientBoostingSurvivalAnalysis
risk_scoreevaluation-set risk scores
concordance_indexevaluation concordance
train_risk_scoretraining-set risk scores
train_concordance_indextraining concordance
y_train / y_evalstructured survival arrays used by scikit-survival

Higher risk scores indicate higher event risk.

Practical Checklist

  • Confirm time values are positive.
  • Confirm event uses 1 for event and 0 for censored.
  • Plot Kaplan-Meier curves before modeling.
  • Report group sizes and censoring patterns.
  • Use RMST when an absolute time difference is more interpretable than a hazard ratio.
  • Check PH assumptions before relying on Cox PH interpretations.
  • Treat survival boosting as a prediction/ranking model, not a replacement for causal survival analysis.

Return Values

Function familyReturn shapeNotes
Kaplan-Meier plottingKaplanMeierPlotResultIncludes figure/axes plus group counts and log-rank details when available.
RMST helpersDataFrameOne row per group or contrast.
Cox/AFT helpersdict with result table and fitted modelKeep both the table and fitter for diagnostics or plotting.
PH diagnosticsdictIncludes summary tables and violation flags.
survival boostingdictIncludes model, risk scores, concordance, and structured survival arrays.

Common Pitfalls

  • Do not use a post-diagnosis or post-treatment variable as if it were baseline.
  • Do not interpret Cox hazard ratios without checking proportional hazards.
  • Do not choose tau for RMST only after inspecting the most favorable result.
  • Do not compare KM curves without checking group sizes and censoring.
  • Ensure all duration columns use the same time unit.