Skip to content

Commit b0d4af3

Browse files
feat(plotnine): implement violin-grouped-swarm (#3545)
## Implementation: `violin-grouped-swarm` - plotnine Implements the **plotnine** version of `violin-grouped-swarm`. **File:** `plots/violin-grouped-swarm/implementations/plotnine.py` **Parent Issue:** #3529 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20858843569)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 40ff874 commit b0d4af3

2 files changed

Lines changed: 293 additions & 0 deletions

File tree

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
""" pyplots.ai
2+
violin-grouped-swarm: Grouped Violin Plot with Swarm Overlay
3+
Library: plotnine 0.15.2 | Python 3.13.11
4+
Quality: 91/100 | Created: 2026-01-09
5+
"""
6+
7+
import numpy as np
8+
import pandas as pd
9+
from plotnine import (
10+
aes,
11+
element_line,
12+
element_text,
13+
geom_jitter,
14+
geom_violin,
15+
ggplot,
16+
guide_legend,
17+
guides,
18+
labs,
19+
position_dodge,
20+
scale_color_manual,
21+
scale_fill_manual,
22+
theme,
23+
theme_minimal,
24+
)
25+
26+
27+
# Data - Response times (ms) across task types and expertise levels
28+
np.random.seed(42)
29+
30+
categories = ["Simple", "Moderate", "Complex"]
31+
groups = ["Novice", "Expert"]
32+
n_per_combination = 40
33+
34+
data = []
35+
for category in categories:
36+
for group in groups:
37+
# Base response time varies by complexity
38+
base = {"Simple": 400, "Moderate": 700, "Complex": 1100}[category]
39+
# Experts are faster
40+
if group == "Expert":
41+
base -= 150
42+
# Generate data with varying spreads
43+
spread = {"Simple": 60, "Moderate": 100, "Complex": 150}[category]
44+
values = np.random.normal(base, spread, n_per_combination)
45+
# Add some variation for visual interest
46+
values = np.clip(values, base - 3 * spread, base + 3 * spread)
47+
for v in values:
48+
data.append({"task_type": category, "expertise": group, "response_time": v})
49+
50+
df = pd.DataFrame(data)
51+
# Set category order
52+
df["task_type"] = pd.Categorical(df["task_type"], categories=categories, ordered=True)
53+
df["expertise"] = pd.Categorical(df["expertise"], categories=groups, ordered=True)
54+
55+
# Colors - Python Blue and Yellow
56+
colors = {"Novice": "#306998", "Expert": "#FFD43B"}
57+
58+
# Create plot
59+
plot = (
60+
ggplot(df, aes(x="task_type", y="response_time", fill="expertise", color="expertise"))
61+
+ geom_violin(position=position_dodge(width=0.8), alpha=0.5, size=0.8)
62+
+ geom_jitter(position=position_dodge(width=0.8), size=2.5, alpha=0.8)
63+
+ scale_fill_manual(values=colors, name="Expertise")
64+
+ scale_color_manual(values=colors, name="Expertise")
65+
+ guides(fill=guide_legend(), color=guide_legend())
66+
+ labs(title="violin-grouped-swarm · plotnine · pyplots.ai", x="Task Type", y="Response Time (ms)")
67+
+ theme_minimal()
68+
+ theme(
69+
figure_size=(16, 9),
70+
text=element_text(size=14),
71+
axis_title=element_text(size=20),
72+
axis_text=element_text(size=16),
73+
plot_title=element_text(size=24),
74+
legend_title=element_text(size=18),
75+
legend_text=element_text(size=16),
76+
panel_grid_major=element_line(alpha=0.3),
77+
panel_grid_minor=element_line(alpha=0.15),
78+
)
79+
)
80+
81+
# Save
82+
plot.save("plot.png", dpi=300, verbose=False)
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
library: plotnine
2+
specification_id: violin-grouped-swarm
3+
created: '2026-01-09T16:48:57Z'
4+
updated: '2026-01-09T16:52:15Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20858843569
7+
issue: 3529
8+
python_version: 3.13.11
9+
library_version: 0.15.2
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/violin-grouped-swarm/plotnine/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/violin-grouped-swarm/plotnine/plot_thumb.png
12+
preview_html: null
13+
quality_score: 91
14+
review:
15+
strengths:
16+
- Excellent color palette with high contrast (blue/yellow) that is colorblind-safe
17+
- Clean layered grammar of graphics approach with geom_violin + geom_jitter
18+
- Realistic and well-thought-out data scenario with varying complexity levels and
19+
expertise
20+
- Proper use of position_dodge to align grouped elements
21+
- Text sizing follows guidelines perfectly for 4800x2700 output
22+
weaknesses:
23+
- Legend appears with duplicate Expertise entries (both fill and color scales create
24+
separate legend items)
25+
- Uses geom_jitter for swarm points rather than true swarm positioning - plotnine
26+
lacks native geom_beeswarm
27+
- The guides() call with guide_legend() does not effectively merge the legends
28+
image_description: 'The plot displays a grouped violin chart with swarm overlay
29+
showing response times (in milliseconds) across three task types (Simple, Moderate,
30+
Complex) and two expertise levels (Novice in steel blue, Expert in golden yellow).
31+
Each violin shape effectively shows the distribution of data, with individual
32+
data points rendered as jittered markers inside the violins. The Novice group
33+
consistently shows higher response times than Expert across all task complexity
34+
levels. Response times increase from Simple (~300-500ms) to Moderate (~400-900ms)
35+
to Complex (~600-1400ms). The violins have semi-transparent fills (alpha ~0.5)
36+
allowing the swarm points to remain visible. The title uses the correct format:
37+
''violin-grouped-swarm · plotnine · pyplots.ai''. The legend is positioned on
38+
the right side, clearly labeling the two expertise levels with matching colors.'
39+
criteria_checklist:
40+
visual_quality:
41+
score: 37
42+
max: 40
43+
items:
44+
- id: VQ-01
45+
name: Text Legibility
46+
score: 10
47+
max: 10
48+
passed: true
49+
comment: Title at 24pt, axis labels at 20pt, tick labels at 16pt - all perfectly
50+
readable
51+
- id: VQ-02
52+
name: No Overlap
53+
score: 8
54+
max: 8
55+
passed: true
56+
comment: No overlapping text elements, violins well-spaced
57+
- id: VQ-03
58+
name: Element Visibility
59+
score: 7
60+
max: 8
61+
passed: true
62+
comment: Swarm points visible, though slightly small for the data density;
63+
violins well-sized
64+
- id: VQ-04
65+
name: Color Accessibility
66+
score: 5
67+
max: 5
68+
passed: true
69+
comment: Blue and yellow palette is colorblind-safe, excellent contrast
70+
- id: VQ-05
71+
name: Layout Balance
72+
score: 5
73+
max: 5
74+
passed: true
75+
comment: Good proportions, plot fills canvas well, balanced margins
76+
- id: VQ-06
77+
name: Axis Labels
78+
score: 2
79+
max: 2
80+
passed: true
81+
comment: Y-axis includes units 'Response Time (ms)', X-axis labeled 'Task
82+
Type'
83+
- id: VQ-07
84+
name: Grid & Legend
85+
score: 0
86+
max: 2
87+
passed: false
88+
comment: Grid is subtle (alpha 0.3), but legend shows duplicate entries (Expertise
89+
appears twice with same colors)
90+
spec_compliance:
91+
score: 23
92+
max: 25
93+
items:
94+
- id: SC-01
95+
name: Plot Type
96+
score: 8
97+
max: 8
98+
passed: true
99+
comment: Correct grouped violin plot with swarm overlay
100+
- id: SC-02
101+
name: Data Mapping
102+
score: 5
103+
max: 5
104+
passed: true
105+
comment: Category on x-axis, values on y-axis, grouping by color/hue
106+
- id: SC-03
107+
name: Required Features
108+
score: 3
109+
max: 5
110+
passed: true
111+
comment: Has violins, swarm points, grouping, transparency; however swarm
112+
points use jitter rather than true swarm positioning
113+
- id: SC-04
114+
name: Data Range
115+
score: 3
116+
max: 3
117+
passed: true
118+
comment: All data visible within axes
119+
- id: SC-05
120+
name: Legend Accuracy
121+
score: 2
122+
max: 2
123+
passed: true
124+
comment: Legend correctly identifies Novice and Expert groups
125+
- id: SC-06
126+
name: Title Format
127+
score: 2
128+
max: 2
129+
passed: true
130+
comment: Correct format '{spec-id} · {library} · pyplots.ai'
131+
data_quality:
132+
score: 20
133+
max: 20
134+
items:
135+
- id: DQ-01
136+
name: Feature Coverage
137+
score: 8
138+
max: 8
139+
passed: true
140+
comment: Shows varying distributions across complexity levels and expertise,
141+
different spreads visible
142+
- id: DQ-02
143+
name: Realistic Context
144+
score: 7
145+
max: 7
146+
passed: true
147+
comment: Response time across task complexity is a real, neutral, plausible
148+
scenario
149+
- id: DQ-03
150+
name: Appropriate Scale
151+
score: 5
152+
max: 5
153+
passed: true
154+
comment: Response times in 200-1400ms range are realistic for cognitive tasks
155+
code_quality:
156+
score: 9
157+
max: 10
158+
items:
159+
- id: CQ-01
160+
name: KISS Structure
161+
score: 3
162+
max: 3
163+
passed: true
164+
comment: Simple imports -> data -> plot -> save structure
165+
- id: CQ-02
166+
name: Reproducibility
167+
score: 3
168+
max: 3
169+
passed: true
170+
comment: np.random.seed(42) set
171+
- id: CQ-03
172+
name: Clean Imports
173+
score: 1
174+
max: 2
175+
passed: true
176+
comment: All imports used, but guide_legend import may be unnecessary
177+
- id: CQ-04
178+
name: No Deprecated API
179+
score: 1
180+
max: 1
181+
passed: true
182+
comment: Uses current plotnine API
183+
- id: CQ-05
184+
name: Output Correct
185+
score: 1
186+
max: 1
187+
passed: true
188+
comment: Saves as 'plot.png'
189+
library_features:
190+
score: 2
191+
max: 5
192+
items:
193+
- id: LF-01
194+
name: Distinctive Features
195+
score: 2
196+
max: 5
197+
passed: false
198+
comment: Uses plotnine grammar of graphics with layered geoms and position_dodge,
199+
but uses geom_jitter instead of proper swarm positioning
200+
verdict: APPROVED
201+
impl_tags:
202+
dependencies: []
203+
techniques:
204+
- layer-composition
205+
patterns:
206+
- data-generation
207+
- iteration-over-groups
208+
dataprep: []
209+
styling:
210+
- alpha-blending
211+
- grid-styling

0 commit comments

Comments
 (0)