This is an R Markdown Notebook. When you execute code within the notebook, the results appear beneath the code.

Try executing this chunk by clicking the Run button within the chunk or by placing your cursor inside it and pressing Cmd+Shift+Enter.

Add a new chunk by clicking the Insert Chunk button on the toolbar or by pressing Cmd+Option+I.

When you save the notebook, an HTML file containing the code and output will be saved alongside it (click the Preview button or press Cmd+Shift+K to preview the HTML file).

The preview shows you a rendered HTML copy of the contents of the editor. Consequently, unlike Knit, Preview does not run any R code chunks. Instead, the output of the chunk when it was last run in the editor is displayed.

# Load required packages
library(randomForestSRC)
library(survival)
library(dplyr)
library(parallel)

# Assuming your data frame is named 'data' with:
# - time: survival time
# - status: event indicator (1=event, 0=censored)
# - other columns: predictor variables

# Example data preparation (replace with your actual data)
# data <- your_dataframe
# Make sure it has columns: time, status, and predictor variables

Random Grid Generation (200 Combinations) The code samples 200 random hyperparameter combinations based on the ranges specified in your Eye-Risk project document

set.seed(123)

# Generate 200 random hyperparameter combinations
n_combinations <- 200

hyper_grid <- data.frame(
  ntree    = sample(800:1500, n_combinations, replace = TRUE),
  mtry     = sample(1:10, n_combinations, replace = TRUE),
  nodesize = sample(1:10, n_combinations, replace = TRUE),
  nsplit   = sample(5:15, n_combinations, replace = TRUE)
) %>%
  distinct()

# Display default values mentioned in document (ntree=1000, mtry=6, nodesize=3, nsplit=10)
cat("Default values: ntree=1000, mtry=6, nodesize=3, nsplit=10\n")
cat("Number of unique combinations:", nrow(hyper_grid), "\n")

5-Fold Cross-Validation Stratified on Events This stratifies the folds to ensure balanced event distribution across folds

# Create stratified folds based on event status
set.seed(456)

# Stratify on number of events
create_stratified_folds <- function(data, k = 5) {
  events <- which(data$status == 1)
  censored <- which(data$status == 0)
  
  # Create folds for events and censored separately
  event_folds <- split(sample(events), 1:k)
  censored_folds <- split(sample(censored), 1:k)
  
  # Combine to create balanced folds
  folds <- lapply(1:k, function(i) {
    c(event_folds[[i]], censored_folds[[i]])
  })
  
  return(folds)
}

folds <- create_stratified_folds(data, k = 5)

# Verify stratification
cat("Events per fold:\n")
sapply(folds, function(fold) sum(data$status[fold]))

Cross-Validation Function with C-Index The performance metric is Harrell’s C-index, calculated as 1 - err.rate in randomForestSRC

# Function to evaluate one hyperparameter combination
cv_evaluate <- function(params, data, folds) {
  
  c_indices <- numeric(length(folds))
  
  for (i in seq_along(folds)) {
    
    # Split data
    test_idx <- folds[[i]]
    train_data <- data[-test_idx, ]
    test_data <- data[test_idx, ]
    
    # Fit Random Survival Forest
    rsf_model <- rfsrc(
      Surv(time, status) ~ .,
      data = train_data,
      ntree = params$ntree,
      mtry = params$mtry,
      nodesize = params$nodesize,
      nsplit = params$nsplit,
      splitrule = "logrank",
      importance = "none",
      seed = -1
    )
    
    # Predict on test set
    rsf_pred <- predict(rsf_model, newdata = test_data)
    
    # Calculate C-index (1 - error rate)
    c_indices[i] <- 1 - rsf_pred$err.rate[rsf_pred$ntree]
  }
  
  # Return mean C-index across folds
  return(mean(c_indices, na.rm = TRUE))
}

Grid Search with Parallel Processing Using parallel processing as mentioned in your document (parallelMap):

# Setup parallel processing
n_cores <- detectCores() - 1
cl <- makeCluster(n_cores)
clusterEvalQ(cl, {
  library(randomForestSRC)
  library(survival)
})
clusterExport(cl, c("data", "folds", "cv_evaluate"))

# Perform grid search
cat("Starting grid search with", nrow(hyper_grid), "combinations...\n")

cv_results <- parLapply(cl, 1:nrow(hyper_grid), function(i) {
  c_index <- cv_evaluate(hyper_grid[i, ], data, folds)
  return(data.frame(
    combo_id = i,
    ntree = hyper_grid$ntree[i],
    mtry = hyper_grid$mtry[i],
    nodesize = hyper_grid$nodesize[i],
    nsplit = hyper_grid$nsplit[i],
    mean_c_index = c_index
  ))
})

stopCluster(cl)

# Combine results
results_df <- do.call(rbind, cv_results) %>%
  arrange(desc(mean_c_index))

# Display top 10 combinations
head(results_df, 10)

Final Model with Best Hyperparameters Train the final model using the best hyperparameters on the full dataset:

# Extract best hyperparameters
best_params <- results_df[1, ]

cat("\nBest hyperparameters:\n")
print(best_params)

# Train final model on full dataset
final_model <- rfsrc(
  Surv(time, status) ~ .,
  data = data,
  ntree = best_params$ntree,
  mtry = best_params$mtry,
  nodesize = best_params$nodesize,
  nsplit = best_params$nsplit,
  splitrule = "logrank",
  importance = "permute",  # Get variable importance
  seed = 123
)

# Model summary
print(final_model)

# Variable importance
var_imp <- data.frame(
  variable = names(final_model$importance),
  importance = final_model$importance
) %>%
  arrange(desc(importance))

print(var_imp)

# Final C-index on training data
final_c_index <- 1 - final_model$err.rate[final_model$ntree]
cat("\nFinal C-index:", round(final_c_index, 4), "\n")

Prediction on New Data To make predictions on new observations

# Example: predict on new data
# new_data <- your_new_dataframe

# predictions <- predict(final_model, newdata = new_data)

# Extract predicted mortality (cumulative hazard)
# predicted_risk <- predictions$predicted

# Extract survival probabilities at specific times
# survival_prob <- predictions$survival
LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKVGhpcyBpcyBhbiBbUiBNYXJrZG93bl0oaHR0cDovL3JtYXJrZG93bi5yc3R1ZGlvLmNvbSkgTm90ZWJvb2suIFdoZW4geW91IGV4ZWN1dGUgY29kZSB3aXRoaW4gdGhlIG5vdGVib29rLCB0aGUgcmVzdWx0cyBhcHBlYXIgYmVuZWF0aCB0aGUgY29kZS4gCgpUcnkgZXhlY3V0aW5nIHRoaXMgY2h1bmsgYnkgY2xpY2tpbmcgdGhlICpSdW4qIGJ1dHRvbiB3aXRoaW4gdGhlIGNodW5rIG9yIGJ5IHBsYWNpbmcgeW91ciBjdXJzb3IgaW5zaWRlIGl0IGFuZCBwcmVzc2luZyAqQ21kK1NoaWZ0K0VudGVyKi4gCgpBZGQgYSBuZXcgY2h1bmsgYnkgY2xpY2tpbmcgdGhlICpJbnNlcnQgQ2h1bmsqIGJ1dHRvbiBvbiB0aGUgdG9vbGJhciBvciBieSBwcmVzc2luZyAqQ21kK09wdGlvbitJKi4KCldoZW4geW91IHNhdmUgdGhlIG5vdGVib29rLCBhbiBIVE1MIGZpbGUgY29udGFpbmluZyB0aGUgY29kZSBhbmQgb3V0cHV0IHdpbGwgYmUgc2F2ZWQgYWxvbmdzaWRlIGl0IChjbGljayB0aGUgKlByZXZpZXcqIGJ1dHRvbiBvciBwcmVzcyAqQ21kK1NoaWZ0K0sqIHRvIHByZXZpZXcgdGhlIEhUTUwgZmlsZSkuIAoKVGhlIHByZXZpZXcgc2hvd3MgeW91IGEgcmVuZGVyZWQgSFRNTCBjb3B5IG9mIHRoZSBjb250ZW50cyBvZiB0aGUgZWRpdG9yLiBDb25zZXF1ZW50bHksIHVubGlrZSAqS25pdCosICpQcmV2aWV3KiBkb2VzIG5vdCBydW4gYW55IFIgY29kZSBjaHVua3MuIEluc3RlYWQsIHRoZSBvdXRwdXQgb2YgdGhlIGNodW5rIHdoZW4gaXQgd2FzIGxhc3QgcnVuIGluIHRoZSBlZGl0b3IgaXMgZGlzcGxheWVkLgoKYGBge3J9CiMgTG9hZCByZXF1aXJlZCBwYWNrYWdlcwpsaWJyYXJ5KHJhbmRvbUZvcmVzdFNSQykKbGlicmFyeShzdXJ2aXZhbCkKbGlicmFyeShkcGx5cikKbGlicmFyeShwYXJhbGxlbCkKCiMgQXNzdW1pbmcgeW91ciBkYXRhIGZyYW1lIGlzIG5hbWVkICdkYXRhJyB3aXRoOgojIC0gdGltZTogc3Vydml2YWwgdGltZQojIC0gc3RhdHVzOiBldmVudCBpbmRpY2F0b3IgKDE9ZXZlbnQsIDA9Y2Vuc29yZWQpCiMgLSBvdGhlciBjb2x1bW5zOiBwcmVkaWN0b3IgdmFyaWFibGVzCgojIEV4YW1wbGUgZGF0YSBwcmVwYXJhdGlvbiAocmVwbGFjZSB3aXRoIHlvdXIgYWN0dWFsIGRhdGEpCiMgZGF0YSA8LSB5b3VyX2RhdGFmcmFtZQojIE1ha2Ugc3VyZSBpdCBoYXMgY29sdW1uczogdGltZSwgc3RhdHVzLCBhbmQgcHJlZGljdG9yIHZhcmlhYmxlcwoKYGBgCgpSYW5kb20gR3JpZCBHZW5lcmF0aW9uICgyMDAgQ29tYmluYXRpb25zKQpUaGUgY29kZSBzYW1wbGVzIDIwMCByYW5kb20gaHlwZXJwYXJhbWV0ZXIgY29tYmluYXRpb25zIGJhc2VkIG9uIHRoZSByYW5nZXMgc3BlY2lmaWVkIGluIHlvdXIgRXllLVJpc2sgcHJvamVjdCBkb2N1bWVudAoKCmBgYHtyfQpzZXQuc2VlZCgxMjMpCgojIEdlbmVyYXRlIDIwMCByYW5kb20gaHlwZXJwYXJhbWV0ZXIgY29tYmluYXRpb25zCm5fY29tYmluYXRpb25zIDwtIDIwMAoKaHlwZXJfZ3JpZCA8LSBkYXRhLmZyYW1lKAogIG50cmVlICAgID0gc2FtcGxlKDgwMDoxNTAwLCBuX2NvbWJpbmF0aW9ucywgcmVwbGFjZSA9IFRSVUUpLAogIG10cnkgICAgID0gc2FtcGxlKDE6MTAsIG5fY29tYmluYXRpb25zLCByZXBsYWNlID0gVFJVRSksCiAgbm9kZXNpemUgPSBzYW1wbGUoMToxMCwgbl9jb21iaW5hdGlvbnMsIHJlcGxhY2UgPSBUUlVFKSwKICBuc3BsaXQgICA9IHNhbXBsZSg1OjE1LCBuX2NvbWJpbmF0aW9ucywgcmVwbGFjZSA9IFRSVUUpCikgJT4lCiAgZGlzdGluY3QoKQoKIyBEaXNwbGF5IGRlZmF1bHQgdmFsdWVzIG1lbnRpb25lZCBpbiBkb2N1bWVudCAobnRyZWU9MTAwMCwgbXRyeT02LCBub2Rlc2l6ZT0zLCBuc3BsaXQ9MTApCmNhdCgiRGVmYXVsdCB2YWx1ZXM6IG50cmVlPTEwMDAsIG10cnk9Niwgbm9kZXNpemU9MywgbnNwbGl0PTEwXG4iKQpjYXQoIk51bWJlciBvZiB1bmlxdWUgY29tYmluYXRpb25zOiIsIG5yb3coaHlwZXJfZ3JpZCksICJcbiIpCgpgYGAKCjUtRm9sZCBDcm9zcy1WYWxpZGF0aW9uIFN0cmF0aWZpZWQgb24gRXZlbnRzClRoaXMgc3RyYXRpZmllcyB0aGUgZm9sZHMgdG8gZW5zdXJlIGJhbGFuY2VkIGV2ZW50IGRpc3RyaWJ1dGlvbiBhY3Jvc3MgZm9sZHMKCmBgYHtyfQojIENyZWF0ZSBzdHJhdGlmaWVkIGZvbGRzIGJhc2VkIG9uIGV2ZW50IHN0YXR1cwpzZXQuc2VlZCg0NTYpCgojIFN0cmF0aWZ5IG9uIG51bWJlciBvZiBldmVudHMKY3JlYXRlX3N0cmF0aWZpZWRfZm9sZHMgPC0gZnVuY3Rpb24oZGF0YSwgayA9IDUpIHsKICBldmVudHMgPC0gd2hpY2goZGF0YSRzdGF0dXMgPT0gMSkKICBjZW5zb3JlZCA8LSB3aGljaChkYXRhJHN0YXR1cyA9PSAwKQogIAogICMgQ3JlYXRlIGZvbGRzIGZvciBldmVudHMgYW5kIGNlbnNvcmVkIHNlcGFyYXRlbHkKICBldmVudF9mb2xkcyA8LSBzcGxpdChzYW1wbGUoZXZlbnRzKSwgMTprKQogIGNlbnNvcmVkX2ZvbGRzIDwtIHNwbGl0KHNhbXBsZShjZW5zb3JlZCksIDE6aykKICAKICAjIENvbWJpbmUgdG8gY3JlYXRlIGJhbGFuY2VkIGZvbGRzCiAgZm9sZHMgPC0gbGFwcGx5KDE6aywgZnVuY3Rpb24oaSkgewogICAgYyhldmVudF9mb2xkc1tbaV1dLCBjZW5zb3JlZF9mb2xkc1tbaV1dKQogIH0pCiAgCiAgcmV0dXJuKGZvbGRzKQp9Cgpmb2xkcyA8LSBjcmVhdGVfc3RyYXRpZmllZF9mb2xkcyhkYXRhLCBrID0gNSkKCiMgVmVyaWZ5IHN0cmF0aWZpY2F0aW9uCmNhdCgiRXZlbnRzIHBlciBmb2xkOlxuIikKc2FwcGx5KGZvbGRzLCBmdW5jdGlvbihmb2xkKSBzdW0oZGF0YSRzdGF0dXNbZm9sZF0pKQoKYGBgCgpDcm9zcy1WYWxpZGF0aW9uIEZ1bmN0aW9uIHdpdGggQy1JbmRleApUaGUgcGVyZm9ybWFuY2UgbWV0cmljIGlzIEhhcnJlbGwncyBDLWluZGV4LCBjYWxjdWxhdGVkIGFzIDEgLSBlcnIucmF0ZSBpbiByYW5kb21Gb3Jlc3RTUkMKCmBgYHtyfQojIEZ1bmN0aW9uIHRvIGV2YWx1YXRlIG9uZSBoeXBlcnBhcmFtZXRlciBjb21iaW5hdGlvbgpjdl9ldmFsdWF0ZSA8LSBmdW5jdGlvbihwYXJhbXMsIGRhdGEsIGZvbGRzKSB7CiAgCiAgY19pbmRpY2VzIDwtIG51bWVyaWMobGVuZ3RoKGZvbGRzKSkKICAKICBmb3IgKGkgaW4gc2VxX2Fsb25nKGZvbGRzKSkgewogICAgCiAgICAjIFNwbGl0IGRhdGEKICAgIHRlc3RfaWR4IDwtIGZvbGRzW1tpXV0KICAgIHRyYWluX2RhdGEgPC0gZGF0YVstdGVzdF9pZHgsIF0KICAgIHRlc3RfZGF0YSA8LSBkYXRhW3Rlc3RfaWR4LCBdCiAgICAKICAgICMgRml0IFJhbmRvbSBTdXJ2aXZhbCBGb3Jlc3QKICAgIHJzZl9tb2RlbCA8LSByZnNyYygKICAgICAgU3Vydih0aW1lLCBzdGF0dXMpIH4gLiwKICAgICAgZGF0YSA9IHRyYWluX2RhdGEsCiAgICAgIG50cmVlID0gcGFyYW1zJG50cmVlLAogICAgICBtdHJ5ID0gcGFyYW1zJG10cnksCiAgICAgIG5vZGVzaXplID0gcGFyYW1zJG5vZGVzaXplLAogICAgICBuc3BsaXQgPSBwYXJhbXMkbnNwbGl0LAogICAgICBzcGxpdHJ1bGUgPSAibG9ncmFuayIsCiAgICAgIGltcG9ydGFuY2UgPSAibm9uZSIsCiAgICAgIHNlZWQgPSAtMQogICAgKQogICAgCiAgICAjIFByZWRpY3Qgb24gdGVzdCBzZXQKICAgIHJzZl9wcmVkIDwtIHByZWRpY3QocnNmX21vZGVsLCBuZXdkYXRhID0gdGVzdF9kYXRhKQogICAgCiAgICAjIENhbGN1bGF0ZSBDLWluZGV4ICgxIC0gZXJyb3IgcmF0ZSkKICAgIGNfaW5kaWNlc1tpXSA8LSAxIC0gcnNmX3ByZWQkZXJyLnJhdGVbcnNmX3ByZWQkbnRyZWVdCiAgfQogIAogICMgUmV0dXJuIG1lYW4gQy1pbmRleCBhY3Jvc3MgZm9sZHMKICByZXR1cm4obWVhbihjX2luZGljZXMsIG5hLnJtID0gVFJVRSkpCn0KCmBgYAoKR3JpZCBTZWFyY2ggd2l0aCBQYXJhbGxlbCBQcm9jZXNzaW5nClVzaW5nIHBhcmFsbGVsIHByb2Nlc3NpbmcgYXMgbWVudGlvbmVkIGluIHlvdXIgZG9jdW1lbnQgKHBhcmFsbGVsTWFwKToKCmBgYHtyfQojIFNldHVwIHBhcmFsbGVsIHByb2Nlc3NpbmcKbl9jb3JlcyA8LSBkZXRlY3RDb3JlcygpIC0gMQpjbCA8LSBtYWtlQ2x1c3RlcihuX2NvcmVzKQpjbHVzdGVyRXZhbFEoY2wsIHsKICBsaWJyYXJ5KHJhbmRvbUZvcmVzdFNSQykKICBsaWJyYXJ5KHN1cnZpdmFsKQp9KQpjbHVzdGVyRXhwb3J0KGNsLCBjKCJkYXRhIiwgImZvbGRzIiwgImN2X2V2YWx1YXRlIikpCgojIFBlcmZvcm0gZ3JpZCBzZWFyY2gKY2F0KCJTdGFydGluZyBncmlkIHNlYXJjaCB3aXRoIiwgbnJvdyhoeXBlcl9ncmlkKSwgImNvbWJpbmF0aW9ucy4uLlxuIikKCmN2X3Jlc3VsdHMgPC0gcGFyTGFwcGx5KGNsLCAxOm5yb3coaHlwZXJfZ3JpZCksIGZ1bmN0aW9uKGkpIHsKICBjX2luZGV4IDwtIGN2X2V2YWx1YXRlKGh5cGVyX2dyaWRbaSwgXSwgZGF0YSwgZm9sZHMpCiAgcmV0dXJuKGRhdGEuZnJhbWUoCiAgICBjb21ib19pZCA9IGksCiAgICBudHJlZSA9IGh5cGVyX2dyaWQkbnRyZWVbaV0sCiAgICBtdHJ5ID0gaHlwZXJfZ3JpZCRtdHJ5W2ldLAogICAgbm9kZXNpemUgPSBoeXBlcl9ncmlkJG5vZGVzaXplW2ldLAogICAgbnNwbGl0ID0gaHlwZXJfZ3JpZCRuc3BsaXRbaV0sCiAgICBtZWFuX2NfaW5kZXggPSBjX2luZGV4CiAgKSkKfSkKCnN0b3BDbHVzdGVyKGNsKQoKIyBDb21iaW5lIHJlc3VsdHMKcmVzdWx0c19kZiA8LSBkby5jYWxsKHJiaW5kLCBjdl9yZXN1bHRzKSAlPiUKICBhcnJhbmdlKGRlc2MobWVhbl9jX2luZGV4KSkKCiMgRGlzcGxheSB0b3AgMTAgY29tYmluYXRpb25zCmhlYWQocmVzdWx0c19kZiwgMTApCgpgYGAKCkZpbmFsIE1vZGVsIHdpdGggQmVzdCBIeXBlcnBhcmFtZXRlcnMKVHJhaW4gdGhlIGZpbmFsIG1vZGVsIHVzaW5nIHRoZSBiZXN0IGh5cGVycGFyYW1ldGVycyBvbiB0aGUgZnVsbCBkYXRhc2V0OgoKYGBge3J9CiMgRXh0cmFjdCBiZXN0IGh5cGVycGFyYW1ldGVycwpiZXN0X3BhcmFtcyA8LSByZXN1bHRzX2RmWzEsIF0KCmNhdCgiXG5CZXN0IGh5cGVycGFyYW1ldGVyczpcbiIpCnByaW50KGJlc3RfcGFyYW1zKQoKIyBUcmFpbiBmaW5hbCBtb2RlbCBvbiBmdWxsIGRhdGFzZXQKZmluYWxfbW9kZWwgPC0gcmZzcmMoCiAgU3Vydih0aW1lLCBzdGF0dXMpIH4gLiwKICBkYXRhID0gZGF0YSwKICBudHJlZSA9IGJlc3RfcGFyYW1zJG50cmVlLAogIG10cnkgPSBiZXN0X3BhcmFtcyRtdHJ5LAogIG5vZGVzaXplID0gYmVzdF9wYXJhbXMkbm9kZXNpemUsCiAgbnNwbGl0ID0gYmVzdF9wYXJhbXMkbnNwbGl0LAogIHNwbGl0cnVsZSA9ICJsb2dyYW5rIiwKICBpbXBvcnRhbmNlID0gInBlcm11dGUiLCAgIyBHZXQgdmFyaWFibGUgaW1wb3J0YW5jZQogIHNlZWQgPSAxMjMKKQoKIyBNb2RlbCBzdW1tYXJ5CnByaW50KGZpbmFsX21vZGVsKQoKIyBWYXJpYWJsZSBpbXBvcnRhbmNlCnZhcl9pbXAgPC0gZGF0YS5mcmFtZSgKICB2YXJpYWJsZSA9IG5hbWVzKGZpbmFsX21vZGVsJGltcG9ydGFuY2UpLAogIGltcG9ydGFuY2UgPSBmaW5hbF9tb2RlbCRpbXBvcnRhbmNlCikgJT4lCiAgYXJyYW5nZShkZXNjKGltcG9ydGFuY2UpKQoKcHJpbnQodmFyX2ltcCkKCiMgRmluYWwgQy1pbmRleCBvbiB0cmFpbmluZyBkYXRhCmZpbmFsX2NfaW5kZXggPC0gMSAtIGZpbmFsX21vZGVsJGVyci5yYXRlW2ZpbmFsX21vZGVsJG50cmVlXQpjYXQoIlxuRmluYWwgQy1pbmRleDoiLCByb3VuZChmaW5hbF9jX2luZGV4LCA0KSwgIlxuIikKCmBgYAoKUHJlZGljdGlvbiBvbiBOZXcgRGF0YQpUbyBtYWtlIHByZWRpY3Rpb25zIG9uIG5ldyBvYnNlcnZhdGlvbnMKCmBgYHtyfQojIEV4YW1wbGU6IHByZWRpY3Qgb24gbmV3IGRhdGEKIyBuZXdfZGF0YSA8LSB5b3VyX25ld19kYXRhZnJhbWUKCiMgcHJlZGljdGlvbnMgPC0gcHJlZGljdChmaW5hbF9tb2RlbCwgbmV3ZGF0YSA9IG5ld19kYXRhKQoKIyBFeHRyYWN0IHByZWRpY3RlZCBtb3J0YWxpdHkgKGN1bXVsYXRpdmUgaGF6YXJkKQojIHByZWRpY3RlZF9yaXNrIDwtIHByZWRpY3Rpb25zJHByZWRpY3RlZAoKIyBFeHRyYWN0IHN1cnZpdmFsIHByb2JhYmlsaXRpZXMgYXQgc3BlY2lmaWMgdGltZXMKIyBzdXJ2aXZhbF9wcm9iIDwtIHByZWRpY3Rpb25zJHN1cnZpdmFsCgpgYGAKCgo=