diff --git a/plots/density-basic/implementations/letsplot.py b/plots/density-basic/implementations/letsplot.py index d2e6fea4e6..02a0b17e30 100644 --- a/plots/density-basic/implementations/letsplot.py +++ b/plots/density-basic/implementations/letsplot.py @@ -1,41 +1,92 @@ """ pyplots.ai density-basic: Basic Density Plot -Library: letsplot 4.8.2 | Python 3.13.11 -Quality: 91/100 | Created: 2025-12-23 +Library: letsplot 4.8.2 | Python 3.14.3 +Quality: 91/100 | Updated: 2026-02-23 """ import numpy as np +import pandas as pd from lets_plot import * # noqa: F403 -from lets_plot.export import ggsave as export_ggsave LetsPlot.setup_html() # noqa: F405 -# Data - Generate realistic test scores with slight right skew +# Data - Simulated marathon finish times with realistic right skew np.random.seed(42) -scores = np.concatenate( +finish_minutes = np.concatenate( [ - np.random.normal(72, 12, 300), # Main group of students - np.random.normal(90, 5, 100), # High achievers + np.random.normal(240, 25, 350), # Main pack (~4 hour runners) + np.random.normal(200, 15, 100), # Competitive runners (~3:20) + np.random.normal(300, 20, 50), # Casual runners (~5 hours) ] ) +finish_minutes = np.clip(finish_minutes, 140, 400) -# Create plot +df = pd.DataFrame({"time": finish_minutes}) + +# Rug data: small vertical ticks at each observation +rug_df = pd.DataFrame({"x": finish_minutes, "y0": 0.0, "y1": 0.0004}) + +# Runner group centroids for storytelling annotations (staggered y to avoid crowding) +group_labels = pd.DataFrame( + { + "x": [195, 243, 300], + "y": [0.0131, 0.0119, 0.0131], + "label": ["Competitive (~3:20)", "Main Pack (~4:00)", "Casual (~5:00)"], + } +) + +# Plot plot = ( - ggplot({"scores": scores}, aes(x="scores")) # noqa: F405 - + geom_density(fill="#306998", color="#306998", alpha=0.6, size=1.5) # noqa: F405 - + labs(x="Test Score", y="Density", title="density-basic · letsplot · pyplots.ai") # noqa: F405 + ggplot(df, aes(x="time")) # noqa: F405 + + geom_density( # noqa: F405 + fill="#306998", + color="#1e4263", + alpha=0.55, + size=1.8, + kernel="gaussian", + adjust=0.85, + trim=True, + tooltips=layer_tooltips() # noqa: F405 + .line("@|@time") + .line("density|@..density.."), + ) + + geom_segment( # noqa: F405 + data=rug_df, + mapping=aes(x="x", y="y0", xend="x", yend="y1"), # noqa: F405 + color="#1e4263", + alpha=0.15, + size=0.4, + ) + + geom_vline(xintercept=200, linetype="dashed", color="#1a5276", alpha=0.4, size=0.7) # noqa: F405 + + geom_vline(xintercept=240, linetype="dashed", color="#306998", alpha=0.4, size=0.7) # noqa: F405 + + geom_vline(xintercept=300, linetype="dashed", color="#5d8aa8", alpha=0.4, size=0.7) # noqa: F405 + + geom_text( # noqa: F405 + data=group_labels, + mapping=aes(x="x", y="y", label="label"), # noqa: F405 + size=12, + color="#444444", + ) + + labs( # noqa: F405 + x="Finish Time (minutes)", y="Density (×10⁻³)", title="density-basic · letsplot · pyplots.ai" + ) + + scale_x_continuous(breaks=list(range(150, 401, 50))) # noqa: F405 + + scale_y_continuous( # noqa: F405 + breaks=[0.002, 0.004, 0.006, 0.008, 0.010], labels=["2", "4", "6", "8", "10"], expand=[0.02, 0, 0.38, 0] + ) + theme_minimal() # noqa: F405 + theme( # noqa: F405 axis_title=element_text(size=20), # noqa: F405 axis_text=element_text(size=16), # noqa: F405 plot_title=element_text(size=24), # noqa: F405 - panel_grid_major=element_line(color="#cccccc", size=0.5), # noqa: F405 + panel_grid_major_x=element_blank(), # noqa: F405 + panel_grid_major_y=element_line(color="#e0e0e0", size=0.4), # noqa: F405 panel_grid_minor=element_blank(), # noqa: F405 + axis_ticks=element_blank(), # noqa: F405 ) + ggsize(1600, 900) # noqa: F405 ) -# Save PNG (scale 3x for 4800x2700) and HTML -export_ggsave(plot, "plot.png", path=".", scale=3) -export_ggsave(plot, "plot.html", path=".") +# Save PNG (scale 3x for 4800 x 2700 px) and HTML +ggsave(plot, "plot.png", path=".", scale=3) # noqa: F405 +ggsave(plot, "plot.html", path=".") # noqa: F405 diff --git a/plots/density-basic/metadata/letsplot.yaml b/plots/density-basic/metadata/letsplot.yaml index 20cfc6d96f..d5078ccdb3 100644 --- a/plots/density-basic/metadata/letsplot.yaml +++ b/plots/density-basic/metadata/letsplot.yaml @@ -1,11 +1,11 @@ library: letsplot specification_id: density-basic created: '2025-12-23T10:03:24Z' -updated: '2025-12-23T10:11:02Z' -generated_by: claude-opus-4-5-20251101 +updated: '2026-02-23T23:11:17Z' +generated_by: claude-opus-4-6 workflow_run: 20457538654 issue: 0 -python_version: 3.13.11 +python_version: 3.14.3 library_version: 4.8.2 preview_url: https://storage.googleapis.com/pyplots-images/plots/density-basic/letsplot/plot.png preview_thumb: https://storage.googleapis.com/pyplots-images/plots/density-basic/letsplot/plot_thumb.png @@ -14,151 +14,168 @@ quality_score: 91 impl_tags: dependencies: [] techniques: - - layer-composition - - html-export + - annotations + - layer-composition + - hover-tooltips + - html-export patterns: - - data-generation - dataprep: - - kde + - data-generation + dataprep: [] styling: - - alpha-blending + - alpha-blending + - grid-styling review: strengths: - - Clean ggplot2-style grammar implementation with proper layering - - Excellent bimodal data generation showing both main distribution and high achievers - - Proper use of theme customization for text sizing and grid styling - - Correct 16:9 aspect ratio with 3x scaling for 4800x2700 output - - Good color choice with appropriate alpha for fill transparency + - Excellent data storytelling with three annotated marathon runner groups creating + immediate narrative clarity + - Professional monochromatic blue palette with intentional visual hierarchy (curve + > annotations > rug) + - Comprehensive spec coverage including optional rug plot and bandwidth tuning + - Clean y-axis rescaling trick (×10⁻³) improves readability of small density values + - Clever use of layer_tooltips() for interactive hover information weaknesses: - - Axis labels lack units (e.g., "Test Score (points)" or "Density (probability)") - - Test score values exceeding 100 are unrealistic for typical grading scales - - Could benefit from a rug plot to show individual observations as suggested in - spec notes - image_description: The plot displays a smooth kernel density estimation curve showing - the distribution of test scores. The x-axis shows "Test Score" ranging from approximately - 30 to 120, while the y-axis shows "Density" ranging from 0 to about 0.026. The - density curve is filled with Python blue (#306998) with appropriate transparency, - and the outline is the same color. The distribution is bimodal, with a primary - peak around 75 and a secondary peak around 88, reflecting the mixture of a main - student group and high achievers. The title "density-basic · letsplot · pyplots.ai" - is positioned at the top. The background is clean with subtle gray gridlines on - a minimal theme. The overall layout has good proportions in 16:9 aspect ratio. + - Annotation text size (12) is noticeably smaller than axis text — could be increased + for better visual balance + - Rug plot at alpha=0.15 is very subtle and individual marks are hard to distinguish + in dense regions + image_description: 'The plot displays a density (KDE) curve of marathon finish times + ranging from approximately 150 to 370 minutes. The curve is filled with a muted + Python Blue (#306998) at 55% opacity with a darker blue (#1e4263) outline. The + distribution is right-skewed with a main peak around 235-240 minutes. Three vertical + dashed lines mark group centroids at ~200, ~240, and ~300 minutes, each labeled + with text annotations: "Competitive (~3:20)", "Main Pack (~4:00)", and "Casual + (~5:00)". A subtle rug plot of individual observations runs along the x-axis. + The x-axis reads "Finish Time (minutes)" with ticks at 150-350 in increments of + 50. The y-axis reads "Density (×10⁻³)" with scaled labels 2, 4, 6, 8, 10. The + title "density-basic · letsplot · pyplots.ai" appears at the top left. The background + is clean with minimal theme, no x-grid, subtle light-gray y-grid lines, and no + axis ticks or spines.' criteria_checklist: visual_quality: - score: 36 - max: 40 + score: 28 + max: 30 items: - id: VQ-01 name: Text Legibility - score: 10 - max: 10 + score: 7 + max: 8 passed: true - comment: Title, axis labels, and tick marks are all clearly readable at appropriate - sizes + comment: All font sizes explicitly set (title=24, axis_title=20, axis_text=16). + Annotation text at size=12 readable but slightly small. - id: VQ-02 name: No Overlap - score: 8 - max: 8 + score: 6 + max: 6 passed: true - comment: No overlapping text elements + comment: Annotations staggered at different y-positions. No text collisions. - id: VQ-03 name: Element Visibility - score: 8 - max: 8 + score: 5 + max: 6 passed: true - comment: Density curve is smooth and well-visible with good fill transparency + comment: Density curve prominent. Rug plot at alpha=0.15 intentionally subtle + but individual marks hard to distinguish in dense regions. - id: VQ-04 name: Color Accessibility - score: 5 - max: 5 + score: 4 + max: 4 passed: true - comment: Single color scheme, no accessibility issues + comment: Single-series monochromatic blue. No colorblind concerns. Good contrast. - id: VQ-05 - name: Layout Balance - score: 5 - max: 5 + name: Layout & Canvas + score: 4 + max: 4 passed: true - comment: Good proportions, no cut-off content + comment: Plot fills ~65% of 16:9 canvas. Balanced margins. No cut-off. - id: VQ-06 - name: Axis Labels - score: 1 - max: 2 - passed: false - comment: Descriptive labels but no units (e.g., "Test Score (points)" would - be better) - - id: VQ-07 - name: Grid & Legend - score: 1 + name: Axis Labels & Title + score: 2 max: 2 - passed: false - comment: Minor gridlines disabled which is good, but major grid alpha could - be slightly more subtle + passed: true + comment: 'Descriptive labels with units: Finish Time (minutes), Density (×10⁻³). + Correct title format.' + design_excellence: + score: 16 + max: 20 + items: + - id: DE-01 + name: Aesthetic Sophistication + score: 6 + max: 8 + passed: true + comment: Cohesive monochromatic blue palette. Intentional visual hierarchy. + Professional look clearly above defaults. + - id: DE-02 + name: Visual Refinement + score: 5 + max: 6 + passed: true + comment: theme_minimal with x-grid removed, subtle y-grid, ticks removed. + Y-axis rescaling trick for readability. + - id: DE-03 + name: Data Storytelling + score: 5 + max: 6 + passed: true + comment: Three labeled runner groups create clear narrative. Dashed reference + lines guide viewer. Right-skewed distribution tells immediate story. spec_compliance: - score: 25 - max: 25 + score: 15 + max: 15 items: - id: SC-01 name: Plot Type - score: 8 - max: 8 - passed: true - comment: Correct density/KDE plot - - id: SC-02 - name: Data Mapping score: 5 max: 5 passed: true - comment: Continuous variable correctly mapped to x-axis - - id: SC-03 + comment: Correct density/KDE plot using geom_density() with Gaussian kernel. + - id: SC-02 name: Required Features - score: 5 - max: 5 + score: 4 + max: 4 passed: true - comment: Smooth curve with fill under curve as suggested in spec - - id: SC-04 - name: Data Range + comment: Smooth KDE curve, fill with transparency, rug plot, bandwidth control. + All present. + - id: SC-03 + name: Data Mapping score: 3 max: 3 passed: true - comment: All data visible - - id: SC-05 - name: Legend Accuracy - score: 2 - max: 2 - passed: true - comment: No legend needed for single variable - - id: SC-06 - name: Title Format - score: 2 - max: 2 + comment: Continuous variable on x-axis, density on y-axis. All data visible. + - id: SC-04 + name: Title & Legend + score: 3 + max: 3 passed: true - comment: 'Correct format: "density-basic · letsplot · pyplots.ai"' + comment: Title 'density-basic · letsplot · pyplots.ai' correct. No legend + needed for single series. data_quality: - score: 18 - max: 20 + score: 15 + max: 15 items: - id: DQ-01 name: Feature Coverage - score: 7 - max: 8 + score: 6 + max: 6 passed: true - comment: Shows bimodal distribution demonstrating skewness and multiple peaks, - but no rug plot (optional per spec) + comment: Multimodal distribution shows skewness, multiple modes, and population + substructure. - id: DQ-02 name: Realistic Context - score: 7 - max: 7 + score: 5 + max: 5 passed: true - comment: Test scores is a perfect real-world scenario for density plots + comment: Marathon finish times - real-world, neutral scenario with three realistic + runner groups. - id: DQ-03 name: Appropriate Scale score: 4 - max: 5 - passed: false - comment: Good range (30-120), though some extreme values exceed 100 which - is unusual for test scores + max: 4 + passed: true + comment: Competitive ~200min, main pack ~240min, casual ~300min. All realistic + marathon times. code_quality: - score: 9 + score: 10 max: 10 items: - id: CQ-01 @@ -166,42 +183,48 @@ review: score: 3 max: 3 passed: true - comment: Clean imports → data → plot → save structure + comment: Clean Imports → Data → Plot → Save flow. No functions or classes. - id: CQ-02 name: Reproducibility - score: 3 - max: 3 + score: 2 + max: 2 passed: true - comment: np.random.seed(42) used + comment: np.random.seed(42) set before random generation. - id: CQ-03 name: Clean Imports score: 2 max: 2 passed: true - comment: Only necessary imports + comment: numpy, pandas, lets_plot all used. No unused imports. - id: CQ-04 - name: No Deprecated API - score: 1 - max: 1 + name: Code Elegance + score: 2 + max: 2 passed: true - comment: Current API usage + comment: Clean Pythonic code. Appropriate complexity. Separate DataFrames + for rug and annotations. - id: CQ-05 - name: Output Correct - score: 0 + name: Output & API + score: 1 max: 1 - passed: false - comment: Saves both plot.png and plot.html, but uses export_ggsave from lets_plot.export - instead of standard ggsave - library_features: - score: 3 - max: 5 + passed: true + comment: Saves plot.png via ggsave(scale=3) and plot.html. Current API. + library_mastery: + score: 7 + max: 10 items: - - id: LF-01 - name: Uses distinctive library features + - id: LM-01 + name: Idiomatic Usage + score: 4 + max: 5 + passed: true + comment: 'Proper ggplot grammar. Uses lets_plot-specific patterns: ggsize, + ggsave(scale=3), LetsPlot.setup_html(). Good scale_y_continuous trick.' + - id: LM-02 + name: Distinctive Features score: 3 max: 5 - passed: false - comment: Uses ggplot2 grammar correctly with geom_density, theme_minimal, - and proper ggsize for scaling, but doesn't leverage lets-plot specific interactive - features or advanced density options + passed: true + comment: layer_tooltips() with custom format is distinctive to lets_plot. + HTML export leverages interactive capabilities. verdict: APPROVED