Skip to main content

Interactive 2.5D forest visualization for tree-based ML ensembles

Project description

🌲 PrettyForest

Interactive 2.5D forest visualization for tree-based ML ensembles. Explore your model's structure, trace predictions through individual trees, and understand how the ensemble makes decisions.

PrettyForest Demo

Features

  • 2.5D isometric forest — trees rendered with depth perspective, growth animation, seasonal themes
  • All major frameworks — scikit-learn, LightGBM, CatBoost (RandomForest, GradientBoosting, single DecisionTree)
  • Prediction tracing — select a data point, see per-tree corrections/votes, get the ensemble's actual prediction
  • True label comparison — pass target=y_test to see if the model got it right
  • Interactive detail view — double-click a tree to drill into its decision structure with per-node expansion
  • Boosted tree awareness — correct display of gradient corrections vs class probabilities, with explanatory notes
  • Dark mode — toggle with 🌙 button
  • Scales to thousands — pagination, sorting, spotlighting for large ensembles

Installation

pip install prettyforest

# With optional frameworks
pip install prettyforest[all]  # includes lightgbm + catboost

Quick Start

from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
import polars as pl
from prettyforest import prettygrow

# Train
iris = load_iris()
X_train, X_test, y_train, y_test = train_test_split(
    iris.data, iris.target, test_size=0.3, random_state=42
)
model = RandomForestClassifier(n_estimators=20, max_depth=5, random_state=42)
model.fit(X_train, y_train)

# Visualize
X_test_pl = pl.DataFrame({name: X_test[:, i] for i, name in enumerate(iris.feature_names)})
prettygrow(model, data=X_test_pl, target=y_test, output_path="forest.html")

API

from prettyforest import prettygrow

prettygrow(
    model,              # Trained tree model (sklearn, LightGBM, or CatBoost)
    *,
    data=None,          # Polars DataFrame or NumPy array for prediction tracing
    target=None,        # True labels/values — shown alongside predictions
    output_path=None,   # Write HTML to file; if None, returns string or displays in notebook
    feature_names=None, # Column names when data is a NumPy array
    seed=42,            # Random seed for layout
    season=None,        # Color theme: "summer", "autumn", "winter", or None (metric-based)
)

Returns: Interactive AnyWidget instance when running inside a notebook environment (JupyterLab, Marimo, VS Code, Google Colab); raw HTML string if outside a notebook and no output_path; None if output_path is specified.

Supported Models

Model Type Trees Leaf values
DecisionTreeClassifier/Regressor Single tree 1 Direct predictions
RandomForestClassifier/Regressor Bagging N independent Class proportions / target means
GradientBoostingClassifier/Regressor Boosting N × classes sequential Gradient corrections
LGBMClassifier/Regressor Boosting N × classes sequential Log-odds / residuals
CatBoostClassifier/Regressor Boosting N sequential Ordered gradient corrections

Interaction Guide

Action What it does
Hover a tree Tooltip with tree index, depth, nodes, leaves, purity/magnitude, and metric rank
Double-click a tree Full interactive decision structure with per-node expand
Sort by dropdown Rearrange into grid by depth/nodes/leaves/metric
◀ ▶ Page through large ensembles (200/page)
🌙 Toggle dark mode
Description on how the ensemble works
Trace Show per-tree badges + ensemble prediction + true label
Click a truncated node Expand that subtree 3 more levels

Seasons & Themes

Switch the visual theme live in the browser or set it via Python:

prettygrow(model, season="spring")   # 🌸 Light greens + pink blossoms
prettygrow(model, season="summer")   # 🌿 Deep lush greens
prettygrow(model, season="autumn")   # 🍂 Warm oranges, reds, golds
prettygrow(model, season="winter")   # ❄️ Bare branches, blue-grey
prettygrow(model)                    # 🌳 Natural (metric-based coloring)

You can also switch seasons on the fly using the dropdown in the header — no need to re-run Python. The canopies, ground, sky, and grass patches all update instantly.

Season Canopy colors Ground Best for
🌳 Natural Green→amber by metric Soft green Analysis (purity/variance encoded in color)
🌸 Spring Light green + pink/purple Fresh green Presentations
🌿 Summer Deep forest greens Rich green Dense forests
🍂 Autumn Orange, red, gold Warm brown Warm aesthetics
❄️ Winter Bare (no canopy) Blue-grey Seeing structure clearly

Prediction Display

When you trace a sample:

  • Per-tree badges show on each tree's canopy:
    • Random Forest: the class vote (colored by class)
    • Boosted models: the raw correction value (green = positive, red = negative)
  • Ensemble prediction: the actual model.predict() output — always correct
  • True label: shown in green if target was provided

Marimo & Jupyter Notebook Integration

PrettyForest uses native AnyWidget support for bi-directional synchronization and interactive rendering across all notebook environments (JupyterLab, Marimo desktop/cloud, VS Code, and Google Colab).

from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_iris
import polars as pl
from prettyforest import prettygrow

iris = load_iris()
model = RandomForestClassifier(n_estimators=20, max_depth=5, random_state=42)
model.fit(iris.data, iris.target)

X_pl = pl.DataFrame({name: iris.data[:, i] for i, name in enumerate(iris.feature_names)})

# Directly returns and displays an interactive AnyWidget inside Jupyter or Marimo
prettygrow(model, data=X_pl, target=iris.target)

Understanding Boosted vs Bagged Trees

Random Forest (bagged): Each tree trains independently on a random data subset. Leaves contain real class proportions or target means. Ensemble averages/votes.

Gradient Boosting / LightGBM / CatBoost (boosted): Trees train sequentially — each corrects the previous ensemble's errors. Leaf values are small gradient adjustments, not standalone predictions. Ensemble sums corrections.

When you double-click a boosted tree, a warning appears:

⚠️ This is a boosted tree — leaf values are gradient corrections, not final predictions.

The splits and features are fully interpretable — they show which features matter and how the space is partitioned. The leaf values just represent "how much to adjust" rather than "what to predict."

Development

git clone https://github.com/fabioscantamburlo/prettyforest.git
cd prettyforest
uv sync

# Run tests
uv run pytest

# Run experiments (all models)
uv run run_experiments.py --n-samples 1000 --n-trees 50 --max-depth 8

# Iris example (quick, all frameworks)
uv run examples/iris_forest.py

# MNIST example (larger, RF only)
uv run examples/mnist_forest.py

Pre-commit

pre-commit install  # set up hooks
pre-commit run --all-files  # manual run

Hooks: trailing whitespace, end-of-file, YAML check, large file guard (500KB), ruff lint + format.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

prettyforest-0.3.0.tar.gz (222.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

prettyforest-0.3.0-py3-none-any.whl (45.6 kB view details)

Uploaded Python 3

File details

Details for the file prettyforest-0.3.0.tar.gz.

File metadata

  • Download URL: prettyforest-0.3.0.tar.gz
  • Upload date:
  • Size: 222.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.8.4

File hashes

Hashes for prettyforest-0.3.0.tar.gz
Algorithm Hash digest
SHA256 2ce2c6977cff04f1d9a7cbf1973c8a4e91642670469b64ff81147cfafff99a20
MD5 322cbc7e7d45f9120a9e47a0f1112ccc
BLAKE2b-256 eefe2029e21a26400ef5f84d04e73e72dedbcd1089c339a9dae66806277b5320

See more details on using hashes here.

File details

Details for the file prettyforest-0.3.0-py3-none-any.whl.

File metadata

File hashes

Hashes for prettyforest-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 3bebb36a1a9631a4bb9cffc2c59b4c78709b1c86a6929988c2c0d0cfd3081a25
MD5 6802b9d013c7f2ed81be0359c622d4cf
BLAKE2b-256 9d5db18a62b90d7afe1c8bfe5ff752b8e6330cbc8c24fdd45d74e8198ab3e055

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page