🌲 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.
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_testto 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
targetwas 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)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| prettyforest-0.3.0.tar.gz | 222.0 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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.