Skip to content

Commit 556c76e

Browse files
update(density-basic): plotly — comprehensive quality review (#4382)
## Summary Updated **plotly** implementation for **density-basic**. **Changes:** Comprehensive quality review ### Changes - Replaced manual KDE with scipy.stats.gaussian_kde - Changed data to SAT Math Scores with realistic range - Added units to axis labels, refined grid styling - Quality: 89/100 (local self-evaluation, after 2 iterations) ## Test Plan - [x] Preview images uploaded to GCS staging - [x] Implementation file passes ruff format/check - [x] Metadata YAML updated with current versions - [ ] Automated review triggered --- Generated with [Claude Code](https://claude.com/claude-code) `/update` command --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent ef433f1 commit 556c76e

2 files changed

Lines changed: 227 additions & 168 deletions

File tree

Lines changed: 76 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,97 +1,134 @@
11
""" pyplots.ai
22
density-basic: Basic Density Plot
3-
Library: plotly 6.5.0 | Python 3.13.11
4-
Quality: 92/100 | Created: 2025-12-23
3+
Library: plotly 6.5.2 | Python 3.14.3
4+
Quality: 93/100 | Updated: 2026-02-23
55
"""
66

77
import numpy as np
88
import plotly.graph_objects as go
9+
from scipy.stats import gaussian_kde
910

1011

11-
# Data - Test scores with realistic bimodal distribution
12+
# Data - SAT Math scores with bimodal distribution
1213
np.random.seed(42)
13-
scores = np.concatenate(
14+
sat_scores = np.concatenate(
1415
[
15-
np.random.normal(72, 10, 300), # Main group around 72
16-
np.random.normal(88, 5, 100), # High achievers around 88
16+
np.random.normal(540, 60, 350), # Main group around 540
17+
np.random.normal(680, 35, 150), # High achievers around 680
1718
]
1819
)
20+
sat_scores = np.clip(sat_scores, 200, 800) # SAT range
1921

20-
# Compute KDE using Silverman's rule of thumb for bandwidth
21-
n = len(scores)
22-
std = np.std(scores, ddof=1)
23-
iqr = np.percentile(scores, 75) - np.percentile(scores, 25)
24-
bandwidth = 0.9 * min(std, iqr / 1.34) * n ** (-0.2)
22+
# KDE using scipy
23+
kde = gaussian_kde(sat_scores)
24+
x_grid = np.linspace(350, 800, 500)
25+
density = kde(x_grid)
2526

26-
# Evaluate density at each point on a grid
27-
x_range = np.linspace(scores.min() - 10, scores.max() + 10, 500)
28-
density = np.zeros_like(x_range)
29-
for xi in scores:
30-
density += np.exp(-0.5 * ((x_range - xi) / bandwidth) ** 2)
31-
density /= n * bandwidth * np.sqrt(2 * np.pi)
27+
# Identify peaks for annotations (split at valley ~620 pts)
28+
split = int(500 * (620 - 350) / (800 - 350))
29+
peak1_idx = np.argmax(density[:split])
30+
peak2_idx = split + np.argmax(density[split:])
31+
peak1_x, peak1_y = x_grid[peak1_idx], density[peak1_idx]
32+
peak2_x, peak2_y = x_grid[peak2_idx], density[peak2_idx]
3233

33-
# Create figure
34+
# Plot
3435
fig = go.Figure()
3536

36-
# Density curve with fill
3737
fig.add_trace(
3838
go.Scatter(
39-
x=x_range,
39+
x=x_grid,
4040
y=density,
4141
mode="lines",
4242
fill="tozeroy",
43-
fillcolor="rgba(48, 105, 152, 0.3)",
44-
line={"color": "#306998", "width": 4},
43+
fillcolor="rgba(48, 105, 152, 0.25)",
44+
line={"color": "#306998", "width": 3.5},
4545
name="Density",
46-
hovertemplate="Score: %{x:.1f}<br>Density: %{y:.4f}<extra></extra>",
46+
hovertemplate="Score: %{x:.0f}<br>Density: %{y:.4f}<extra></extra>",
4747
)
4848
)
4949

50-
# Rug plot showing individual observations
50+
# Rug plot
5151
fig.add_trace(
5252
go.Scatter(
53-
x=scores,
54-
y=[-0.001] * len(scores),
53+
x=sat_scores,
54+
y=np.zeros(len(sat_scores)),
5555
mode="markers",
56-
marker={"symbol": "line-ns", "size": 12, "color": "#306998", "line": {"width": 1.5}},
56+
marker={"symbol": "line-ns", "size": 14, "color": "#306998", "opacity": 0.5, "line": {"width": 1.5}},
5757
name="Observations",
58-
hovertemplate="Score: %{x:.1f}<extra></extra>",
58+
hovertemplate="Score: %{x:.0f}<extra></extra>",
5959
)
6060
)
6161

62+
# Peak annotations to highlight bimodal structure
63+
fig.add_annotation(
64+
x=peak1_x,
65+
y=peak1_y,
66+
text=f"<b>Primary Peak</b><br>~{peak1_x:.0f} pts",
67+
showarrow=True,
68+
arrowhead=2,
69+
arrowsize=1.2,
70+
arrowwidth=2,
71+
arrowcolor="#306998",
72+
font={"size": 18, "color": "#306998"},
73+
ax=-80,
74+
ay=-50,
75+
bgcolor="rgba(255, 255, 255, 0.9)",
76+
borderpad=6,
77+
)
78+
fig.add_annotation(
79+
x=peak2_x,
80+
y=peak2_y,
81+
text=f"<b>High Achievers</b><br>~{peak2_x:.0f} pts",
82+
showarrow=True,
83+
arrowhead=2,
84+
arrowsize=1.2,
85+
arrowwidth=2,
86+
arrowcolor="#306998",
87+
font={"size": 18, "color": "#306998"},
88+
ax=80,
89+
ay=-40,
90+
bgcolor="rgba(255, 255, 255, 0.9)",
91+
borderpad=6,
92+
)
93+
6294
# Layout
6395
fig.update_layout(
6496
title={"text": "density-basic · plotly · pyplots.ai", "font": {"size": 36}, "x": 0.5, "xanchor": "center"},
6597
xaxis={
66-
"title": {"text": "Test Score", "font": {"size": 28}},
98+
"title": {"text": "SAT Math Score (points)", "font": {"size": 28}},
6799
"tickfont": {"size": 22},
68-
"showgrid": True,
69-
"gridwidth": 1,
70-
"gridcolor": "rgba(128, 128, 128, 0.2)",
100+
"showgrid": False,
71101
"zeroline": False,
102+
"showspikes": True,
103+
"spikemode": "across",
104+
"spikethickness": 1,
105+
"spikecolor": "rgba(48, 105, 152, 0.3)",
106+
"spikedash": "dot",
72107
},
73108
yaxis={
74109
"title": {"text": "Density", "font": {"size": 28}},
75110
"tickfont": {"size": 22},
76-
"showgrid": True,
111+
"gridcolor": "rgba(128, 128, 128, 0.15)",
77112
"gridwidth": 1,
78-
"gridcolor": "rgba(128, 128, 128, 0.2)",
79113
"zeroline": False,
80114
"rangemode": "tozero",
81115
},
82116
template="plotly_white",
83117
showlegend=True,
84118
legend={
85119
"font": {"size": 20},
86-
"x": 0.98,
87-
"y": 0.98,
120+
"x": 0.97,
121+
"y": 0.95,
88122
"xanchor": "right",
89123
"yanchor": "top",
90124
"bgcolor": "rgba(255, 255, 255, 0.8)",
125+
"borderwidth": 0,
91126
},
92-
margin={"l": 100, "r": 60, "t": 100, "b": 100},
127+
hovermode="x",
128+
margin={"l": 90, "r": 40, "t": 90, "b": 90},
129+
plot_bgcolor="white",
93130
)
94131

95-
# Save as PNG and HTML
132+
# Save
96133
fig.write_image("plot.png", width=1600, height=900, scale=3)
97-
fig.write_html("plot.html", include_plotlyjs="cdn")
134+
fig.write_html("plot.html", include_plotlyjs="cdn", config={"displayModeBar": True, "scrollZoom": True})

0 commit comments

Comments
 (0)