This vignette walks through a full clinical prediction modelling workflow using triageR, from raw data to a TRIPOD+AI-aligned report. We will use the PIMA Indians Diabetes dataset, a well-known real-world clinical dataset, to demonstrate each stage of the pipeline.
The PIMA dataset has a known data quality issue: several clinical
measurements use 0 to represent a missing value, which we
know is physiologically impossible for variables like blood pressure or
BMI. We convert these to proper NAs before proceeding, this
is a good example of the kind of cleaning
tr_load_clinical() expects to receive data after.
data(PimaIndiansDiabetes)
pima <- PimaIndiansDiabetes
pima$id <- seq_len(nrow(pima))
pima$glucose[pima$glucose == 0] <- NA
pima$pressure[pima$pressure == 0] <- NA
pima$triceps[pima$triceps == 0] <- NA
pima$insulin[pima$insulin == 0] <- NA
pima$mass[pima$mass == 0] <- NA
pima_loaded <- tr_load_clinical(pima, id_col = "id")
pima_loaded
#> # A tibble: 768 × 10
#> pregnant glucose pressure triceps insulin mass pedigree age diabetes id
#> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <fct> <int>
#> 1 6 148 72 35 NA 33.6 0.627 50 pos 1
#> 2 1 85 66 29 NA 26.6 0.351 31 neg 2
#> 3 8 183 64 NA NA 23.3 0.672 32 pos 3
#> 4 1 89 66 23 94 28.1 0.167 21 neg 4
#> 5 0 137 40 35 168 43.1 2.29 33 pos 5
#> 6 5 116 74 NA NA 25.6 0.201 30 neg 6
#> 7 3 78 50 32 88 31 0.248 26 pos 7
#> 8 10 115 NA NA NA 35.3 0.134 29 neg 8
#> 9 2 197 70 45 543 30.5 0.158 53 pos 9
#> 10 8 125 96 NA NA NA 0.232 54 pos 10
#> # ℹ 758 more rowstr_check_missing(pima_loaded)
#> Missing data summary:
#> - 5 of 10 columns have missing values
#> - 768 total rows
#> # A tibble: 10 × 3
#> column n_missing pct_missing
#> <chr> <int> <dbl>
#> 1 insulin 374 48.7
#> 2 triceps 227 29.6
#> 3 pressure 35 4.6
#> 4 mass 11 1.4
#> 5 glucose 5 0.7
#> 6 pregnant 0 0
#> 7 pedigree 0 0
#> 8 age 0 0
#> 9 diabetes 0 0
#> 10 id 0 0insulin and triceps show substantial
missingness — a realistic scenario in clinical datasets. We impute using
mice:
We split the data into training and test sets, then fit a logistic regression model.
set.seed(42)
n <- nrow(pima_imputed)
train_idx <- sample(seq_len(n), size = floor(0.7 * n))
pima_train <- pima_imputed[train_idx, ]
pima_test <- pima_imputed[-train_idx, ]
pima_model <- tr_fit(pima_train, outcome = "diabetes", engine = "logistic_reg")
#> Model fitted successfully using engine: logistic_regtriageR also supports "random_forest" and
"boost_tree" engines via the same interface — useful for
comparing approaches without rewriting your pipeline:
Validation on a genuine holdout set produces discrimination metrics, a confusion matrix, and an ROC curve. We validate all three engines on the same holdout set to compare performance.
pima_validation <- tr_validate(pima_model, newdata = pima_test)
#> Validation metrics (newdata):
#> # A tibble: 4 × 3
#> .metric .estimator .estimate
#> <chr> <chr> <dbl>
#> 1 roc_auc binary 0.835
#> 2 sens binary 0.587
#> 3 spec binary 0.865
#> 4 accuracy binary 0.775
#>
#> Confusion Matrix:
#> Truth
#> Prediction neg pos
#> neg 135 31
#> pos 21 44pima_validation_rf <- tr_validate(pima_model_rf, newdata = pima_test)
#> Validation metrics (newdata):
#> # A tibble: 4 × 3
#> .metric .estimator .estimate
#> <chr> <chr> <dbl>
#> 1 roc_auc binary 0.829
#> 2 sens binary 0.613
#> 3 spec binary 0.853
#> 4 accuracy binary 0.775
#>
#> Confusion Matrix:
#> Truth
#> Prediction neg pos
#> neg 133 29
#> pos 23 46pima_validation_xgb <- tr_validate(pima_model_xgb, newdata = pima_test)
#> Validation metrics (newdata):
#> # A tibble: 4 × 3
#> .metric .estimator .estimate
#> <chr> <chr> <dbl>
#> 1 roc_auc binary 0.828
#> 2 sens binary 0.587
#> 3 spec binary 0.846
#> 4 accuracy binary 0.762
#>
#> Confusion Matrix:
#> Truth
#> Prediction neg pos
#> neg 132 31
#> pos 24 44engine_comparison <- dplyr::bind_rows(
dplyr::mutate(pima_validation$metrics, engine = "logistic_reg"),
dplyr::mutate(pima_validation_rf$metrics, engine = "random_forest"),
dplyr::mutate(pima_validation_xgb$metrics, engine = "boost_tree")
)
tidyr::pivot_wider(engine_comparison, names_from = .metric, values_from = .estimate)
#> # A tibble: 3 × 6
#> .estimator engine roc_auc sens spec accuracy
#> <chr> <chr> <dbl> <dbl> <dbl> <dbl>
#> 1 binary logistic_reg 0.835 0.587 0.865 0.775
#> 2 binary random_forest 0.829 0.613 0.853 0.775
#> 3 binary boost_tree 0.828 0.587 0.846 0.762For the remainder of this vignette, we continue with the logistic regression model for explainability and reporting, as it offers the most directly interpretable coefficients for clinical use.
Glucose consistently emerges as the strongest predictor of diabetes in this dataset, consistent with established clinical literature.
tr_agent_review() checks for common clinical modelling
pitfalls: class imbalance, low events-per-variable ratio, possible data
leakage, and small sample size.
pima_sensitivity <- tr_sensitivity(pima_train, pima_model)
#> Model fitted successfully using engine: logistic_reg
#> Validation metrics (training_data):
#> # A tibble: 4 × 3
#> .metric .estimator .estimate
#> <chr> <chr> <dbl>
#> 1 roc_auc binary 0.850
#> 2 sens binary 0.575
#> 3 spec binary 0.884
#> 4 accuracy binary 0.773
#>
#> Confusion Matrix:
#> Truth
#> Prediction neg pos
#> neg 304 82
#> pos 40 111#> Model fitted successfully using engine: logistic_reg
#> Validation metrics (training_data):
#> # A tibble: 4 × 3
#> .metric .estimator .estimate
#> <chr> <chr> <dbl>
#> 1 roc_auc binary 0.860
#> 2 sens binary 0.611
#> 3 spec binary 0.890
#> 4 accuracy binary 0.792
#>
#> Confusion Matrix:
#> Truth
#> Prediction neg pos
#> neg 290 68
#> pos 36 107
#>
#> --- Sensitivity Analysis Comparison ---
#> # A tibble: 2 × 6
#> scenario .estimator roc_auc sens spec accuracy
#> <chr> <chr> <dbl> <dbl> <dbl> <dbl>
#> 1 complete_case binary 0.850 0.575 0.884 0.773
#> 2 outliers_excluded binary 0.860 0.611 0.890 0.792
#>
#> Note: Compare AUC/sensitivity/specificity across scenarios. Large swings suggest the model is not robust to that assumption.
Finally, all of the above can be compiled into a single reproducible report, aligned with TRIPOD+AI reporting guidance.
If a Gemini API key is configured (see
?tr_recommend_method), triageR can also suggest an
appropriate statistical or ML approach based on the structure of your
data:
This vignette covered the full triageR workflow: loading and cleaning clinical data, handling missingness, fitting and validating a model, explainability, automated pipeline review, sensitivity analysis, and reproducible reporting, all using a real clinical dataset.