|
1 | 1 | """ pyplots.ai |
2 | 2 | violin-basic: Basic Violin Plot |
3 | | -Library: matplotlib 3.10.8 | Python 3.13.11 |
4 | | -Quality: 92/100 | Created: 2025-12-23 |
| 3 | +Library: matplotlib 3.10.8 | Python 3.14.3 |
| 4 | +Quality: 92/100 | Updated: 2026-02-21 |
5 | 5 | """ |
6 | 6 |
|
| 7 | +import matplotlib.patheffects as pe |
7 | 8 | import matplotlib.pyplot as plt |
8 | 9 | import numpy as np |
9 | 10 |
|
10 | 11 |
|
11 | | -# Data - simulated test scores across different schools |
| 12 | +# Data - simulated test scores (0-100) across different schools |
12 | 13 | np.random.seed(42) |
13 | | -categories = ["School A", "School B", "School C", "School D"] |
| 14 | +categories = ["Lincoln HS", "Roosevelt Acad.", "Jefferson HS", "Hamilton Prep"] |
14 | 15 | data = [ |
15 | | - np.random.normal(75, 10, 150), # School A: centered around 75 |
16 | | - np.random.normal(82, 8, 150), # School B: higher scores, less spread |
17 | | - np.random.normal(68, 15, 150), # School C: lower average, more spread |
18 | | - np.random.normal(78, 12, 150), # School D: moderate |
| 16 | + np.clip(np.random.normal(75, 10, 150), 0, 100), # Lincoln: normal, centered ~75 |
| 17 | + np.clip(np.random.normal(85, 6, 150), 0, 100), # Roosevelt: high, tight cluster |
| 18 | + np.clip(np.random.normal(62, 15, 150), 0, 100), # Jefferson: lower, wide spread |
| 19 | + np.clip( |
| 20 | + np.concatenate([np.random.normal(70, 5, 80), np.random.normal(88, 4, 70)]), 0, 100 |
| 21 | + ), # Hamilton: bimodal (two subgroups) |
19 | 22 | ] |
20 | 23 |
|
21 | | -# Create plot (4800x2700 px) |
| 24 | +# Multi-series palette starting with Python Blue; warm accent for bimodal Hamilton |
| 25 | +colors = ["#306998", "#5BA58B", "#7A6FB5", "#D4853F"] |
| 26 | +edge_colors = ["#1E4060", "#3A7460", "#524A80", "#9A5F2A"] |
| 27 | + |
| 28 | +# Plot |
22 | 29 | fig, ax = plt.subplots(figsize=(16, 9)) |
23 | 30 |
|
24 | | -# Create violin plot with quartile markers |
25 | | -parts = ax.violinplot(data, positions=range(len(categories)), showmeans=False, showmedians=True, showextrema=True) |
| 31 | +parts = ax.violinplot( |
| 32 | + data, |
| 33 | + positions=range(len(categories)), |
| 34 | + quantiles=[[0.25, 0.5, 0.75]] * len(categories), |
| 35 | + showmeans=False, |
| 36 | + showmedians=False, |
| 37 | + showextrema=False, |
| 38 | + bw_method=0.3, |
| 39 | + widths=0.75, |
| 40 | +) |
26 | 41 |
|
27 | | -# Style the violins with Python Blue |
28 | | -for pc in parts["bodies"]: |
29 | | - pc.set_facecolor("#306998") |
30 | | - pc.set_edgecolor("#1e4a6e") |
31 | | - pc.set_alpha(0.7) |
| 42 | +# Style each violin body with a distinct color |
| 43 | +for i, pc in enumerate(parts["bodies"]): |
| 44 | + pc.set_facecolor(colors[i]) |
| 45 | + pc.set_edgecolor(edge_colors[i]) |
| 46 | + pc.set_alpha(0.8) |
32 | 47 | pc.set_linewidth(2) |
33 | 48 |
|
34 | | -# Style the lines (median, min, max) |
35 | | -parts["cmedians"].set_color("#FFD43B") |
36 | | -parts["cmedians"].set_linewidth(3) |
37 | | -parts["cmins"].set_color("#1e4a6e") |
38 | | -parts["cmins"].set_linewidth(2) |
39 | | -parts["cmaxes"].set_color("#1e4a6e") |
40 | | -parts["cmaxes"].set_linewidth(2) |
41 | | -parts["cbars"].set_color("#1e4a6e") |
42 | | -parts["cbars"].set_linewidth(2) |
43 | | - |
44 | | -# Add quartile markers (Q1 and Q3) as box indicators |
45 | | -quartile1 = [np.percentile(d, 25) for d in data] |
46 | | -quartile3 = [np.percentile(d, 75) for d in data] |
47 | | - |
48 | | -# Draw thin boxes for interquartile range |
49 | | -for i, (q1, q3) in enumerate(zip(quartile1, quartile3, strict=True)): |
50 | | - ax.vlines(i, q1, q3, color="#1e4a6e", linewidth=6, zorder=3) |
51 | | - |
52 | | -# Labels and styling (scaled font sizes for 4800x2700) |
| 49 | +# Quantile lines with path effects for legibility against colored bodies |
| 50 | +q_colors = ["white", "#FFD43B", "white"] * len(categories) |
| 51 | +q_widths = [2.5, 4, 2.5] * len(categories) |
| 52 | +parts["cquantiles"].set_colors(q_colors) |
| 53 | +parts["cquantiles"].set_linewidths(q_widths) |
| 54 | +parts["cquantiles"].set_path_effects([pe.Stroke(linewidth=6, foreground="black", alpha=0.3), pe.Normal()]) |
| 55 | + |
| 56 | +# Labels and styling |
53 | 57 | ax.set_xticks(range(len(categories))) |
54 | 58 | ax.set_xticklabels(categories) |
55 | 59 | ax.set_xlabel("School", fontsize=20) |
56 | 60 | ax.set_ylabel("Test Score (points)", fontsize=20) |
57 | | -ax.set_title("violin-basic · matplotlib · pyplots.ai", fontsize=24) |
| 61 | +ax.set_title("violin-basic \u00b7 matplotlib \u00b7 pyplots.ai", fontsize=24, fontweight="medium") |
58 | 62 | ax.tick_params(axis="both", labelsize=16) |
59 | | -ax.grid(True, alpha=0.3, linestyle="--", axis="y") |
| 63 | + |
| 64 | +ax.spines["top"].set_visible(False) |
| 65 | +ax.spines["right"].set_visible(False) |
| 66 | +ax.yaxis.grid(True, alpha=0.2, linewidth=0.8) |
| 67 | + |
| 68 | +# Subtle annotation highlighting Hamilton Prep's bimodal distribution |
| 69 | +ax.annotate( |
| 70 | + "Two distinct\nperformance groups", |
| 71 | + xy=(3, 75), |
| 72 | + xytext=(3, 45), |
| 73 | + fontsize=13, |
| 74 | + color="#9A5F2A", |
| 75 | + fontstyle="italic", |
| 76 | + ha="center", |
| 77 | + arrowprops={"arrowstyle": "->", "color": "#9A5F2A", "lw": 1.5}, |
| 78 | +) |
60 | 79 |
|
61 | 80 | plt.tight_layout() |
62 | 81 | plt.savefig("plot.png", dpi=300, bbox_inches="tight") |
0 commit comments