Skip to contents

Reports, for every predictor, how many splitting rules the forest spends on it and how often it is used at all, which is the meaning of "variable importance" for a BART model. This is the table summary.bartisan_fit() prints, returned as a data frame rather than displayed (i.e., ready to sort, filter, or plot).

Usage

variable_importance(object, level = 0.95, draws = FALSE)

# S3 method for class 'bartisan_importance'
plot(x, y, ...)

Arguments

object

a <bartisan_fit> object; the output of a call to bartisan().

level

numeric; the width of the interval reported for splits. Default is .95 for a 95% interval.

draws

logical; whether to return the splitting counts of every posterior draw rather than a summary of them, for a comparison the summary does not offer. Default is FALSE.

x

a <bartisan_importance> object; the output of a call to variable_importance().

y

not used.

...

not used.

Value

A <bartisan_importance> object, which is a data frame with one row per predictor and its own print() and plot() methods, sorted by prop_used and then splits, both decreasing, and with the following columns:

  • variable: the predictor, under the name the formula gave it

  • prop_used: the proportion of draws in which it received at least one rule

  • prop_splits: its share of all the splitting rules in the forest, averaged over draws

  • splits: the mean number of splitting rules per draw that use it

  • splits_lower and splits_upper: the endpoints of the level interval on that number, which the print() method leaves out of the displayed table

A family with more than one additive predictor has a forest for each, and the data frame then gains a leading predictor column naming which.

With draws = TRUE, the draws-by-predictors matrix of counts instead, or a named list of them when there is more than one forest.

plot() on the result draws splits and its interval for each predictor and returns a ggplot2 object. The result can be subset first, so plot(vi[vi$prop_used > .5, ]) draws only the predictors that are used.

Details

Reading the Columns

What prop_used means depends on the splitting prior. Under the default (sparsity = FALSE in bartisan_control()), every predictor keeps a share of the rules and prop_used sits near 1 throughout, which the print() method notes; the ranking is then carried by the other two columns. With sparsity = TRUE, it behaves like a posterior probability that the predictor belongs in the model, separates signal from noise sharply, and is the column to read first.

prop_splits is the one to reach for when two fits are being compared. The share is computed within each draw before averaging, so the shares add to one whatever the forest size, where splits counts rules and so scales with num_trees. splits itself, the mean number of rules per draw, says how much of the forest's structure a predictor accounts for, and is the easier of the three to over-read.

Limits of a Usage Ranking

Usage is not effect size: a predictor can be split on constantly and move the prediction very little. Where the question is how much a predictor moves the outcome, marginaleffects::avg_comparisons() on the fitted model answers it; see bartisan-marginaleffects. Where two predictors carry the same information the trees split on whichever is convenient and the usage distributes between them arbitrarily, so a group of correlated predictors is best read as a group. And a ranking by usage describes this fitted function rather than what would happen if a predictor were changed.

Reading It as Variable Selection

When the question is which predictors the model needs, the fit should be made with sparsity = TRUE. prop_used is then usable as a selection rule: the predictors the forest genuinely needs sit near 1 and the rest fall near 0, usually with a wide gap rather than a continuum, and cutting that gap at .5 gives the median probability model. No threshold is correct in general, so the gap is the thing to look at, and a conclusion worth reporting will not depend on where in it the cut is made. A forest asked to fit noise still puts its rules somewhere, so the cut chooses a predictive submodel rather than testing one. vignette("importance") calibrates it against noise predictors and works through correlated ones.

See also

summary.bartisan_fit(), which prints the same table; bartisan_control() for sparsity; bartisan-marginaleffects for effects rather than usage

Examples

data("rhc")
set.seed(123)

model <- death ~ rhc + age + sex + race + edu + aps +
  meanbp + resp + hema + pafi + paco2 + crea + surv2m +
  card

# The sparsity prior concentrates the splitting rules on
# the predictors that earn them, which makes `prop_used`
# readable as a selection rule
fit <- bartisan(model, data = rhc, num_trees = 10,
                num_burn = 50, num_draws = 50,
                sparsity = TRUE, verbose = FALSE)
#> ℹ Using `family = binomial()`.
#> ℹ Set `family` explicitly to silence this message.

imp <- variable_importance(fit)
imp
#> Variable importance
#> 
#>  variable prop_used prop_splits splits
#>      pafi      1.00       0.163    2.8
#>       aps      1.00       0.151    2.6
#>    surv2m      1.00       0.112    1.9
#>       age      1.00       0.091    1.6
#>     paco2      1.00       0.090    1.5
#>       rhc      0.84       0.099    1.7
#>    meanbp      0.84       0.087    1.5
#>       edu      0.44       0.039    0.7
#>      card      0.42       0.029    0.5
#>      hema      0.40       0.023    0.4
#>      race      0.38       0.032    0.5
#>      resp      0.36       0.044    0.7
#>      crea      0.28       0.020    0.4
#>       sex      0.24       0.019    0.3
#> 
#> ℹ splits_lower and splits_upper hold the 95% interval, not shown above.

# The predictors the forest reaches for in nearly every
# draw
subset(imp, prop_used > .9)
#> Variable importance
#> 
#>  variable prop_used prop_splits splits
#>      pafi         1       0.163    2.8
#>       aps         1       0.151    2.6
#>    surv2m         1       0.112    1.9
#>       age         1       0.091    1.6
#>     paco2         1       0.090    1.5
#> 

# `prop_splits` is the column that survives a change of
# forest size, since the shares add to one however many
# rules there are to share
big <- bartisan(model, data = rhc, num_trees = 40,
                num_burn = 50, num_draws = 50,
                sparsity = TRUE, verbose = FALSE)
#> ℹ Using `family = binomial()`.
#> ℹ Set `family` explicitly to silence this message.

shares <- c("variable", "prop_splits")
merge(variable_importance(fit)[shares],
      variable_importance(big)[shares],
      by = "variable", suffixes = c("_10", "_40"))
#>    variable prop_splits_10 prop_splits_40
#> 1       age     0.09145554    0.081909718
#> 2       aps     0.15094173    0.150314800
#> 3      card     0.02948447    0.039288290
#> 4      crea     0.02047668    0.088576493
#> 5       edu     0.03915304    0.067878559
#> 6      hema     0.02317787    0.015440428
#> 7    meanbp     0.08678033    0.009164937
#> 8     paco2     0.08975803    0.066894390
#> 9      pafi     0.16322893    0.053418438
#> 10     race     0.03218804    0.012447264
#> 11     resp     0.04446252    0.053555629
#> 12      rhc     0.09870784    0.067816662
#> 13      sex     0.01862418    0.048068330
#> 14   surv2m     0.11156079    0.245226062

# The counts themselves, for a comparison the summary
# does not make
counts <- variable_importance(fit, draws = TRUE)
mean(counts[, "aps"] > counts[, "meanbp"])
#> [1] 0.56

# The ranking, drawn. Subsetting first keeps a wide
# model readable.
plot(head(imp, 8))