Skip to main content

🌲 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

Metadata

Release files for prettyforest 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for prettyforest 0.3.0
File Size Uploaded Provenance
prettyforest-0.3.0.tar.gz 222.0 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for prettyforest 0.3.0
File Interpreter ABI Platform
prettyforest-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 267.5 kB

Release files / prettyforest-0.3.0.tar.gz

Download URL prettyforest-0.3.0.tar.gz
Size 222.0 kB
Tags Source
SHA-256 checksum
How to use checksums
2ce2c6977cff04f1d9a7cbf1973c8a4e91642670469b64ff81147cfafff99a20
BLAKE2b-256 checksum
How to use checksums
eefe2029e21a26400ef5f84d04e73e72dedbcd1089c339a9dae66806277b5320
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.8.4

Release files / prettyforest-0.3.0-py3-none-any.whl

Download URL prettyforest-0.3.0-py3-none-any.whl
Size 45.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3bebb36a1a9631a4bb9cffc2c59b4c78709b1c86a6929988c2c0d0cfd3081a25
BLAKE2b-256 checksum
How to use checksums
9d5db18a62b90d7afe1c8bfe5ff752b8e6330cbc8c24fdd45d74e8198ab3e055
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.8.4

Security for prettyforest 0.3.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page