diff --git a/plots/map-marker-clustered/implementations/python/seaborn.py b/plots/map-marker-clustered/implementations/python/seaborn.py index 2355412ebf..b6753885f1 100644 --- a/plots/map-marker-clustered/implementations/python/seaborn.py +++ b/plots/map-marker-clustered/implementations/python/seaborn.py @@ -1,9 +1,11 @@ -""" pyplots.ai +""" anyplot.ai map-marker-clustered: Clustered Marker Map -Library: seaborn 0.13.2 | Python 3.13.11 -Quality: 90/100 | Created: 2026-01-20 +Library: seaborn 0.13.2 | Python 3.13.13 +Quality: 89/100 | Updated: 2026-05-23 """ +import os + import matplotlib.patches as mpatches import matplotlib.pyplot as plt import numpy as np @@ -13,22 +15,32 @@ from sklearn.cluster import AgglomerativeClustering -# Generate sample geographic data - business locations across NYC region +# Theme tokens +THEME = os.getenv("ANYPLOT_THEME", "light") +PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" +ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420" +INK = "#1A1A17" if THEME == "light" else "#F0EFE8" +INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0" +INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F" + +# anyplot palette — canonical order, 4 categories +ANYPLOT_PALETTE = ["#009E73", "#9418DB", "#B71D27", "#16B8F3"] +category_names = ["Coffee Shop", "Restaurant", "Bookstore", "Gym"] +category_palette = dict(zip(category_names, ANYPLOT_PALETTE, strict=True)) + +# Data — business locations across NYC region np.random.seed(42) n_points = 500 -# Create clustered geographic data (simulating city with neighborhoods) n_neighborhoods = 8 neighborhood_centers = np.random.uniform(-0.5, 0.5, (n_neighborhoods, 2)) points_per_neighborhood = n_points // n_neighborhoods lats, lons, categories = [], [], [] -category_names = ["Coffee Shop", "Restaurant", "Bookstore", "Gym"] - for i, center in enumerate(neighborhood_centers): n_pts = points_per_neighborhood + (n_points % n_neighborhoods if i == 0 else 0) - lat = np.random.normal(center[0], 0.08, n_pts) + 40.7 # NYC-like latitude - lon = np.random.normal(center[1], 0.08, n_pts) - 74.0 # NYC-like longitude + lat = np.random.normal(center[0], 0.08, n_pts) + 40.7 + lon = np.random.normal(center[1], 0.08, n_pts) - 74.0 lats.extend(lat) lons.extend(lon) categories.extend(np.random.choice(category_names, n_pts)) @@ -37,10 +49,10 @@ # Apply hierarchical clustering to group nearby markers coords = df[["lat", "lon"]].values -clustering = AgglomerativeClustering(n_clusters=None, distance_threshold=0.05, linkage="ward") +clustering = AgglomerativeClustering(n_clusters=None, distance_threshold=0.18, linkage="ward") df["cluster"] = clustering.fit_predict(coords) -# Calculate cluster centers and sizes +# Cluster centers, sizes and dominant category cluster_stats = ( df.groupby("cluster") .agg( @@ -52,45 +64,62 @@ .reset_index() ) -# Set seaborn context and style for the entire plot -sns.set_theme(style="whitegrid", context="talk", font_scale=1.2) - -# Use colorblind-friendly palette from seaborn -palette = sns.color_palette("colorblind", n_colors=4) -category_palette = dict(zip(category_names, palette, strict=True)) - -# Create the plot -fig, ax = plt.subplots(figsize=(16, 9)) - -# Add simplified geographic context - stylized NYC region boundaries -# Hudson River approximation (western boundary) -hudson_river = [[(-74.05, 40.70), (-74.02, 40.85), (-73.95, 41.00), (-73.90, 41.15)]] -# Long Island Sound approximation (eastern boundary) -li_sound = [[(-73.80, 40.85), (-73.70, 40.95), (-73.55, 41.05)]] -# Atlantic coast approximation (southern boundary) -coast = [[(-74.20, 40.55), (-74.00, 40.50), (-73.80, 40.58), (-73.60, 40.62)]] - -# Draw water boundaries as light blue lines for geographic context -for boundary in [hudson_river, li_sound, coast]: - for segment in boundary: - xs, ys = zip(*segment, strict=True) - ax.plot(xs, ys, color="#a8d4e6", linewidth=8, alpha=0.4, zorder=0, solid_capstyle="round") - -# Add a subtle land area fill -land_coords = [(-74.45, 40.0), (-74.45, 41.25), (-73.35, 41.25), (-73.35, 40.0)] -land_patch = mpatches.Polygon(land_coords, facecolor="#f5f5dc", edgecolor="none", alpha=0.3, zorder=-1) +# Compute actual cluster size thresholds for accurate legend labels +count_min = int(cluster_stats["count"].min()) +count_p33 = int(cluster_stats["count"].quantile(0.33)) +count_p66 = int(cluster_stats["count"].quantile(0.66)) +count_max = int(cluster_stats["count"].max()) + +# Set seaborn theme with theme-adaptive chrome +sns.set_theme( + style="ticks", + rc={ + "figure.facecolor": PAGE_BG, + "axes.facecolor": PAGE_BG, + "axes.edgecolor": INK_SOFT, + "axes.labelcolor": INK, + "text.color": INK, + "xtick.color": INK_SOFT, + "ytick.color": INK_SOFT, + "grid.color": INK, + "grid.alpha": 0.10, + "legend.facecolor": ELEVATED_BG, + "legend.edgecolor": INK_SOFT, + }, +) + +# Plot +fig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG) +ax.set_facecolor(PAGE_BG) + +# Geographic context — stylized NYC region water boundaries +hudson_river = [(-74.05, 40.70), (-74.02, 40.85), (-73.95, 41.00), (-73.90, 41.15)] +li_sound = [(-73.80, 40.85), (-73.70, 40.95), (-73.55, 41.05)] +coast = [(-74.20, 40.55), (-74.00, 40.50), (-73.80, 40.58), (-73.60, 40.62)] + +for segment in [hudson_river, li_sound, coast]: + xs, ys = zip(*segment, strict=True) + ax.plot(xs, ys, color="#a8d4e6", linewidth=1.5, alpha=0.4, zorder=0, solid_capstyle="round") + +land_fill = "#f5f5dc" if THEME == "light" else "#2a2a20" +land_patch = mpatches.Polygon( + [(-74.45, 40.0), (-74.45, 41.25), (-73.35, 41.25), (-73.35, 40.0)], + facecolor=land_fill, + edgecolor="none", + alpha=0.3, + zorder=-1, +) ax.add_patch(land_patch) -# Plot individual points as background layer using seaborn scatterplot +# KDE density contours — seaborn-distinctive statistical overlay showing point density +sns.kdeplot(data=df, x="lon", y="lat", levels=6, color=INK_MUTED, alpha=0.35, linewidths=0.9, ax=ax, zorder=1) + +# Background layer — individual points at minimal alpha sns.scatterplot( - data=df, x="lon", y="lat", hue="category", palette=category_palette, s=40, alpha=0.3, ax=ax, legend=False + data=df, x="lon", y="lat", hue="category", palette=category_palette, s=6, alpha=0.08, ax=ax, legend=False ) -# Create cluster dataframe for seaborn visualization -cluster_stats["size_scaled"] = cluster_stats["count"] * 20 -cluster_stats["color"] = cluster_stats["dominant_category"].map(category_palette) - -# Plot cluster markers using seaborn scatterplot with size encoding +# Foreground layer — cluster markers sized by count sns.scatterplot( data=cluster_stats, x="lon_center", @@ -98,85 +127,96 @@ size="count", hue="dominant_category", palette=category_palette, - sizes=(100, 800), + sizes=(50, 500), alpha=0.85, - edgecolor="white", - linewidth=2, + edgecolor=PAGE_BG, + linewidth=0.8, ax=ax, legend=False, ) -# Add count labels to larger clusters +# Count labels on clusters for _, row in cluster_stats.iterrows(): - if row["count"] > 4: + if row["count"] > 1: ax.annotate( str(int(row["count"])), (row["lon_center"], row["lat_center"]), ha="center", va="center", - fontsize=11, + fontsize=8, fontweight="bold", color="white", zorder=10, ) -# Create custom legend using matplotlib patches to avoid seaborn override -handles = [ - mpatches.Patch(facecolor=category_palette[cat], edgecolor="white", linewidth=1.5, label=cat) +# Category legend +cat_handles = [ + mpatches.Patch(facecolor=category_palette[cat], edgecolor=PAGE_BG, linewidth=0.5, label=cat) for cat in category_names ] -legend = ax.legend( - handles=handles, +cat_legend = ax.legend( + handles=cat_handles, title="Business Type", loc="upper left", - fontsize=14, - title_fontsize=16, + fontsize=8, + title_fontsize=9, framealpha=0.95, - edgecolor="gray", + facecolor=ELEVATED_BG, + edgecolor=INK_SOFT, ) -legend.get_frame().set_linewidth(1.5) +cat_legend.get_title().set_color(INK) +for text in cat_legend.get_texts(): + text.set_color(INK_SOFT) + +# Size legend — labels derived from actual computed cluster size ranges +small_label = f"{count_min} pt" if count_min == count_p33 else f"{count_min}–{count_p33} pts" +medium_label = f"{count_p33 + 1} pt" if count_p33 + 1 == count_p66 else f"{count_p33 + 1}–{count_p66} pts" +large_label = f"{count_p66 + 1}–{count_max} pts" if count_p66 + 1 < count_max else f"{count_max}+ pts" -# Add size legend for cluster markers size_handles = [ - Line2D([0], [0], marker="o", color="w", markerfacecolor="gray", markersize=8, alpha=0.7, label="2-5 points"), - Line2D([0], [0], marker="o", color="w", markerfacecolor="gray", markersize=14, alpha=0.7, label="6-15 points"), - Line2D([0], [0], marker="o", color="w", markerfacecolor="gray", markersize=20, alpha=0.7, label="16+ points"), + Line2D([0], [0], marker="o", color="w", markerfacecolor=INK_SOFT, markersize=5, alpha=0.7, label=small_label), + Line2D([0], [0], marker="o", color="w", markerfacecolor=INK_SOFT, markersize=9, alpha=0.7, label=medium_label), + Line2D([0], [0], marker="o", color="w", markerfacecolor=INK_SOFT, markersize=13, alpha=0.7, label=large_label), ] size_legend = ax.legend( handles=size_handles, title="Cluster Size", loc="lower left", - fontsize=12, - title_fontsize=14, + fontsize=8, + title_fontsize=9, framealpha=0.95, - edgecolor="gray", + facecolor=ELEVATED_BG, + edgecolor=INK_SOFT, ) -size_legend.get_frame().set_linewidth(1.5) -ax.add_artist(legend) # Re-add the first legend +size_legend.get_title().set_color(INK) +for text in size_legend.get_texts(): + text.set_color(INK_SOFT) +ax.add_artist(cat_legend) -# Styling with seaborn-friendly axis formatting -ax.set_xlabel("Longitude (°)", fontsize=20) -ax.set_ylabel("Latitude (°)", fontsize=20) -ax.set_title("map-marker-clustered · seaborn · pyplots.ai", fontsize=24, fontweight="bold", pad=15) -ax.tick_params(axis="both", labelsize=16) +# Style +ax.set_xlabel("Longitude (°)", fontsize=10, color=INK) +ax.set_ylabel("Latitude (°)", fontsize=10, color=INK) +ax.set_title("map-marker-clustered · python · seaborn · anyplot.ai", fontsize=12, fontweight="medium", color=INK) +ax.tick_params(axis="both", labelsize=8, colors=INK_SOFT) -# Customize grid using seaborn despine and grid settings sns.despine(ax=ax, left=False, bottom=False) -ax.grid(True, alpha=0.15, linestyle="-", color="lightgray") +ax.spines["left"].set_color(INK_SOFT) +ax.spines["bottom"].set_color(INK_SOFT) +ax.grid(True, alpha=0.10, linestyle="-", linewidth=0.6, color=INK) -# Add annotation about clustering +# Summary annotation (lower right, away from size legend) ax.text( - 0.02, + 0.98, 0.02, f"{n_points} locations · {len(cluster_stats)} clusters", transform=ax.transAxes, - fontsize=13, - ha="left", + fontsize=7, + ha="right", va="bottom", style="italic", - color="dimgray", - bbox={"boxstyle": "round,pad=0.3", "facecolor": "white", "alpha": 0.8, "edgecolor": "lightgray"}, + color=INK_MUTED, + bbox={"boxstyle": "round,pad=0.3", "facecolor": ELEVATED_BG, "alpha": 0.85, "edgecolor": INK_SOFT}, ) -plt.tight_layout() -plt.savefig("plot.png", dpi=300, bbox_inches="tight") +# Save +plt.savefig(f"plot-{THEME}.png", dpi=400, facecolor=PAGE_BG) diff --git a/plots/map-marker-clustered/metadata/python/seaborn.yaml b/plots/map-marker-clustered/metadata/python/seaborn.yaml index 000d213f62..3fa772ecbb 100644 --- a/plots/map-marker-clustered/metadata/python/seaborn.yaml +++ b/plots/map-marker-clustered/metadata/python/seaborn.yaml @@ -1,205 +1,274 @@ library: seaborn +language: python specification_id: map-marker-clustered created: '2026-01-20T18:49:59Z' -updated: '2026-01-20T19:54:46Z' -generated_by: claude-opus-4-5-20251101 -workflow_run: 21183365546 +updated: '2026-05-23T20:12:41Z' +generated_by: claude-sonnet +workflow_run: 26331629481 issue: 3766 -python_version: 3.13.11 +language_version: 3.13.13 library_version: 0.13.2 -preview_url: https://storage.googleapis.com/anyplot-images/plots/map-marker-clustered/seaborn/plot.png -preview_html: null -quality_score: 90 +preview_url_light: https://storage.googleapis.com/anyplot-images/plots/map-marker-clustered/python/seaborn/plot-light.png +preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/map-marker-clustered/python/seaborn/plot-dark.png +preview_html_light: null +preview_html_dark: null +quality_score: 89 review: strengths: - - Effective use of seaborn's colorblind palette for accessibility - - Good implementation of hierarchical clustering using sklearn to group nearby markers - - Clean two-layer visualization with background individual points and foreground - cluster markers - - Appropriate use of alpha blending for overlapping data density - - Informative annotations showing total locations and cluster count - - Proper geographic context with stylized water boundaries + - 'Excellent multi-layer design: background scatter at alpha=0.08 + KDE density + contours + sized cluster markers creates genuine depth without visual clutter' + - 'Geographic context (stylized water boundaries + land fill) adds meaningful real-world + orientation — theme-adaptive land fill (#f5f5dc light / #2a2a20 dark) shows extra + care' + - Cluster markers correctly reflect dominant category color and display count labels + in white — satisfies both spec requirements (count display + category color) cleanly + - Hierarchical clustering with AgglomerativeClustering is a correct and idiomatic + static approximation of dynamic zoom-based clustering for a non-interactive library + - 'Full palette compliance: #009E73 first for Coffee Shop, canonical order positions + 1-4, both renders on correct backgrounds (#FAF8F1 / #1A1A17)' + - Dual legends (Business Type + Cluster Size) with computed data-driven size labels + (e.g., '1–2 pts', '3 pt', '4–7 pts') rather than hardcoded placeholders + - sns.kdeplot() density overlay is a seaborn-idiomatic distinctive touch that adds + real analytical value to the map + - 'All text sizes follow style-guide: title 12pt, axis labels 10pt, tick labels + and legend 8pt' weaknesses: - - Size legend ranges ("2-5 points", "6-15 points", "16+ points") are arbitrary and - don't reflect actual computed cluster sizes in the data - - Grid styling could be more subtle (currently visible but acceptable) - image_description: 'The plot displays a clustered marker map visualization of 500 - business locations across the NYC region. The visualization uses a scatter plot - approach with geographic coordinates (Longitude on X-axis ranging from -74.4 to - -73.4, Latitude on Y-axis ranging from 40.0 to 41.2). Individual data points are - shown as small, semi-transparent markers in the background, while cluster markers - are displayed as larger circles with white borders, sized according to cluster - count. Clusters containing more than 4 points display white count labels centered - within them. Four business categories are color-coded using seaborn''s colorblind - palette: Coffee Shop (dark blue), Restaurant (yellow/gold), Bookstore (green), - and Gym (pink/coral). A category legend appears in the upper left. The title follows - the correct format "map-marker-clustered · seaborn · pyplots.ai". An annotation - in the lower right indicates "500 locations · 185 clusters". The background has - a subtle beige land area fill and light blue boundary lines representing water - features.' + - In the densely-clustered central zone (lat ~40.6–40.9, lon ~-74.0 to -73.7), many + cluster circles overlap and count labels inside small circles (~8pt) can be difficult + to read — consider increasing count label fontsize to 9pt or limiting labels to + clusters with count > 2 + - 'DE-01 could be elevated further: the geographic line segments (Hudson River, + LI Sound, coast) are low-detail and don''t add substantial orientation beyond + the axis ticks; more recognizable borough boundaries or neighborhood labels would + improve the basemap' + - 'LM-02: seaborn KDE contour is a good distinctive feature but only one seaborn-specific + technique is used; size+hue encoding in scatterplot is also good seaborn usage + but not uniquely seaborn' + image_description: |- + Light render (plot-light.png): + Background: Warm off-white (#FAF8F1 surface), clearly not pure white — correct. + Chrome: Title "map-marker-clustered · python · seaborn · anyplot.ai" at 12pt is fully readable. X-axis "Longitude (°)" and Y-axis "Latitude (°)" at 10pt are clear. Tick labels at 8pt readable. Both legend panels ("Business Type" upper-left, "Cluster Size" lower-left) have readable labels at 8pt. Summary annotation "500 locations · 185 clusters" in lower-right corner in italic at 7pt — readable at full resolution. + Data: 4-color categorical palette (#009E73 Coffee Shop, #9418DB Restaurant, #B71D27 Bookstore, #16B8F3 Gym). Cluster markers sized proportionally (small ~50pt² to large ~500pt²). Background scatter at alpha=0.08 visible. KDE contours in muted gray (INK_MUTED) at alpha=0.35. Geographic water lines at alpha=0.4. Count labels in white inside cluster markers. + Legibility verdict: PASS — all elements are readable; first series correctly #009E73. + + Dark render (plot-dark.png): + Background: Warm near-black (#1A1A17 surface), not pure black — correct. + Chrome: Title and axis labels are rendered in light ink (#F0EFE8) — clearly readable on dark surface. Tick labels at INK_SOFT (#B8B7B0) visible. Both legend panels have light-colored text, legend backgrounds at ELEVATED_BG (#242420), borders at INK_SOFT — all readable. Count labels remain white — excellent contrast on dark. Summary annotation readable. + Data: Data colors identical to light render (#009E73, #9418DB, #B71D27, #16B8F3 unchanged). Chrome flipped correctly: background is near-black, land fill is #2a2a20 (dark adaptation). KDE contours use INK_MUTED for dark (#A8A79F), subtle but visible. + Legibility verdict: PASS — no dark-on-dark failures detected; all chrome elements correctly flip while data colors stay constant. 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 at 24pt bold, axis labels at 20pt, tick labels at 16pt, all - clearly readable + comment: 'All font sizes follow style-guide (title 12pt, axis 10pt, ticks/legend + 8pt). Both renders fully readable. Minor deduction: count labels at 8pt + inside small cluster circles at dense central zone are a bit tight.' - id: VQ-02 name: No Overlap - score: 8 - max: 8 + score: 5 + max: 6 passed: true - comment: No overlapping text elements, cluster labels are well-positioned - within markers + comment: Legend boxes don't overlap data. Minor overlap between cluster circles + in the dense central zone (inherent to clustering visualization) causes + some count labels to appear in overlapping regions. -1 for this expected + but notable visual crowding. - id: VQ-03 name: Element Visibility - score: 7 - max: 8 + score: 6 + max: 6 passed: true - comment: Cluster markers appropriately sized with white edges; individual - points appropriately small with alpha=0.3 + comment: 'All layers visible: background scatter at alpha=0.08, KDE contours + at alpha=0.35, cluster markers at alpha=0.85 with sizes 50-500. Geographic + context visible. White count labels clearly read on colored markers.' - id: VQ-04 name: Color Accessibility - score: 5 - max: 5 + score: 2 + max: 2 passed: true - comment: Uses seaborn colorblind palette, all four categories clearly distinguishable + comment: 4-color anyplot palette (CVD-safe by design). White labels on colored + cluster markers provide strong contrast. No red-green sole signal. - id: VQ-05 - name: Layout Balance + name: Layout & Canvas score: 4 - max: 5 + max: 4 passed: true - comment: Good use of canvas space, data fills the plot area well, legends - positioned appropriately + comment: Canvas gate passed (3200x1800). Dual legends well-positioned (upper-left + + lower-left). Summary annotation lower-right. No overflow or clipping. + Landscape format correct for geographic map. - id: VQ-06 - name: Axis Labels + name: Axis Labels & Title score: 2 max: 2 passed: true - comment: 'Descriptive labels with units: Longitude (°), Latitude (°)' + comment: 'Title in correct format: ''map-marker-clustered · python · seaborn + · anyplot.ai''. Axis labels include units: ''Longitude (°)'' and ''Latitude + (°)''.' - id: VQ-07 - name: Grid & Legend - score: 0 + name: Palette Compliance + score: 2 max: 2 - passed: false - comment: Size legend ranges don't accurately reflect actual cluster sizes - in the data + passed: true + comment: 'First series #009E73 (Coffee Shop). Canonical order positions 1-4. + Backgrounds #FAF8F1 light / #1A1A17 dark. Data colors identical across themes. + No non-anyplot cmaps used.' + design_excellence: + score: 14 + max: 20 + items: + - id: DE-01 + name: Aesthetic Sophistication + score: 6 + max: 8 + passed: true + comment: Above default (4/8). Multi-layer composition (KDE + background scatter + + sized cluster markers), theme-adaptive land fill, dual legend system with + computed size labels, summary annotation. Basemap lines are minimal but + present. + - id: DE-02 + name: Visual Refinement + score: 4 + max: 6 + passed: true + comment: Above default (2/6). L-shaped spines via sns.despine() appropriate + for geographic scatter. Grid at alpha=0.10. Edge colors on cluster markers + match PAGE_BG (visual definition). Legend framealpha=0.95 with matching + borders. KDE and background scatter at low alpha create layered depth. + - id: DE-03 + name: Data Storytelling + score: 4 + max: 6 + passed: true + comment: 'Above default (2/6). Clear visual hierarchy: background scatter + → KDE density contours → cluster markers. Size encoding tells count story. + Geographic context orients viewer. Count labels make individual cluster + data explicit.' spec_compliance: - score: 22 - max: 25 + score: 15 + max: 15 items: - id: SC-01 name: Plot Type - score: 8 - max: 8 - passed: true - comment: Correctly implements clustered marker map concept using hierarchical - clustering - - id: SC-02 - name: Data Mapping score: 5 max: 5 passed: true - comment: Lat/lon correctly assigned to Y/X axes - - id: SC-03 + comment: Clustered marker map is correctly implemented as a static approximation + using AgglomerativeClustering — the right approach for seaborn (static library). + Spec's interactive zoom-based clustering is appropriately silently omitted + per seaborn.md rules. + - id: SC-02 name: Required Features score: 4 - max: 5 + max: 4 passed: true - comment: Shows clusters with counts, category coloring, dominant category; - missing interactive zoom (static limitation) - - id: SC-04 - name: Data Range + comment: Count labels on clusters ✓, distinct colors by category with dominant-category + cluster coloring ✓, geographic basemap context ✓. Interactive features (zoom + transitions, click-to-zoom, hover spider lines) silently omitted per static + library rules ✓. + - id: SC-03 + name: Data Mapping score: 3 max: 3 passed: true - comment: All data visible within axes - - id: SC-05 - name: Legend Accuracy - score: 0 - max: 2 - passed: false - comment: Size legend ranges don't accurately reflect actual cluster sizes - shown - - id: SC-06 - name: Title Format - score: 2 - max: 2 + comment: X=Longitude, Y=Latitude ✓. Cluster marker size=count ✓. Cluster color=dominant_category + ✓. Background scatter color=category ✓. + - id: SC-04 + name: Title & Legend + score: 3 + max: 3 passed: true - comment: 'Correct format: map-marker-clustered · seaborn · pyplots.ai' + comment: Title 'map-marker-clustered · python · seaborn · anyplot.ai' correct. + Business Type legend with all 4 categories. Cluster Size legend with computed + size ranges. data_quality: - score: 19 - 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 clustering at multiple densities, category distribution, count - labels + comment: Demonstrates clustering (agglomerative), count-based sizing, category + coloring, geographic scatter, KDE density overlay, basemap context. Comprehensive + coverage of map-marker-clustered features. - id: DQ-02 name: Realistic Context - score: 7 - max: 7 + score: 5 + max: 5 passed: true - comment: NYC business locations is a realistic, neutral scenario + comment: NYC business locations (Coffee Shop, Restaurant, Bookstore, Gym) + — realistic and neutral. Coordinates in NYC region (lat ~40.7, lon ~-74.0). + 500 points, 185 clusters is realistic for a city-scale dataset. - id: DQ-03 name: Appropriate Scale - score: 5 - max: 5 + score: 4 + max: 4 passed: true - comment: NYC-area coordinates (40.0-41.2°N, 73.4-74.4°W) are realistic + comment: 500 points at distance_threshold=0.18 producing 185 clusters is well-tuned. + Neighborhood-based generation with std=0.08 produces realistic geographic + dispersion. code_quality: - score: 9 + score: 10 max: 10 items: - id: CQ-01 name: KISS Structure - score: 2 + score: 3 max: 3 passed: true - comment: Reasonably organized but uses some helper iterations + comment: Top-level script, no functions or classes. Clean sequential flow. - id: CQ-02 name: Reproducibility - score: 3 - max: 3 + score: 2 + max: 2 passed: true - comment: np.random.seed(42) set at beginning + comment: np.random.seed(42) ensures deterministic output. - id: CQ-03 name: Clean Imports score: 2 max: 2 passed: true - comment: All imports are used + comment: 'All imports are used: os, mpatches, plt, np, pd, sns, Line2D, AgglomerativeClustering.' - id: CQ-04 - name: No Deprecated API - score: 1 - max: 1 + name: Code Elegance + score: 2 + max: 2 passed: true - comment: Uses current seaborn and matplotlib APIs + comment: No fake interactivity. groupby().agg() is clean. Computed size labels + from quantiles is thoughtful. Appropriate complexity. - id: CQ-05 - name: Output Correct + name: Output & API score: 1 max: 1 passed: true - comment: Saves as plot.png with correct dpi and bbox_inches - library_features: - score: 4 - max: 5 + comment: Saves as plot-{THEME}.png with dpi=400. No bbox_inches='tight' (correct). + Current seaborn 0.13.2 API. + library_mastery: + score: 7 + max: 10 items: - - id: LF-01 - name: Distinctive Features + - id: LM-01 + name: Idiomatic Usage score: 4 max: 5 passed: true - comment: Good use of sns.scatterplot with hue/size encoding, sns.set_theme, - sns.despine, colorblind palette + comment: sns.set_theme() with rc dict for theme tokens, sns.scatterplot() + with hue+size encoding, sns.despine(), ax-level API throughout. Good seaborn + idioms. + - id: LM-02 + name: Distinctive Features + score: 3 + max: 5 + passed: true + comment: sns.kdeplot() density overlay is a genuine seaborn-distinctive feature + used meaningfully. Multi-layer scatterplot with hue+size is also idiomatic. + Could use more seaborn-unique features (e.g., sns.rugplot for marginals). verdict: APPROVED impl_tags: dependencies: @@ -208,13 +277,14 @@ impl_tags: - annotations - custom-legend - patches - - layer-composition patterns: - data-generation - groupby-aggregation - iteration-over-groups + - explicit-figure dataprep: - hierarchical-clustering + - kde styling: - alpha-blending - edge-highlighting