Skip to content

Commit 58bd74d

Browse files
update(density-basic): letsplot — comprehensive quality review (#4388)
## Summary Updated **letsplot** implementation for **density-basic**. **Changes:** Comprehensive quality review ### Changes - Replaced test scores with marathon finish times data - Fixed save method from deprecated export_ggsave to ggsave - Added distinctive features: trim, kernel, adjust, tooltips - Improved grid to y-axis only - Quality: 84/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 7f9df95 commit 58bd74d

2 files changed

Lines changed: 211 additions & 137 deletions

File tree

Lines changed: 66 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,92 @@
11
""" pyplots.ai
22
density-basic: Basic Density Plot
3-
Library: letsplot 4.8.2 | Python 3.13.11
4-
Quality: 91/100 | Created: 2025-12-23
3+
Library: letsplot 4.8.2 | Python 3.14.3
4+
Quality: 91/100 | Updated: 2026-02-23
55
"""
66

77
import numpy as np
8+
import pandas as pd
89
from lets_plot import * # noqa: F403
9-
from lets_plot.export import ggsave as export_ggsave
1010

1111

1212
LetsPlot.setup_html() # noqa: F405
1313

14-
# Data - Generate realistic test scores with slight right skew
14+
# Data - Simulated marathon finish times with realistic right skew
1515
np.random.seed(42)
16-
scores = np.concatenate(
16+
finish_minutes = np.concatenate(
1717
[
18-
np.random.normal(72, 12, 300), # Main group of students
19-
np.random.normal(90, 5, 100), # High achievers
18+
np.random.normal(240, 25, 350), # Main pack (~4 hour runners)
19+
np.random.normal(200, 15, 100), # Competitive runners (~3:20)
20+
np.random.normal(300, 20, 50), # Casual runners (~5 hours)
2021
]
2122
)
23+
finish_minutes = np.clip(finish_minutes, 140, 400)
2224

23-
# Create plot
25+
df = pd.DataFrame({"time": finish_minutes})
26+
27+
# Rug data: small vertical ticks at each observation
28+
rug_df = pd.DataFrame({"x": finish_minutes, "y0": 0.0, "y1": 0.0004})
29+
30+
# Runner group centroids for storytelling annotations (staggered y to avoid crowding)
31+
group_labels = pd.DataFrame(
32+
{
33+
"x": [195, 243, 300],
34+
"y": [0.0131, 0.0119, 0.0131],
35+
"label": ["Competitive (~3:20)", "Main Pack (~4:00)", "Casual (~5:00)"],
36+
}
37+
)
38+
39+
# Plot
2440
plot = (
25-
ggplot({"scores": scores}, aes(x="scores")) # noqa: F405
26-
+ geom_density(fill="#306998", color="#306998", alpha=0.6, size=1.5) # noqa: F405
27-
+ labs(x="Test Score", y="Density", title="density-basic · letsplot · pyplots.ai") # noqa: F405
41+
ggplot(df, aes(x="time")) # noqa: F405
42+
+ geom_density( # noqa: F405
43+
fill="#306998",
44+
color="#1e4263",
45+
alpha=0.55,
46+
size=1.8,
47+
kernel="gaussian",
48+
adjust=0.85,
49+
trim=True,
50+
tooltips=layer_tooltips() # noqa: F405
51+
.line("@|@time")
52+
.line("density|@..density.."),
53+
)
54+
+ geom_segment( # noqa: F405
55+
data=rug_df,
56+
mapping=aes(x="x", y="y0", xend="x", yend="y1"), # noqa: F405
57+
color="#1e4263",
58+
alpha=0.15,
59+
size=0.4,
60+
)
61+
+ geom_vline(xintercept=200, linetype="dashed", color="#1a5276", alpha=0.4, size=0.7) # noqa: F405
62+
+ geom_vline(xintercept=240, linetype="dashed", color="#306998", alpha=0.4, size=0.7) # noqa: F405
63+
+ geom_vline(xintercept=300, linetype="dashed", color="#5d8aa8", alpha=0.4, size=0.7) # noqa: F405
64+
+ geom_text( # noqa: F405
65+
data=group_labels,
66+
mapping=aes(x="x", y="y", label="label"), # noqa: F405
67+
size=12,
68+
color="#444444",
69+
)
70+
+ labs( # noqa: F405
71+
x="Finish Time (minutes)", y="Density (×10⁻³)", title="density-basic · letsplot · pyplots.ai"
72+
)
73+
+ scale_x_continuous(breaks=list(range(150, 401, 50))) # noqa: F405
74+
+ scale_y_continuous( # noqa: F405
75+
breaks=[0.002, 0.004, 0.006, 0.008, 0.010], labels=["2", "4", "6", "8", "10"], expand=[0.02, 0, 0.38, 0]
76+
)
2877
+ theme_minimal() # noqa: F405
2978
+ theme( # noqa: F405
3079
axis_title=element_text(size=20), # noqa: F405
3180
axis_text=element_text(size=16), # noqa: F405
3281
plot_title=element_text(size=24), # noqa: F405
33-
panel_grid_major=element_line(color="#cccccc", size=0.5), # noqa: F405
82+
panel_grid_major_x=element_blank(), # noqa: F405
83+
panel_grid_major_y=element_line(color="#e0e0e0", size=0.4), # noqa: F405
3484
panel_grid_minor=element_blank(), # noqa: F405
85+
axis_ticks=element_blank(), # noqa: F405
3586
)
3687
+ ggsize(1600, 900) # noqa: F405
3788
)
3889

39-
# Save PNG (scale 3x for 4800x2700) and HTML
40-
export_ggsave(plot, "plot.png", path=".", scale=3)
41-
export_ggsave(plot, "plot.html", path=".")
90+
# Save PNG (scale 3x for 4800 x 2700 px) and HTML
91+
ggsave(plot, "plot.png", path=".", scale=3) # noqa: F405
92+
ggsave(plot, "plot.html", path=".") # noqa: F405

0 commit comments

Comments
 (0)