Skip to content

Commit ce273a0

Browse files
feat(altair): implement residual-plot (#2344)
## Implementation: `residual-plot` - altair Implements the **altair** version of `residual-plot`. **File:** `plots/residual-plot/implementations/altair.py` --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20528204950)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 922f02d commit ce273a0

2 files changed

Lines changed: 118 additions & 0 deletions

File tree

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
""" pyplots.ai
2+
residual-plot: Residual Plot
3+
Library: altair 6.0.0 | Python 3.13.11
4+
Quality: 91/100 | Created: 2025-12-26
5+
"""
6+
7+
import altair as alt
8+
import numpy as np
9+
import pandas as pd
10+
11+
12+
# Data: Simulate a linear regression scenario with some non-linearity
13+
np.random.seed(42)
14+
n = 150
15+
16+
# Generate realistic housing price prediction scenario
17+
x = np.linspace(1000, 3000, n) # House size in sq ft
18+
noise = np.random.randn(n) * 15000
19+
y_true = 50000 + 150 * x + 0.02 * (x - 2000) ** 2 + noise # True prices with slight curvature
20+
y_pred = 50000 + 155 * x # Linear model predictions
21+
22+
residuals = y_true - y_pred
23+
std_residual = np.std(residuals)
24+
25+
# Identify outliers (beyond ±2 standard deviations)
26+
is_outlier = np.abs(residuals) > 2 * std_residual
27+
28+
# Create DataFrame
29+
df = pd.DataFrame(
30+
{
31+
"Fitted Values ($)": y_pred,
32+
"Residuals ($)": residuals,
33+
"Outlier": np.where(is_outlier, "Outlier (>2σ)", "Normal"),
34+
}
35+
)
36+
37+
# Base scatter plot with color encoding for outliers
38+
scatter = (
39+
alt.Chart(df)
40+
.mark_point(size=120, opacity=0.7)
41+
.encode(
42+
x=alt.X("Fitted Values ($):Q", title="Fitted Values ($)", scale=alt.Scale(nice=True)),
43+
y=alt.Y("Residuals ($):Q", title="Residuals ($)", scale=alt.Scale(nice=True)),
44+
color=alt.Color(
45+
"Outlier:N",
46+
scale=alt.Scale(domain=["Normal", "Outlier (>2σ)"], range=["#306998", "#FFD43B"]),
47+
legend=alt.Legend(title="Point Type", titleFontSize=18, labelFontSize=16),
48+
),
49+
tooltip=["Fitted Values ($):Q", "Residuals ($):Q", "Outlier:N"],
50+
)
51+
)
52+
53+
# Zero reference line
54+
zero_line = (
55+
alt.Chart(pd.DataFrame({"y": [0]})).mark_rule(color="#333333", strokeWidth=2, strokeDash=[8, 4]).encode(y="y:Q")
56+
)
57+
58+
# ±2 standard deviation bands
59+
bands_df = pd.DataFrame({"y": [2 * std_residual, -2 * std_residual], "label": ["+2σ", "-2σ"]})
60+
61+
band_lines = alt.Chart(bands_df).mark_rule(color="#888888", strokeWidth=1.5, strokeDash=[4, 4]).encode(y="y:Q")
62+
63+
# Add LOWESS-like trend using polynomial regression
64+
loess_df = df.copy()
65+
loess_df = loess_df.sort_values("Fitted Values ($)")
66+
67+
loess_line = (
68+
alt.Chart(loess_df)
69+
.transform_loess("Fitted Values ($)", "Residuals ($)", bandwidth=0.3)
70+
.mark_line(color="#E24A33", strokeWidth=3)
71+
.encode(x="Fitted Values ($):Q", y="Residuals ($):Q")
72+
)
73+
74+
# Combine all layers
75+
chart = (
76+
alt.layer(zero_line, band_lines, scatter, loess_line)
77+
.properties(
78+
width=1600,
79+
height=900,
80+
title=alt.Title(text="residual-plot · altair · pyplots.ai", fontSize=28, anchor="middle"),
81+
)
82+
.configure_axis(labelFontSize=18, titleFontSize=22, gridOpacity=0.3)
83+
.configure_view(strokeWidth=0)
84+
.configure_legend(orient="right", padding=10)
85+
)
86+
87+
# Save outputs
88+
chart.save("plot.png", scale_factor=3.0)
89+
chart.save("plot.html")
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
library: altair
2+
specification_id: residual-plot
3+
created: '2025-12-26T19:35:59Z'
4+
updated: '2025-12-26T19:41:11Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20528204950
7+
issue: 0
8+
python_version: 3.13.11
9+
library_version: 6.0.0
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/residual-plot/altair/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/residual-plot/altair/plot_thumb.png
12+
preview_html: https://storage.googleapis.com/pyplots-images/plots/residual-plot/altair/plot.html
13+
quality_score: 91
14+
review:
15+
strengths:
16+
- Excellent implementation of all specification requirements including zero reference
17+
line, LOESS smoothing, ±2σ bands, and outlier highlighting
18+
- Clear demonstration of non-linear residual pattern through LOESS line - educationally
19+
valuable
20+
- Strong colorblind-safe palette with Python logo colors (blue/yellow)
21+
- Good use of Altair declarative layering for combining multiple visual elements
22+
- Realistic housing price scenario makes the plot immediately comprehensible
23+
- Proper title format and descriptive axis labels with units
24+
weaknesses:
25+
- Legend placement is slightly isolated from the main chart area - could be positioned
26+
closer
27+
- Points use outlined circles which can be slightly less visible than filled circles
28+
at smaller sizes
29+
- The grid lines behind the ±2σ dashed lines create slight visual noise

0 commit comments

Comments
 (0)