Skip to content

Tree family plotting - #69

Open
jemus42 wants to merge 4 commits into
masterfrom
tree-plot
Open

jemus42 wants to merge 4 commits into
masterfrom
tree-plot

Conversation

@jemus42

@jemus42 jemus42 commented Oct 8, 2026

Copy link
Copy Markdown
Collaborator

Resolves #51 if it works out ok, vibecoded example below

# pak::pak("PlantedML/randomPlantedForest@tree-plot")
library(randomPlantedForest)

A toy example

y has a main effect of x1 and an interaction of x2 and x3, x4 is noise. A single tree family with a few splits is small enough to read as a diagram.

set.seed(1)
n <- 500
toy <- data.frame(x1 = runif(n), x2 = runif(n), x3 = runif(n), x4 = runif(n))
toy$y <- 2 * toy$x1 + 4 * (toy$x2 > 0.5) * toy$x3 + rnorm(n, sd = 0.2)

fit_toy <- rpf(y ~ ., data = toy, ntrees = 1, splits = 12)
plot(fit_toy)
toy-tree-1

Each tree belongs to a set of variables and the prediction is the sum of the values of all leaves containing a point. The same leaves in variable space, before and after purification:

head(rpf_boxes(fit_toy), 3)
#>          tree order box      value    x1_lower  x1_upper x2_lower x2_upper
#> 1 (Intercept)     0   1  0.0000000          NA        NA       NA       NA
#> 2          x1     1   1 -0.4789080 0.001836858 0.4935413       NA       NA
#> 3          x1     1   2  0.7489024 0.754705622 1.0160774       NA       NA
#>   x3_lower x3_upper x4_lower x4_upper
#> 1       NA       NA       NA       NA
#> 2       NA       NA       NA       NA
#> 3       NA       NA       NA       NA
plot(fit_toy, type = "boxes")
toy-boxes-1
purify(fit_toy)
plot(fit_toy, type = "boxes")
toy-boxes-2

Bike sharing

data("Bikeshare", package = "ISLR2")
bike <- Bikeshare
bike$hr <- as.numeric(as.character(bike$hr))
bike$workingday <- factor(bike$workingday, levels = 0:1, labels = c("no", "yes"))
bike$season <- factor(bike$season, levels = 1:4, labels = c("Winter", "Spring", "Summer", "Fall"))
bike <- bike[setdiff(names(bike), c("atemp", "day", "registered", "casual", "mnth"))]

A small fit as a diagram, with the root split twice on the hour of day and two interaction trees:

set.seed(1)
fit_small <- rpf(bikers ~ hr + workingday + temp, data = bike, ntrees = 1, splits = 12)
plot(fit_small)
bike-tree-1

With tuned parameters,
one tree family is enough for plotting. At the tuned max_interaction = 7, most leaves are in trees on many variables, which
plot() summarizes as a remainder:

tuned <- list(
  splits = 1000, split_try = 3, t_try = 0.0116, split_decay_rate = 0.357,
  max_candidates = 73, delete_leaves = FALSE, split_structure = "leaves"
)
set.seed(1)
fit_tuned <- do.call(rpf, c(list(bikers ~ ., data = bike, ntrees = 1, max_interaction = 7), tuned))
table(order = rpf_boxes(fit_tuned)$order)
#> order
#>   0   1   2   3   4   5   6   7 
#>   1  10  64 298 634 524 382  88

Restricting the forest to pairwise interactions costs some accuracy (test RMSE, 80/20 split, ntrees = 200):

max_interaction test RMSE
7 (tuned) 34.1
2 41.3
1 74.1
intercept only 131.7

In exchange, after purification, every tree is a component of the functional decomposition that can be read off directly:

set.seed(1)
fit_pairs <- do.call(rpf, c(list(bikers ~ ., data = bike, ntrees = 1, max_interaction = 2), tuned))
purify(fit_pairs)
plot(fit_pairs, type = "boxes", trees = c("hr", "temp", "season", "hr:workingday", "hr:temp", "hr:season"))
bike-boxes-1

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Plot tree families similar to illustrations in paper

1 participant