Getting started with MuTATE

What MuTATE does

MuTATE (Multi-Target Automated Tree Engine) recursively partitions a dataset on binary feature splits, chosen to jointly separate multiple outcome variables at once, rather than a single target the way a standard decision tree (e.g. rpart) does. This is useful when you want one interpretable tree that explains, say, response, tumor size, adverse-event burden, and survival simultaneously - for example when characterizing candidate patient subtypes across several clinically relevant endpoints at once.

At each node, MuTATE evaluates every candidate feature split against every outcome, standardizes the information gain across outcome types, and picks the split that performs best in aggregate (governed by the evalmethod argument - see ?MTPart).

MuTATE supports four outcome types, set via outcome_defs:

Code Outcome type Example
"Cat" Categorical treatment response
"Cont" Continuous tumor size
"Count" Count / event rate adverse event count
"Surv" Time-to-event (survival) overall survival
library(MuTATE)
data(mutate_example)
head(mutate_example)
#>   age sex biomarker      response tumor_size ae_count        time status
#> 1  74   M  4.990758 non_responder   28.01214        3 13.07537131      1
#> 2  54   M  6.520484     responder   33.37856        2 22.34313266      0
#> 3  64   M  5.077982 non_responder   37.90123        3 26.00931724      1
#> 4  66   F  6.470144 non_responder   36.68455        1  4.60484425      0
#> 5  64   M  4.707055 non_responder   24.71583        2 54.52374708      0
#> 6  59   M  4.884225     responder   42.51256        3  0.02140851      1
#>   OS_definition_time_status
#> 1                         1
#> 2                         0
#> 3                         1
#> 4                         0
#> 5                         0
#> 6                         1

mutate_example is a small simulated dataset shipped with the package (see ?mutate_example) with three features (age, sex, biomarker) and four outcomes, one of each supported type.

The survival outcome naming convention

Before fitting a tree, note one MuTATE-specific quirk: for a "Surv" outcome, the name you pass in outcomes must itself be a column in data, and its 3rd and 4th underscore-separated tokens must exactly match the names of the actual time and event columns. In mutate_example, the time column is time, the event column is status, and the outcome name that encodes both is OS_definition_time_status:

strsplit("OS_definition_time_status", "_")[[1]]
#> [1] "OS"         "definition" "time"       "status"
# [3] "time"   -> must match the time column name
# [4] "status" -> must match the event column name

Fitting a tree

features     <- c("age", "sex", "biomarker")
outcomes     <- c("response", "tumor_size", "ae_count", "OS_definition_time_status")
outcome_defs <- c("Cat", "Cont", "Count", "Surv")

tree <- MTPart(
  features, outcomes, outcome_defs, mutate_example,
  depth    = 2,   # maximum tree depth
  nodesize = 30   # minimum observations per node before it can be split
)

MTPart() returns a list with two elements:

tree$partitions
#>    parent child
#> 1    <NA>     1
#> NA      1   2 *
#> 3       1   3 *

Summarizing the tree

MTPartSummary() collapses the tree into a table of one row per split depth, reporting the complexity parameter (CP), average/total relative error, and a cross-validated error estimate at each depth - the multi-target analogue of an rpart complexity table:

summ <- MTPartSummary(tree)
summ$summary_table
#>   nsplit leaves        CP AvgRelError TotRelError Xerror Xstd Eval
#> 1      0      1 0.5042458   1.0000000   1.0000000      1    0    1
#> 2      1      2 0.5042458   0.4957542   0.9915085      1    0    1

MTSummary() computes the same per-outcome summary statistics used inside MTPart(), callable directly on any outcome/data combination - useful for inspecting a single node’s target distributions:

targets <- list(Definitions = outcome_defs, Z = mutate_example[, outcomes])
MTSummary(targets, mutate_example)$response
#> $absent
#> [1] 0
#> 
#> $non_responder_count
#> [1] 104
#> 
#> $non_responder_prop
#> [1] 0.52
#> 
#> $responder_count
#> [1] 96
#> 
#> $responder_prop
#> [1] 0.48
#> 
#> $predicted
#> [1] "non_responder"
#> 
#> $expectedloss
#> [1] 0.48

Pruning

MTPrune() performs cost-complexity pruning, dropping splits whose CP falls below a chosen threshold:

pruned <- MTPrune(tree, cp = 0.02)
pruned$partitions
#>    parent child
#> NA      1   2 *
#> 3       1   3 *

Scoring new data

MTTest() applies a fitted tree’s split rules to new data and recomputes the per-node outcome summaries and error metrics on that new sample - the multi-target analogue of predict():

test_result <- MTTest(tree, features, outcomes, outcome_defs, mutate_example)

Visualizing the tree

PlotTree(tree)

Tuning hyperparameters

CV_Tune() performs a grid search with k-fold cross-validation over depth, nodesize, evalmethod, and related tuning parameters. Because it fits one MuTATE tree per fold per grid point, even a small grid can take a while - the example below uses a minimal single-point grid purely to illustrate the call:

cv_results <- CV_Tune(
  features, outcomes, outcome_defs, mutate_example,
  kfolds = 2, Y = "response",
  drange = 2, noderange = 30, splitmin_div = 2,
  method = "avgIG", alpharange = 0.05, igrange = 0.95,
  psplitrange = 1, pdepthrange = 1, cp_val = 0
)

For a real tuning run, widen drange, noderange, and method to search a meaningful range - see ?CV_Tune for the full parameter list.