The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.
ppforest2 trains trees by composing six pluggable strategies:
| Strategy | Purpose | Built-in |
|---|---|---|
| PP (projection pursuit) | Find the projection that best separates groups | pp_pda() — Penalized Discriminant Analysis |
| Vars (variable selection) | Select which variables are available at each split | vars_uniform(), vars_all() |
| Threshold (split cutpoint) | Compute the split cutpoint in projected space | cutpoint_mean_of_means() |
| Stop (stopping rule) | Decide when to stop growing | stop_pure_node() |
| Binarize (binarization) | Reduce multiclass to binary at each node | binarize_largest_gap() |
| Grouping (group partition) | Route observations to children | grouping_by_label() |
You can add new strategies without modifying the core tree-building logic. This vignette walks through the process.
Each strategy is an R list with a name field that
identifies it, a display_name for summaries, and any
parameters the strategy needs. The name must match a C++
strategy registered under the same name.
library(ppforest2)
pp_pda(0.5)
#> $name
#> [1] "pda"
#>
#> $display_name
#> [1] "PDA"
#>
#> $lambda
#> [1] 0.5
#>
#> attr(,"class")
#> [1] "pp_strategy"
vars_uniform(n_vars = 2)
#> $name
#> [1] "uniform"
#>
#> $display_name
#> [1] "Uniform random"
#>
#> $count
#> [1] 2
#>
#> $p_vars
#> NULL
#>
#> attr(,"class")
#> [1] "vars_strategy"
cutpoint_mean_of_means()
#> $name
#> [1] "mean_of_means"
#>
#> $display_name
#> [1] "Mean of means"
#>
#> attr(,"class")
#> [1] "cutpoint_strategy"When you call pptr() or pprf(), the
strategy lists are passed to C++, where the name field
dispatches to the corresponding C++ implementation. The actual
computation (optimization, variable selection, cutpoint) happens
entirely in C++.
Adding a strategy requires work on both sides:
Each strategy family has a base class with pure virtual methods. Your new strategy inherits from the appropriate base and implements them.
For example, a new projection pursuit strategy needs to implement
optimize() (find the best projection):
// File: core/src/models/strategies/pp/MyMethod.hpp
#pragma once
#include "models/strategies/pp/ProjectionPursuit.hpp"
#include "models/strategies/Strategy.hpp"
#include "utils/JsonValidation.hpp"
namespace ppforest2::pp {
struct MyMethod : public ProjectionPursuit {
explicit MyMethod(float alpha) : alpha_(alpha) {}
std::string display_name() const override { return "My method"; }
Result optimize(
const types::FeatureMatrix& x,
const stats::GroupPartition& group_spec) const override {
// Find the optimal projector for the data.
// Return Result{ projector_vector, index_value }.
...
}
nlohmann::json to_json() const override {
return {{"name", "my_method"}, {"alpha", alpha_}};
}
static ProjectionPursuit::Ptr from_json(const nlohmann::json& j) {
JsonReader{j, "my_method"}.only_keys({"name", "alpha"});
return my_method(j.at("alpha").get<float>());
}
PPFOREST2_REGISTER_STRATEGY(ProjectionPursuit, "my_method")
private:
const float alpha_;
};
inline ProjectionPursuit::Ptr my_method(float alpha) {
return std::make_shared<MyMethod>(alpha);
}
} // namespace ppforest2::ppThe key pieces:
to_json() serializes the strategy name
and parameters. This is used for model persistence.from_json() deserializes from JSON and
validates that no unexpected keys are present.PPFOREST2_REGISTER_STRATEGY registers
the factory so JSON deserialization finds it automatically.display_name() returns a
human-readable label for summaries.my_method()) is a
convenience wrapper.The same pattern applies to variable selection strategies
(select()), cutpoint strategies (cutpoint()),
and the other strategy families. See the C++ documentation for complete
interface definitions and examples.
After writing the .cpp file, add it to
core/src/models/CMakeLists.txt.
Write an R function that creates a strategy list. The
name field must match the C++ registration name
exactly.
#' My custom projection pursuit strategy.
#'
#' @param alpha A tuning parameter.
#' @return A \code{pp_strategy} object.
#' @export
pp_my_method <- function(alpha = 1.0) {
if (!is.numeric(alpha) || length(alpha) != 1)
stop("`alpha` must be a single number.")
structure(
list(name = "my_method", display_name = "My method", alpha = alpha),
class = "pp_strategy"
)
}The constructor should:
pp_strategy,
vars_strategy, cutpoint_strategy,
stop_strategy, binarize_strategy, or
grouping_strategy. This is checked by
resolve_strategies().display_name for readable
output in summary().to_json() in C++. The R list is converted to JSON and
passed to from_json() on the C++ side.Once both sides are in place, the new strategy works like any built-in:
# Single tree
tree <- pptr(Species ~ ., data = iris, pp = pp_my_method(alpha = 0.5))
# Forest
forest <- pprf(Species ~ ., data = iris, pp = pp_my_method(alpha = 0.5), vars = vars_uniform(n_vars = 2))
# Summary shows the strategy
summary(tree)The strategy is also available from the CLI:
And models trained with the new strategy can be saved and loaded as usual — the JSON registry handles serialization automatically.
Controls how the tree finds the best linear combination of variables at each node.
index(x, group_spec, projector) -> scalar
optimize(x, group_spec) -> Result{projector, index}
optimize() is the main method. It receives the data
matrix and group partition and returns the best projection vector.
index() evaluates a given projection (used for variable
importance calculations).
Controls which variables are available to projection pursuit at each split. This is what makes random forests “random”.
select(x, group_spec, rng) -> Result{selected_indices, original_cols}
The returned VariableSelection::Result tracks which
columns were selected so the reduced-space projector can be expanded
back to the full feature space.
Controls where the split cutpoint is placed in the projected space.
cutpoint(group_1, group_2, projector) -> scalar
Receives the two groups (already partitioned by projection pursuit) and the projection vector. Returns the cutpoint value.
Controls when to stop growing the tree.
should_stop(group_partition, depth) -> bool
Controls how multiclass nodes (>2 groups) are reduced to a binary problem.
regroup(projected_x, group_partition) -> Result
Controls how observations are routed to children after a split.
split(partition, lower_group, upper_group) -> Result
core/src/models/strategies/<family>/MyStrategy.hpp
(and .cpp if needed).ProjectionPursuit, VariableSelection,
Cutpoint, StopRule, Binarization,
or Grouping).to_json() with a "name"
field.display_name() for human-readable
summaries.static Ptr from_json() with key validation.PPFOREST2_REGISTER_STRATEGY(Base, "name")..cpp to
core/src/models/CMakeLists.txt.MyStrategy.test.cpp (JSON round-trip +
functional).display_name, and the correct S3 class.These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.