--- title: "Introduction to ppforest2" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Introduction to ppforest2} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include = FALSE} # Cap OpenMP to 2 threads so forest training in this vignette never uses more # than two cores when the vignette is rebuilt under R CMD check (CRAN policy). Sys.setenv(OMP_NUM_THREADS = "2") knitr::opts_chunk$set( collapse = TRUE, comment = "#>", fig.width = 7, fig.height = 5 ) ``` ppforest2 builds oblique decision trees and random forests using projection pursuit. Instead of splitting on a single variable at each node, it finds a linear combination of variables that best separates the groups. ## Single tree Train a projection-pursuit tree on the iris dataset: ```{r tree} library(ppforest2) tree <- pptr(Species ~ ., data = iris, seed = 0) tree ``` The tree splits on linear projections of the features. Use `summary()` to see variable importance: ```{r tree-summary} summary(tree) ``` Predict new observations: ```{r tree-predict} preds <- predict(tree, iris) table(predicted = preds, actual = iris$Species) ``` ## Random forest Train a forest of 100 trees, each considering 2 randomly chosen variables at each split: ```{r forest} forest <- pprf(Species ~ ., data = iris, size = 100, n_vars = 2, seed = 0) summary(forest) ``` The summary shows the OOB (out-of-bag) error estimate and three variable importance measures: - **Projection**: average absolute projection coefficient across all splits - **Weighted**: projection importance weighted by the number of observations routed through each node - **Permuted**: decrease in OOB accuracy when each variable is permuted Predict with class probabilities: ```{r forest-predict} probs <- predict(forest, iris[1:5, ], type = "prob") probs ``` ## Visualization ppforest2 provides several plot types via `plot()` (requires ggplot2): ```{r plot-structure, eval = requireNamespace("ggplot2", quietly = TRUE)} # Tree structure with projected data histograms at each node plot(tree, type = "structure") ``` ```{r plot-importance, eval = requireNamespace("ggplot2", quietly = TRUE)} # Variable importance bar chart plot(tree, type = "importance") ``` ```{r plot-projection, eval = requireNamespace("ggplot2", quietly = TRUE)} # Data projected onto the first split's projection vector plot(tree, type = "projection") ``` ```{r plot-boundaries, eval = requireNamespace("ggplot2", quietly = TRUE)} # Decision boundaries for two selected variables plot(tree, type = "boundaries") ``` For forests, variable importance is the only global visualization available. Individual trees can be plotted by specifying a `tree_index`. ```{r plot-forest, eval = requireNamespace("ggplot2", quietly = TRUE)} plot(forest) ``` ```{r plot-forest-tree, eval = requireNamespace("ggplot2", quietly = TRUE)} plot(forest, type = "structure", tree_index = 1) ``` ## Regularization with PDA Set `lambda > 0` to use Penalized Discriminant Analysis instead of LDA. This can help when features are highly correlated: ```{r pda} tree_pda <- pptr(Species ~ ., data = iris, lambda = 0.5, seed = 0) summary(tree_pda) ``` ## Explicit strategy selection For more control, you can pass strategy objects directly instead of using the shortcut parameters (`lambda`, `n_vars`, `p_vars`). This is equivalent but makes the strategy choice explicit: ```{r strategies} # These two calls produce identical results: forest_shortcut <- pprf(Species ~ ., data = iris, size = 10, lambda = 0.5, n_vars = 2, seed = 0) forest_explicit <- pprf(Species ~ ., data = iris, size = 10, pp = pp_pda(0.5), vars = vars_uniform(n_vars = 2), seed = 0) all.equal(predict(forest_shortcut, iris), predict(forest_explicit, iris)) ``` Available strategy constructors: - `pp_pda(lambda)` — PDA projection pursuit (`lambda = 0` for LDA) - `vars_uniform(n_vars)` or `vars_uniform(p_vars)` — random variable selection - `vars_all()` — use all variables (default for single trees) - `cutpoint_mean_of_means()` — midpoint split rule (default) Strategy objects can also be passed as engine arguments in parsnip: ```{r strategies-parsnip, eval = requireNamespace("parsnip", quietly = TRUE)} library(parsnip) spec <- pp_rand_forest(trees = 10) |> set_engine("ppforest2", pp = pp_pda(0.5), vars = vars_uniform(n_vars = 2)) |> set_mode("classification") fit <- spec |> fit(Species ~ ., data = iris) predict(fit, iris[1:5, ]) ``` ## Tidymodels integration ppforest2 integrates with the tidymodels ecosystem via parsnip: ```{r parsnip, eval = requireNamespace("parsnip", quietly = TRUE)} library(parsnip) # Single tree spec <- pp_tree(penalty = 0.5) |> set_engine("ppforest2") |> set_mode("classification") fit <- spec |> fit(Species ~ ., data = iris) predict(fit, iris[1:5, ]) ``` ```{r parsnip-forest, eval = requireNamespace("parsnip", quietly = TRUE)} # Random forest spec <- pp_rand_forest(trees = 50, mtry = 2, penalty = 0.5) |> set_engine("ppforest2") |> set_mode("classification") fit <- spec |> fit(Species ~ ., data = iris) predict(fit, iris[1:5, ], type = "prob") ```