|
1 | 1 | """ pyplots.ai |
2 | 2 | violin-basic: Basic Violin Plot |
3 | | -Library: letsplot 4.8.1 | Python 3.13.11 |
4 | | -Quality: 91/100 | Created: 2025-12-23 |
| 3 | +Library: letsplot 4.8.2 | Python 3.14.3 |
| 4 | +Quality: 90/100 | Updated: 2026-02-21 |
5 | 5 | """ |
6 | 6 |
|
7 | 7 | import numpy as np |
|
15 | 15 | # Data |
16 | 16 | np.random.seed(42) |
17 | 17 |
|
18 | | -categories = ["Engineering", "Marketing", "Sales", "Design"] |
19 | | -colors = ["#306998", "#FFD43B", "#4B8BBE", "#FFE873"] |
| 18 | +# Ordered by median salary (high → low) for visual storytelling |
| 19 | +dept_order = ["Engineering", "Design", "Marketing", "Sales"] |
| 20 | +palette = ["#306998", "#2E8B57", "#E8A317", "#E07A5F"] |
20 | 21 |
|
21 | | -# Generate realistic salary distributions per department |
22 | 22 | data = [] |
23 | | -distributions = { |
24 | | - "Engineering": {"mean": 95000, "std": 20000, "n": 200}, |
25 | | - "Marketing": {"mean": 75000, "std": 15000, "n": 150}, |
26 | | - "Sales": {"mean": 70000, "std": 25000, "n": 180}, |
27 | | - "Design": {"mean": 80000, "std": 18000, "n": 120}, |
28 | | -} |
29 | 23 |
|
30 | | -for cat in categories: |
31 | | - dist = distributions[cat] |
32 | | - values = np.random.normal(dist["mean"], dist["std"], dist["n"]) |
33 | | - values = np.clip(values, 30000, 200000) # Realistic salary bounds |
34 | | - for v in values: |
35 | | - data.append({"Department": cat, "Salary": v}) |
| 24 | +# Engineering: bimodal (junior ~$70k + senior ~$115k) — showcases violin strength |
| 25 | +eng_junior = np.random.normal(70000, 8000, 80) |
| 26 | +eng_senior = np.random.normal(115000, 12000, 120) |
| 27 | +eng_values = np.clip(np.concatenate([eng_junior, eng_senior]), 30000, 200000) |
| 28 | +for v in eng_values: |
| 29 | + data.append({"Department": "Engineering", "Salary": v}) |
| 30 | + |
| 31 | +# Design: moderate spread, roughly normal |
| 32 | +design_values = np.random.normal(80000, 18000, 120) |
| 33 | +design_values = np.clip(design_values, 30000, 200000) |
| 34 | +for v in design_values: |
| 35 | + data.append({"Department": "Design", "Salary": v}) |
| 36 | + |
| 37 | +# Marketing: narrower with a small cluster of high earners |
| 38 | +mkt_base = np.random.normal(72000, 12000, 130) |
| 39 | +mkt_high = np.random.normal(105000, 8000, 20) |
| 40 | +mkt_values = np.clip(np.concatenate([mkt_base, mkt_high]), 30000, 200000) |
| 41 | +for v in mkt_values: |
| 42 | + data.append({"Department": "Marketing", "Salary": v}) |
| 43 | + |
| 44 | +# Sales: right-skewed (many moderate earners, few top performers) |
| 45 | +sales_values = np.random.exponential(20000, 180) + 45000 |
| 46 | +sales_values = np.clip(sales_values, 30000, 200000) |
| 47 | +for v in sales_values: |
| 48 | + data.append({"Department": "Sales", "Salary": v}) |
36 | 49 |
|
37 | 50 | df = pd.DataFrame(data) |
38 | 51 |
|
39 | 52 | # Plot |
40 | 53 | plot = ( |
41 | 54 | ggplot(df, aes(x="Department", y="Salary", fill="Department")) # noqa: F405 |
42 | 55 | + geom_violin( # noqa: F405 |
43 | | - quantiles=[0.25, 0.5, 0.75], # Show quartiles including median |
44 | | - quantile_lines=True, # Draw lines at quantiles |
45 | | - size=1.5, # Border thickness |
46 | | - alpha=0.8, |
47 | | - trim=False, # Show full tails |
| 56 | + quantiles=[0.25, 0.5, 0.75], quantile_lines=True, size=1.2, alpha=0.85, trim=False, color="#2C3E50" |
| 57 | + ) |
| 58 | + + scale_x_discrete(limits=dept_order) # noqa: F405 |
| 59 | + + scale_fill_manual(values=dict(zip(dept_order, palette, strict=True))) # noqa: F405 |
| 60 | + + scale_y_continuous( # noqa: F405 |
| 61 | + format="${,.0f}" |
48 | 62 | ) |
49 | | - + scale_fill_manual(values=colors) # noqa: F405 |
50 | 63 | + labs( # noqa: F405 |
51 | | - x="Department", y="Salary ($)", title="violin-basic · lets-plot · pyplots.ai" |
| 64 | + x="Department", y="Salary", title="violin-basic \u00b7 letsplot \u00b7 pyplots.ai" |
52 | 65 | ) |
53 | 66 | + theme_minimal() # noqa: F405 |
54 | 67 | + theme( # noqa: F405 |
55 | 68 | axis_title=element_text(size=20), # noqa: F405 |
56 | 69 | axis_text=element_text(size=16), # noqa: F405 |
57 | 70 | plot_title=element_text(size=24), # noqa: F405 |
58 | | - legend_position="none", # Legend not needed, x-axis shows categories |
| 71 | + legend_position="none", |
| 72 | + panel_grid_major_x=element_blank(), # noqa: F405 |
| 73 | + axis_ticks=element_blank(), # noqa: F405 |
59 | 74 | ) |
60 | 75 | + ggsize(1600, 900) # noqa: F405 |
61 | 76 | ) |
62 | 77 |
|
63 | | -# Save PNG (scale 3x to get 4800 × 2700 px) |
| 78 | +# Save |
64 | 79 | export_ggsave(plot, filename="plot.png", path=".", scale=3) |
65 | | - |
66 | | -# Save HTML for interactive version |
67 | 80 | export_ggsave(plot, filename="plot.html", path=".") |
0 commit comments