|
1 | 1 | """ pyplots.ai |
2 | 2 | violin-basic: Basic Violin Plot |
3 | | -Library: altair 6.0.0 | Python 3.13.11 |
4 | | -Quality: 92/100 | Created: 2025-12-23 |
| 3 | +Library: altair 6.0.0 | Python 3.14.3 |
| 4 | +Quality: 94/100 | Updated: 2026-02-21 |
5 | 5 | """ |
6 | 6 |
|
7 | 7 | import altair as alt |
|
16 | 16 |
|
17 | 17 | for cat in categories: |
18 | 18 | if cat == "Engineering": |
19 | | - # Higher salaries with moderate spread |
20 | 19 | values = np.random.normal(92000, 16000, 150) |
21 | 20 | elif cat == "Marketing": |
22 | | - # Mid-range salaries |
23 | 21 | values = np.random.normal(70000, 13000, 150) |
24 | 22 | elif cat == "Sales": |
25 | 23 | # Bimodal: base salary + high performers with commissions |
26 | 24 | values = np.concatenate([np.random.normal(50000, 8000, 75), np.random.normal(92000, 11000, 75)]) |
27 | 25 | else: # Support |
28 | | - # Lower salary, tighter distribution |
29 | 26 | values = np.random.normal(55000, 10000, 150) |
30 | 27 |
|
31 | 28 | for v in values: |
32 | 29 | data.append({"Department": cat, "Salary": v}) |
33 | 30 |
|
34 | 31 | df = pd.DataFrame(data) |
35 | 32 |
|
36 | | -# Calculate statistics for quartile markers |
37 | | -stats = ( |
38 | | - df.groupby("Department")["Salary"] |
39 | | - .agg(q1=lambda x: x.quantile(0.25), median=lambda x: x.quantile(0.5), q3=lambda x: x.quantile(0.75)) |
40 | | - .reset_index() |
41 | | -) |
42 | | - |
43 | | -# Merge stats for layering |
44 | | -df_with_stats = df.merge(stats, on="Department") |
| 33 | +# Department order: unimodal distributions first, bimodal Sales last as focal point |
| 34 | +dept_order = ["Support", "Marketing", "Engineering", "Sales"] |
45 | 35 |
|
46 | | -# Colors - Python palette |
47 | | -colors = ["#306998", "#FFD43B", "#4B8BBE", "#FFE873"] |
48 | | -color_scale = alt.Scale(domain=categories, range=colors) |
| 36 | +# Colors - four fully distinct colorblind-safe hues with Python Blue |
| 37 | +# brown, purple, Python Blue, orange — each maximally distinct |
| 38 | +palette = ["#8B6C42", "#9467BD", "#306998", "#E5832D"] |
| 39 | +color_scale = alt.Scale(domain=dept_order, range=palette) |
49 | 40 |
|
50 | | -# Base chart |
51 | | -base = alt.Chart(df_with_stats) |
| 41 | +base = alt.Chart(df) |
52 | 42 |
|
53 | 43 | # Violin shape using kernel density transform |
54 | 44 | violin = ( |
|
69 | 59 | axis=alt.Axis(labels=False, values=[0], grid=False, ticks=False), |
70 | 60 | ), |
71 | 61 | color=alt.Color("Department:N", scale=color_scale, legend=None), |
| 62 | + tooltip=[alt.Tooltip("Department:N"), alt.Tooltip("Salary:Q", format="$,.0f")], |
72 | 63 | ) |
73 | 64 | ) |
74 | 65 |
|
75 | | -# IQR rule (black vertical line) |
76 | | -quartile_rule = base.mark_rule(color="black", strokeWidth=5).encode(y="q1:Q", y2="q3:Q") |
| 66 | +# IQR rule via declarative aggregate (one rule per department) |
| 67 | +quartile_rule = ( |
| 68 | + base.transform_aggregate(q1="q1(Salary)", q3="q3(Salary)", groupby=["Department"]) |
| 69 | + .mark_rule(color="#1a1a1a", strokeWidth=5) |
| 70 | + .encode(y="q1:Q", y2="q3:Q") |
| 71 | +) |
77 | 72 |
|
78 | | -# Median point (white dot with black border) |
79 | | -median_point = base.mark_point(color="white", size=250, filled=True, strokeWidth=3, stroke="black").encode(y="median:Q") |
| 73 | +# Median point via declarative aggregate (one dot per department) |
| 74 | +median_point = ( |
| 75 | + base.transform_aggregate(med="median(Salary)", groupby=["Department"]) |
| 76 | + .mark_point(color="white", size=250, filled=True, strokeWidth=3, stroke="#1a1a1a") |
| 77 | + .encode( |
| 78 | + y="med:Q", tooltip=[alt.Tooltip("Department:N"), alt.Tooltip("med:Q", title="Median Salary", format="$,.0f")] |
| 79 | + ) |
| 80 | +) |
80 | 81 |
|
81 | 82 | # Combine layers and facet by department |
82 | 83 | chart = ( |
|
85 | 86 | column=alt.Column( |
86 | 87 | "Department:N", |
87 | 88 | header=alt.Header(labelFontSize=20, labelOrient="bottom", title=None, labelPadding=15), |
88 | | - sort=categories, |
| 89 | + sort=dept_order, |
89 | 90 | ) |
90 | 91 | ) |
91 | 92 | .resolve_scale(x="independent") |
92 | 93 | .properties(title=alt.Title("violin-basic · altair · pyplots.ai", fontSize=28, anchor="middle")) |
93 | 94 | .configure_facet(spacing=20) |
94 | 95 | .configure_view(stroke=None, continuousWidth=350, continuousHeight=750) |
95 | | - .configure_axis(labelFontSize=18, titleFontSize=22, gridOpacity=0.3, gridDash=[3, 3]) |
| 96 | + .configure_axis(labelFontSize=18, titleFontSize=22, gridOpacity=0.2, gridDash=[3, 3]) |
96 | 97 | ) |
97 | 98 |
|
98 | 99 | # Save outputs |
|
0 commit comments