Individual Conditional Expectation (ICE) Plot in plotnine (Python)

The same plot in 14 other libraries — Python: Altair, Bokeh, lets-plot, Matplotlib, Plotly, Pygal, Seaborn; R: ggplot2; Julia: Makie.jl; JavaScript: Chart.js, D3.js, Apache ECharts, Highcharts, MUI X Charts. Compare all 15 side by side: Individual Conditional Expectation (ICE) Plot in Python, R, Julia and JavaScript.

An Individual Conditional Expectation (ICE) plot visualizes how the predicted outcome of a machine learning model changes for each individual observation as a single feature varies across its range. Unlike partial dependence plots (PDP) that show the average marginal effect, ICE plots display one line per observation, revealing heterogeneous effects, feature interactions, and subgroup-specific behaviors that would be hidden by averaging. This makes ICE plots essential for detecting when a feature's effect varies across the population.

Individual Conditional Expectation (ICE) Plot rendered with plotnine

Renders

Python source (plotnine)

""" anyplot.ai
ice-basic: Individual Conditional Expectation (ICE) Plot
Library: plotnine 0.15.8 | Python 3.13.15
Quality: 92/100 | Updated: 2026-08-17
"""

import os
import sys


# Prevent this script from shadowing the plotnine package
_here = os.path.dirname(os.path.abspath(__file__))
sys.path = [p for p in sys.path if os.path.abspath(p if p else ".") != _here]

import numpy as np
import pandas as pd
from plotnine import (
    aes,
    annotate,
    element_blank,
    element_line,
    element_rect,
    element_text,
    geom_line,
    geom_rug,
    geom_vline,
    ggplot,
    labs,
    scale_x_continuous,
    theme,
)
from sklearn.ensemble import GradientBoostingRegressor


# Theme tokens
THEME = os.getenv("ANYPLOT_THEME", "light")
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"

# Imprint palette — first series is always #009E73
IMPRINT_PALETTE = ["#009E73", "#C475FD", "#4467A3", "#BD8233", "#AE3030", "#2ABCCD", "#954477", "#99B314"]
BRAND = IMPRINT_PALETTE[0]
PDP_COLOR = IMPRINT_PALETTE[1]

# Data — synthetic housing dataset
np.random.seed(42)
n_obs = 120

area = np.random.uniform(600, 4000, n_obs)
bedrooms = np.random.randint(1, 6, n_obs).astype(float)
age_years = np.random.uniform(1, 50, n_obs)
location = np.random.uniform(1, 10, n_obs)

X = np.column_stack([area, bedrooms, age_years, location])
y = area * 120 + bedrooms * 25000 - age_years * 800 + location * 40000 + np.random.normal(0, 25000, n_obs)

model = GradientBoostingRegressor(n_estimators=150, max_depth=3, random_state=42)
model.fit(X, y)

# ICE curves: vary area across a grid, hold all other features at observed values
n_grid = 60
area_grid = np.linspace(area.min(), area.max(), n_grid)

ice_rows = []
for obs_id in range(n_obs):
    X_grid = np.tile(X[obs_id], (n_grid, 1))
    X_grid[:, 0] = area_grid
    preds = model.predict(X_grid)
    for feat_val, pred in zip(area_grid, preds, strict=False):
        ice_rows.append({"observation_id": str(obs_id), "feature_value": feat_val, "prediction": pred / 1000})

ice_df = pd.DataFrame(ice_rows)

# PDP: average prediction across all observations at each grid point
pdp_df = ice_df.groupby("feature_value")["prediction"].mean().reset_index()

# Rug: observed area values
rug_df = pd.DataFrame({"feature_value": area})

# Annotation anchor: PDP value at ~75% of area range (past the step discontinuity)
ann_idx = int(n_grid * 0.75)
ann_x = float(area_grid[ann_idx])
ann_y = float(pdp_df.iloc[ann_idx]["prediction"]) + 25

# Largest single-step jump in the PDP curve — marks the tree-split boundary the model learned
jump_idx = int(pdp_df["prediction"].diff().abs().idxmax())
split_x = float(pdp_df.loc[jump_idx, "feature_value"])
split_label_y = float(pdp_df["prediction"].max()) + 20

# Plot
anyplot_theme = theme(
    figure_size=(8, 4.5),
    plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),
    panel_background=element_rect(fill=PAGE_BG),
    panel_grid_major_y=element_line(color=INK, size=0.3, alpha=0.12),
    panel_grid_major_x=element_blank(),
    panel_grid_minor=element_blank(),
    panel_border=element_blank(),
    axis_title=element_text(color=INK, size=10),
    axis_text=element_text(color=INK_SOFT, size=8),
    axis_line=element_line(color=INK_SOFT),
    plot_title=element_text(color=INK, size=12, face="bold"),
    legend_position="none",
    plot_margin=0.05,
)

plot = (
    ggplot(ice_df, aes(x="feature_value", y="prediction", group="observation_id"))
    + geom_line(alpha=0.12, color=BRAND, size=0.5)
    + geom_vline(xintercept=split_x, color=INK_SOFT, alpha=0.5, linetype="dashed", size=0.4)
    + geom_line(
        data=pdp_df, mapping=aes(x="feature_value", y="prediction"), color=PDP_COLOR, size=2.5, inherit_aes=False
    )
    + geom_rug(data=rug_df, mapping=aes(x="feature_value"), color=INK_SOFT, alpha=0.4, sides="b", inherit_aes=False)
    + annotate("text", x=ann_x, y=ann_y, label="PDP (avg effect)", color=PDP_COLOR, size=4.0, fontweight="bold")
    + annotate(
        "text",
        x=split_x,
        y=split_label_y,
        label=f"model split ~{split_x:.0f} sq ft",
        color=INK_SOFT,
        size=3.2,
        fontstyle="italic",
        ha="center",
    )
    + scale_x_continuous(breaks=[1000, 1500, 2000, 2500, 3000, 3500])
    + labs(x="House Area (sq ft)", y="Predicted Price ($000s)", title="ice-basic · plotnine · anyplot.ai")
    + anyplot_theme
)

# Save
plot.save(f"plot-{THEME}.png", dpi=400, width=8, height=4.5, units="in")

Retrieve this implementation

Runnable source as JSON, for any HTTP client: https://api.anyplot.ai/specs/ice-basic/plotnine/code. Any spec id and library id listed in llms-full.txt fit the same URL shape; every URL below is complete and callable.

{
  "spec_id": "ice-basic",
  "language": "python",
  "library": "plotnine",
  "page": "https://anyplot.ai/ice-basic/python/plotnine",
  "hub": "https://anyplot.ai/ice-basic",
  "code_json": "https://api.anyplot.ai/specs/ice-basic/plotnine/code",
  "spec_json": "https://api.anyplot.ai/specs/ice-basic",
  "render_light_png": "https://storage.googleapis.com/anyplot-images/plots/ice-basic/python/plotnine/plot-light.png",
  "render_dark_png": "https://storage.googleapis.com/anyplot-images/plots/ice-basic/python/plotnine/plot-dark.png",
  "quality_score": 92.0,
  "license": "MIT",
  "guide": "https://anyplot.ai/llms.txt"
}

Part of Individual Conditional Expectation (ICE) Plot on anyplot.ai.

Other implementations