1. SuperSurv with Ensemble

Introduction

The core feature of the SuperSurv package is its ability to combine multiple base survival learners into a highly predictive meta-ensemble. This tutorial walks through preparing data, defining a library of models, fitting the Super Learner, and generating predictions for new patients.

1. Load Data & Prepare Matrices

We will use the built-in metabric dataset, extracting the covariates and performing a standard 80/20 train-test split.

library(SuperSurv)
library(survival)

# Load built-in METABRIC data
data("metabric", package = "SuperSurv")

# Quick 80/20 Train-Test split
set.seed(42)
n_total <- nrow(metabric)
train_idx <- sample(1:n_total, 0.8 * n_total)

train <- metabric[train_idx, ]
test  <- metabric[-train_idx, ]

# Extract just the X covariates (assuming they are named x0, x1, etc.)
x_cols <- grep("^x", names(metabric), value = TRUE)
X_tr <- train[, x_cols]
X_te <- test[, x_cols]

# Define the prediction time grid (e.g., survival at 50, 100, 150, 200 months)
new.times <- c(50, 100, 150, 200)

2. Define the Ensemble Library

We define a library of base survival models. For this quick demonstration, we use lightning-fast parametric and tree-based models.

my_library <- c("surv.coxph", "surv.weibull", "surv.rpart")

3. Train the SuperSurv Metalearner

Before we run the main SuperSurv engine, it is important to understand its key parameters. The Super Learner algorithm relies on cross-validation to assign weights to the base models, and it must model both the event and the censoring mechanism to avoid biased evaluations.

Here is the complete guide to the arguments you need to pass:

The Data Inputs

The Model Libraries

The Meta-Learner & Tuning

Let’s fit two models with verbose = FALSE to see how the meta-learner choice affects the final ensemble weights.

# Fit 1: Least Squares Meta-learner
fit_ls <- SuperSurv(
  time = train$duration,
  event = train$event,
  X = X_tr,
  newdata = X_te,                 # Predict on the test set immediately
  new.times = new.times,       # Our evaluation time grid
  event.library = my_library,
  cens.library = my_library,
  metalearner = "brier", 
  control = list(saveFitLibrary = TRUE), 
  verbose = T,             # Turn to TRUE in practice to see progress!
  selection = "ensemble",
  nFolds = 5                   # 5-fold CV for the meta-learner
)

# Fit 2: Negative Log-Likelihood Meta-learner
fit_nll <- SuperSurv(
  time = train$duration,
  event = train$event,
  X = X_tr,
  newdata = X_te,
  new.times = new.times,
  event.library = my_library,
  cens.library = my_library,
  metalearner = "logloss",       # Swap to nloglik
  control = list(saveFitLibrary = TRUE), 
  verbose = FALSE,
  selection = "ensemble",
  nFolds = 5
)

4. Inspect the Ensemble Weights and Risks

The defining feature of the Super Learner is that it does not just pick the single “best” model; it finds the optimal weighted combination of all models based on their cross-validated performance.

Let’s inspect the weights (event.coef) and the cross-validated risks (event.cvRisks) for both of our meta-learners.

cat("\n--- LEAST SQUARES METALEARNER ---\n")
#> 
#> --- LEAST SQUARES METALEARNER ---
print(round(fit_ls$event.coef, 4))
#>   surv.coxph_screen.all surv.weibull_screen.all   surv.rpart_screen.all 
#>                  0.2344                  0.5906                  0.1750
cat("CV Risks (Lower is Better):\n")
#> CV Risks (Lower is Better):
print(round(fit_ls$event.cvRisks, 4))
#>   surv.coxph_screen.all surv.weibull_screen.all   surv.rpart_screen.all 
#>                  0.4856                  0.4854                  0.4958

cat("\n--- NLOGLIK METALEARNER ---\n")
#> 
#> --- NLOGLIK METALEARNER ---
print(round(fit_nll$event.coef, 4))
#>   surv.coxph_screen.all surv.weibull_screen.all   surv.rpart_screen.all 
#>                  0.9929                  0.0042                  0.0029
cat("CV Risks (Lower is Better):\n")
#> CV Risks (Lower is Better):
print(round(fit_nll$event.cvRisks, 4))
#>   surv.coxph_screen.all surv.weibull_screen.all   surv.rpart_screen.all 
#>                  0.4520                  0.4583                  0.4800

How to Interpret This:

5. Generating Predictions on New Data

If you passed newdata during the training phase, SuperSurv already calculated the predictions for your test set. However, in a real-world clinical setting, you will often train the model once and then predict on brand new patients months later.

Because we set control = list(saveFitLibrary = TRUE) during training, we can use the standard R predict() method.

# Select 3 brand new patients from our test set
new_patients <- X_te[1:6, ]

# Generate predictions using the Least Squares ensemble
ensemble_preds <- predict(
  object = fit_ls, 
  newdata = new_patients, 
  new.times = new.times
)

cat("\n--- PREDICTED SURVIVAL PROBABILITIES ---\n")
#> 
#> --- PREDICTED SURVIVAL PROBABILITIES ---
final_matrix <- ensemble_preds$event.predict
colnames(final_matrix) <- paste0("Time_", new.times)
rownames(final_matrix) <- paste0("Patient_", 1:6)

print(round(final_matrix, 4))
#>           Time_50 Time_100 Time_150 Time_200
#> Patient_1  0.7910   0.5800   0.4053   0.2717
#> Patient_2  0.9249   0.8344   0.7404   0.6473
#> Patient_3  0.9054   0.8000   0.6948   0.5944
#> Patient_4  0.8369   0.6624   0.5048   0.3720
#> Patient_5  0.8552   0.6974   0.5504   0.4219
#> Patient_6  0.7321   0.4865   0.3026   0.1775

Understanding the Output Matrix:

The predict() function returns a list, but the most important element is event.SL.predict. * Rows (\(N\)): Represent individual patients. * Columns (\(T\)): Represent the specific time points we defined in new.times. * Values: The estimated probability that the patient will survive past that specific time point. As time increases (moving left to right across a row), the survival probability naturally decreases.

6. Visualizing Patient-Specific Predictions

While raw probability matrices (\(N \times T\)) are perfect for downstream coding and performance benchmarking, they are difficult to interpret clinically. Doctors and researchers need to see the actual survival trajectories.

SuperSurv includes a built-in plot_predict() function to effortlessly translate this matrix into publication-ready survival curves for individual patients.

# Plot the predicted survival curves for our 3 new patients (Rows 1, 2, and 3)
plot_predict(
  preds = ensemble_preds,
  eval_times = new.times,
  patient_idx = 1:6
)

7. Next Steps

You now know how to prepare data, define a model library, choose a meta-learner, and generate patient-specific survival curves.

However, before deploying a model in clinical practice, you must rigorously prove that the ensemble actually outperforms the individual base learners. Head over to Tutorial 2: Model Performance & Benchmarking to learn how to generate time-dependent Brier Score, AUC, and Uno’s C-index plots!