The tourr package generates animated sequences of linear
projections (called a grand tour) that collectively show the
structure of high-dimensional data from many angles. When combined with
classbound, you can visualize how decision boundaries of
different classifiers appear across multiple projection angles.
The key principle is to generate one shared projection sequence and apply it to all models. This ensures that when you compare boundaries side by side, each model is viewed through the exact same projection at the exact same frame.
1. Prepare data (numeric features only)
2. Fit models
3. Generate one tour path via tourr::save_history()
4. For each tour frame (projection basis):
a. Compute boundary via boundary_compute(..., projection = list(basis = frame))
b. Forward-project observations for overlay
c. Plot with plot_boundary()
5. Compare across models at the same frame
We use the data69_1 dataset: 5000 observations with 21
numeric features and 3 classes. For this example we work with a subset
of 300 observations and 5 features to keep computation fast.
All models must be trained on the same features and class levels.
Each element of tour_history is a p × 2
orthonormal basis matrix. Extract a frame, construct the projection
list, compute the boundary for each model, and plot.
if (requireNamespace("tourr", quietly = TRUE) && exists("tour_history")) {
# Use frame 3 as an example
frame_idx <- 3
basis <- matrix(tour_history[, , frame_idx], nrow = length(feat_cols), ncol = 2)
rownames(basis) <- feat_cols
proj_list <- list(basis = basis, center = center_vals, scale = scale_vals)
# Forward-project the training data to get axis ranges
x_mat <- scale(d[, feat_cols], center = center_vals, scale = scale_vals)
z_mat <- x_mat %*% basis
ranges <- list(
Proj1 = range(z_mat[, 1]) + c(-0.3, 0.3),
Proj2 = range(z_mat[, 2]) + c(-0.3, 0.3)
)
colnames(basis) <- c("Proj1", "Proj2")
proj_list$basis <- basis
# Compute boundary for rpart at this frame
b_rpart <- boundary_compute(m_rpart,
feature_range = ranges, resolution = 40,
projection = proj_list
)
plot_boundary(b_rpart,
obs_data = d, true_label = "Y",
x_col = "Proj1", y_col = "Proj2"
) +
ggplot2::ggtitle("rpart: Tour frame 3")
}Because both models are visualized using the same projection, the regions are directly comparable.
if (requireNamespace("tourr", quietly = TRUE) && exists("tour_history")) {
b_rf <- boundary_compute(m_rf,
feature_range = ranges, resolution = 40,
projection = proj_list
)
plot_boundary(b_rf,
obs_data = d, true_label = "Y",
x_col = "Proj1", y_col = "Proj2"
) +
ggplot2::ggtitle("Random Forest: Tour frame 3")
}To create an animated tour, iterate over all frames and display each
plot in sequence. In an interactive R session you can use
tourr::animate() directly with custom display functions, or
save individual frames and combine them with the animation
or gganimate packages.
# Pseudocode (adapt based on your animation workflow)
for (i in seq_len(dim(tour_history)[3])) {
basis_i <- matrix(tour_history[, , i], nrow = length(feat_cols), ncol = 2)
rownames(basis_i) <- feat_cols
colnames(basis_i) <- c("Proj1", "Proj2")
proj_i <- list(basis = basis_i, center = center_vals, scale = scale_vals)
b <- boundary_compute(m_rpart,
feature_range = ranges, resolution = 40,
projection = proj_i
)
p <- plot_boundary(b,
obs_data = d, true_label = "Y",
x_col = "Proj1", y_col = "Proj2"
)
print(p)
}rownames(basis) <- feat_cols and
colnames(basis) <- c("Proj1", "Proj2") to match the
feature order expected by boundary_compute().center and scale vectors reverse any
standardization applied before touring, ensuring the boundary grid is
mapped back to the correct feature space.