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: HTML string if no output_path and not in a notebook; None otherwise.

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 depth, nodes, purity/magnitude
Click a tree Spotlight panel with full stats + rank
Double-click a tree Full 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
? Model description + 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 Integration

import marimo as mo
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)})
html = prettygrow(model, data=X_pl, target=iris.target)
mo.iframe(html, height="700px")

Use mo.iframe() Full interactivity works: zoom, pan, click, trace, expand.

Jupyter Integration

from prettyforest import prettygrow
# ... train model ...
prettygrow(model, data=X_test)  # auto-displays via IPython.display.HTML

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.2.0.tar.gz (215.8 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.2.0-py3-none-any.whl (42.3 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for prettyforest-0.2.0.tar.gz
Algorithm Hash digest
SHA256 9a1c4fb3775bdb626de25b3f116c221c63a383c4c4d3c0ca4730a062834e52c5
MD5 064905a67d6494004a6a4eb5353ea247
BLAKE2b-256 dfec5f8173d641206d4f3aa889659fcba73ed305b4cdf64ad7765cec805cca30

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for prettyforest-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 2437440e0e86698ff1770d250af262511016988f2b93ed6e2a8111a0fb6d984d
MD5 5fd624c53f4e8b69594ce512077b14be
BLAKE2b-256 32c3963249ce286cfbaf545b33544a2bfe55c13642c972be10906cebdaf0256a

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