learner fit predict
<char> <char> <char>
1: coxph fit_coxph predict_coxph
2: aalen fit_aalen predict_aalen
3: glmnet fit_glmnet predict_glmnet
4: selectcox fit_selectcox predict_selectcox
5: aftgee fit_aftgee predict_aftgee
6: flexsurvreg fit_flexsurvreg predict_flexsurvreg
7: stpm2 fit_stpm2 predict_stpm2
8: bnnsurv fit_bnnsurv predict_bnnsurv
9: rpart fit_rpart predict_rpart
10: bart fit_bart predict_bart
11: xgboost fit_xgboost predict_xgboost
12: coxboost fit_coxboost predict_coxboost
13: ranger fit_ranger predict_ranger
14: rsf fit_rsf predict_rsf
15: cforest fit_cforest predict_cforest
16: blackboost fit_blackboost predict_blackboost
17: survsvm fit_survsvm predict_survsvm
18: survdnn fit_survdnn predict_survdnn
19: orsf fit_orsf predict_orsf
20: survmetalearner fit_survmetalearner predict_survmetalearner
learner fit predict
<char> <char> <char>
tune has_fit has_predict has_tune available
<char> <lgcl> <lgcl> <lgcl> <lgcl>
1: <NA> TRUE TRUE FALSE TRUE
2: <NA> TRUE TRUE FALSE TRUE
3: tune_glmnet TRUE TRUE TRUE TRUE
4: tune_selectcox TRUE TRUE TRUE TRUE
5: <NA> TRUE TRUE FALSE TRUE
6: tune_flexsurvreg TRUE TRUE TRUE TRUE
7: <NA> TRUE TRUE FALSE TRUE
8: tune_bnnsurv TRUE TRUE TRUE TRUE
9: tune_rpart TRUE TRUE TRUE TRUE
10: tune_bart TRUE TRUE TRUE TRUE
11: tune_xgboost TRUE TRUE TRUE TRUE
12: tune_coxboost TRUE TRUE TRUE TRUE
13: tune_ranger TRUE TRUE TRUE TRUE
14: tune_rsf TRUE TRUE TRUE TRUE
15: tune_cforest TRUE TRUE TRUE TRUE
16: tune_blackboost TRUE TRUE TRUE TRUE
17: tune_survsvm TRUE TRUE TRUE TRUE
18: tune_survdnn TRUE TRUE TRUE TRUE
19: tune_orsf TRUE TRUE TRUE TRUE
20: <NA> TRUE TRUE FALSE TRUE
tune has_fit has_predict has_tune available
<char> <lgcl> <lgcl> <lgcl> <lgcl>
survalis: An Interpretable Survival Machine Learning Framework in R
One fit/predict contract across 19 survival learners, with benchmarking, calibration, and model-agnostic interpretation built in
Comparing survival models in R usually means learning a different interface for each one: coxph(), randomForestSRC::rfsrc(), xgboost::xgb.train(), and a deep survival network all expect different data shapes and return different prediction objects. survalis was built to close that gap: a unified framework for survival machine learning with a consistent contract for fitting, prediction, evaluation, and interpretation.
Core philosophy
Every learner in survalis follows the same pattern:
-
fit_*()functions return a standardizedmlsurv_modelobject -
predict_*()functions return adata.frameof survival probabilities, one row per subject, one column per requested time (t=100,t=200, …) - Evaluation is fully modular: any
fit_*/predict_*pair plugs intocv_survlearner()orscore_survmodel() - Everything downstream (benchmarking, calibration, interpretation) is designed to work on that same survival-probability matrix, regardless of which learner produced it
Why explicit fit_coxph() / fit_rsf() instead of one fit(learner = ...)
This is a deliberate departure from funcml, my general-purpose ML package, where fit(y ~ ., data, learner = "ranger") collapses every learner behind one verb and a string argument. survalis keeps a named function per learner, fit_coxph(), fit_rsf(), fit_survdnn(), and so on, instead of a single dispatcher.
The reason is that the survival setting is not just “regression with a different loss.” Every learner here already has to negotiate right-censoring, risk sets, and time-varying prediction targets on its own terms, and each one leans on a different underlying package (survival, randomForestSRC, xgboost, torch, flexsurv, …) with its own quirks around ties, time horizons, and baseline hazard estimation. Hiding fit_coxph and fit_rsf behind a shared fit(learner = "rsf") string would trade a small amount of typing for a layer of indirection between the call site and the model-specific machinery it actually invokes, machinery that, in survival analysis, is rarely interchangeable enough to be worth hiding. Keeping the learner name in the function name is a compactness choice: the API surface is larger (nineteen fit_*()/predict_*() pairs instead of one), but each call is self-describing and there is one fewer level of abstraction between what you write and what runs.
What survalis does unify is everything after fitting: predict_*() always returns the same survival-probability matrix shape, so cindex_survmat(), brier(), cv_survlearner(), and benchmark() never need to know which fit_*() produced it. The genericity lives at the evaluation/interpretation layer, not at the fitting layer.
That output shape was a deliberate constraint, not an incidental one. A Cox model naturally hands back a linear predictor or a hazard ratio; a random survival forest hands back an ensemble of terminal-node curves; a deep survival network hands back whatever its loss was trained on (a Cox partial likelihood, an AFT loss, a discrete-time hazard). fit_survdnn() itself can predict a linear predictor if asked, since its loss functions include a Cox-style formulation. survalis standardizes on the survival-probability matrix anyway, over times t=100, t=200, and so on, because a matrix is, by construction, a richer object than a scalar: a linear predictor collapses a subject’s entire risk trajectory into one number and one implicit ranking, while a survival curve preserves how that risk actually unfolds over the follow-up window, which is what a fixed-horizon metric, a time-varying AUC, or a calibration curve each need to read back out at a different point. Forcing every predict_*() to that one shape throws away learner-specific detail on the way out (a Cox model’s clean linear score, a forest’s raw ensemble votes), but it is exactly what makes cindex_survmat(), brier(), timeroc_survmat(), and every interpretation function in the package learner-agnostic, without asking any of them to reconstruct a survival curve from a scalar first. The alternative, keeping each learner’s native output format and writing metric functions that branch on model class, is the design survalis specifically avoids.
Nineteen learners, one interface
That list spans semiparametric (coxph, aalen), parametric (flexsurvreg, stpm2, aftgee), tree-based and ensemble (rpart, rsf, cforest, orsf), boosting (xgboost, blackboost), kernel (survsvm), and deep-learning (survdnn, bnnsurv) learners, plus survmetalearner for combining several of them. Most also ship a matching tune_*() function for grid or random hyperparameter search.
A minimal example
t=90 t=180 t=270
1 0.6640601 0.37822072 0.257579996
2 0.7361432 0.48310054 0.362410259
3 0.6104309 0.30966354 0.194867900
4 0.6541127 0.36490330 0.245015675
5 0.7375010 0.48521958 0.364629899
6 0.1897037 0.01929484 0.004055248
Discrimination and calibration are single function calls away, at a fixed horizon or cross-validated across folds:
cindex.C index brier.brier
0.733744 0.134603
Swap fit_coxph/predict_coxph for any other pair in list_survlearners() and every downstream call, cindex_survmat(), brier(), cv_survlearner(), benchmark(), stays exactly the same.
Beyond prediction: interpretation
Because every learner returns the same survival-probability shape, survalis’s interpretability layer works uniformly across all of them: permutation variable importance (compute_varimp()), accumulated local effects and partial dependence (compute_ale(), compute_pdp()), SHAP-based attribution (compute_shap()), pairwise interaction strength (compute_interactions()), counterfactual recommendations (compute_counterfactual()), and surrogate-tree explanations (compute_surrogate(), compute_tree_surrogate()), each with a matching plot_*() function.
Install
install.packages("survalis")