Skip to content

Commit e37d1a7

Browse files
feat(matplotlib): implement scatter-regression-linear (#10132)
## Implementation: `scatter-regression-linear` - python/matplotlib Implements the **python/matplotlib** version of `scatter-regression-linear`. **File:** `plots/scatter-regression-linear/implementations/python/matplotlib.py` **Parent Issue:** #1821 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/anyplot/actions/runs/31008270400)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent 9392c01 commit e37d1a7

2 files changed

Lines changed: 125 additions & 99 deletions

File tree

Lines changed: 50 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,26 @@
11
""" anyplot.ai
22
scatter-regression-linear: Scatter Plot with Linear Regression
3-
Library: matplotlib 3.10.9 | Python 3.13.13
4-
Quality: 93/100 | Updated: 2026-05-06
3+
Library: matplotlib 3.11.1 | Python 3.13.14
4+
Quality: 92/100 | Updated: 2026-08-05
55
"""
66

77
import os
88

99
import matplotlib.pyplot as plt
1010
import numpy as np
11+
from matplotlib.transforms import blended_transform_factory
1112

1213

13-
# Theme tokens
14+
# Theme tokens (Imprint)
1415
THEME = os.getenv("ANYPLOT_THEME", "light")
1516
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
1617
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
1718
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
1819
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
19-
BRAND = "#009E73" # Okabe-Ito position 1
20-
SECONDARY = "#C475FD" # Okabe-Ito position 2 for regression line
20+
BRAND = "#009E73" # Imprint palette position 1
21+
SECONDARY = "#C475FD" # Imprint palette position 2, for regression line
2122

22-
# Data: Study hours vs exam scores (realistic educational context)
23+
# Data: study hours vs exam scores (realistic educational context)
2324
np.random.seed(42)
2425
n_points = 80
2526
x = np.random.uniform(1, 10, n_points) # Study hours
@@ -31,12 +32,15 @@
3132
coefficients = np.polyfit(x, y, 1)
3233
slope, intercept = coefficients[0], coefficients[1]
3334

34-
# Calculate R-squared
35+
# Coefficient of determination
3536
y_pred = np.polyval(coefficients, x)
3637
ss_res = np.sum((y - y_pred) ** 2)
3738
ss_tot = np.sum((y - np.mean(y)) ** 2)
3839
r_squared = 1 - (ss_res / ss_tot)
3940

41+
# Largest residual: notable outlier worth calling out
42+
outlier_idx = np.argmax(np.abs(y - y_pred))
43+
4044
# Regression line and 95% confidence interval
4145
x_line = np.linspace(x.min() - 0.5, x.max() + 0.5, 100)
4246
y_line = np.polyval(coefficients, x_line)
@@ -45,45 +49,63 @@
4549
ss_xx = np.sum((x - x_mean) ** 2)
4650
se_y = np.sqrt(ss_res / (n_points - 2))
4751
se_line = se_y * np.sqrt(1 / n_points + (x_line - x_mean) ** 2 / ss_xx)
48-
t_val = 1.99 # 95% CI
52+
t_val = 1.99 # 95% CI, df ~ 78
4953
ci_upper = y_line + t_val * se_line
5054
ci_lower = y_line - t_val * se_line
5155

5256
# Plot
53-
fig, ax = plt.subplots(figsize=(16, 9), facecolor=PAGE_BG)
57+
fig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)
5458
ax.set_facecolor(PAGE_BG)
5559

56-
# Confidence interval band (using SECONDARY color with reduced alpha)
57-
ax.fill_between(x_line, ci_lower, ci_upper, alpha=0.2, color=SECONDARY, label="95% CI")
60+
# Confidence interval band (SECONDARY color, low alpha)
61+
ax.fill_between(x_line, ci_lower, ci_upper, alpha=0.2, color=SECONDARY, label="95% CI", zorder=1)
5862

59-
# Scatter points (BRAND green as first series)
60-
ax.scatter(x, y, s=200, alpha=0.7, color=BRAND, edgecolors=PAGE_BG, linewidth=0.5, zorder=3)
63+
# Rug plot: marginal x-distribution along the bottom, drawn in a blended
64+
# transform (data x, axes y) so it hugs the axis regardless of y-range.
65+
trans_x = blended_transform_factory(ax.transData, ax.transAxes)
66+
ax.plot(x, np.full_like(x, 0.015), "|", transform=trans_x, color=BRAND, alpha=0.5, markersize=7, zorder=1)
6167

62-
# Regression line (using SECONDARY color)
63-
ax.plot(x_line, y_line, color=SECONDARY, linewidth=3, label="Regression Line", zorder=2)
68+
# Scatter points (BRAND green as first series)
69+
ax.scatter(x, y, s=130, alpha=0.7, color=BRAND, edgecolors=PAGE_BG, linewidth=0.5, zorder=3)
70+
71+
# Regression line (SECONDARY color)
72+
ax.plot(x_line, y_line, color=SECONDARY, linewidth=2.5, label="Regression Line", zorder=2)
73+
74+
# Callout for the largest residual, showing how far the point strays from the fit
75+
ax.annotate(
76+
"Largest residual",
77+
xy=(x[outlier_idx], y[outlier_idx]),
78+
xytext=(x[outlier_idx] + 1.4, y[outlier_idx] + 10),
79+
fontsize=8,
80+
color=INK_SOFT,
81+
arrowprops={"arrowstyle": "->", "color": INK_SOFT, "linewidth": 1},
82+
zorder=4,
83+
)
6484

65-
# Annotations with theme-adaptive styling
85+
# Annotation: equation and R-squared
6686
equation = f"y = {slope:.2f}x + {intercept:.2f}"
6787
r_text = f"R² = {r_squared:.3f}"
6888
ax.text(
69-
0.05,
70-
0.95,
89+
0.04,
90+
0.94,
7191
f"{equation}\n{r_text}",
7292
transform=ax.transAxes,
73-
fontsize=18,
93+
fontsize=9,
7494
verticalalignment="top",
7595
color=INK,
7696
bbox={"boxstyle": "round,pad=0.4", "facecolor": ELEVATED_BG, "edgecolor": INK_SOFT, "alpha": 0.95},
7797
)
7898

79-
# Styling with theme-adaptive chrome
80-
ax.set_xlabel("Study Hours (hrs)", fontsize=20, color=INK)
81-
ax.set_ylabel("Exam Score (points)", fontsize=20, color=INK)
82-
ax.set_title("scatter-regression-linear · matplotlib · anyplot.ai", fontsize=24, fontweight="medium", color=INK)
83-
ax.tick_params(axis="both", labelsize=16, colors=INK_SOFT)
99+
# Title and axis labels (title short enough to use the default 12pt)
100+
title = "scatter-regression-linear · python · matplotlib · anyplot.ai"
101+
ax.set_title(title, fontsize=12, fontweight="medium", color=INK)
102+
ax.set_xlabel("Study Hours (hrs)", fontsize=10, color=INK)
103+
ax.set_ylabel("Exam Score (points)", fontsize=10, color=INK)
104+
ax.tick_params(axis="both", labelsize=8, colors=INK_SOFT)
84105

85-
# Grid styling (subtle, y-axis preferred)
86-
ax.yaxis.grid(True, alpha=0.15, linewidth=0.8, color=INK)
106+
# Grid: both axes, subtle (scatter convention)
107+
ax.grid(True, alpha=0.15, linewidth=0.8, color=INK)
108+
ax.set_axisbelow(True)
87109

88110
# Spine styling
89111
ax.spines["top"].set_visible(False)
@@ -92,7 +114,7 @@
92114
ax.spines[spine].set_color(INK_SOFT)
93115

94116
# Legend styling
95-
leg = ax.legend(fontsize=16, loc="lower right")
117+
leg = ax.legend(fontsize=8, loc="lower right")
96118
if leg:
97119
leg.get_frame().set_facecolor(ELEVATED_BG)
98120
leg.get_frame().set_edgecolor(INK_SOFT)
@@ -101,4 +123,4 @@
101123
text.set_color(INK_SOFT)
102124

103125
plt.tight_layout()
104-
plt.savefig(f"plot-{THEME}.png", dpi=300, bbox_inches="tight", facecolor=PAGE_BG)
126+
plt.savefig(f"plot-{THEME}.png", dpi=400, facecolor=PAGE_BG)

0 commit comments

Comments
 (0)