Skip to content

Commit 0ce3ccd

Browse files
update(raincloud-basic): plotnine — fix orientation consistency
Fix cloud/rain orientation: cloud (half-violin) extends upward, rain (jittered points) falls downward. Updated spec to clarify absolute y-direction terms.
1 parent a1bd610 commit 0ce3ccd

3 files changed

Lines changed: 40 additions & 78 deletions

File tree

plots/raincloud-basic/implementations/plotnine.py

Lines changed: 32 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
""" pyplots.ai
1+
"""pyplots.ai
22
raincloud-basic: Basic Raincloud Plot
3-
Library: plotnine 0.15.2 | Python 3.13.11
4-
Quality: 91/100 | Created: 2025-12-25
3+
Library: plotnine 0.15.3 | Python 3.14
4+
Quality: /100 | Updated: 2026-02-14
55
"""
66

77
import numpy as np
@@ -13,106 +13,68 @@
1313
element_line,
1414
element_text,
1515
geom_boxplot,
16-
geom_point,
17-
geom_ribbon,
16+
geom_jitter,
17+
geom_violin,
1818
ggplot,
1919
labs,
2020
scale_color_manual,
2121
scale_fill_manual,
22-
scale_x_continuous,
22+
stage,
2323
theme,
2424
theme_minimal,
2525
)
26-
from scipy import stats
2726

2827

2928
# Data - Reaction times (ms) for three experimental conditions
3029
np.random.seed(42)
3130

32-
# Control group: normal distribution centered at 450ms
3331
control = np.random.normal(450, 60, 80)
34-
35-
# Treatment A: faster responses, centered at 380ms
3632
treatment_a = np.random.normal(380, 50, 80)
37-
38-
# Treatment B: bimodal distribution (some fast responders, some slow)
3933
treatment_b = np.concatenate([np.random.normal(350, 40, 50), np.random.normal(500, 45, 30)])
4034

41-
# Build dataframe with numeric x positions
4235
df = pd.DataFrame(
4336
{
44-
"condition": ["Control"] * len(control)
45-
+ ["Treatment A"] * len(treatment_a)
46-
+ ["Treatment B"] * len(treatment_b),
37+
"condition": (
38+
["Control"] * len(control) + ["Treatment A"] * len(treatment_a) + ["Treatment B"] * len(treatment_b)
39+
),
4740
"reaction_time": np.concatenate([control, treatment_a, treatment_b]),
4841
}
4942
)
43+
df["condition"] = pd.Categorical(df["condition"], categories=["Treatment B", "Treatment A", "Control"], ordered=True)
5044

51-
# Map conditions to numeric positions (for coord_flip: higher = top)
52-
condition_map = {"Control": 2, "Treatment A": 1, "Treatment B": 0}
53-
df["x_pos"] = df["condition"].map(condition_map).astype(float)
54-
55-
# Add jitter for rain points (below center, after coord_flip)
56-
np.random.seed(123)
57-
df["x_rain"] = df["x_pos"] - 0.22 + np.random.uniform(-0.06, 0.06, len(df))
58-
59-
# Colors - dictionary mapping for condition names
45+
# Colors
6046
colors = {"Control": "#306998", "Treatment A": "#FFD43B", "Treatment B": "#5BA85B"}
6147

62-
# Create half-violin (cloud) data using KDE
63-
# The cloud should be on TOP only (positive direction after coord_flip)
64-
cloud_dfs = []
65-
for cond, x_base in condition_map.items():
66-
data = df[df["condition"] == cond]["reaction_time"].values
67-
kde = stats.gaussian_kde(data)
68-
y_range = np.linspace(data.min() - 10, data.max() + 10, 200)
69-
density = kde(y_range)
70-
# Normalize density and scale for visual width (half-violin on TOP only)
71-
density_scaled = density / density.max() * 0.35
72-
cloud_df = pd.DataFrame(
73-
{
74-
"reaction_time": y_range,
75-
"ymin": x_base, # Base position (center line)
76-
"ymax": x_base + density_scaled, # Extend upward only (becomes top after flip)
77-
"condition": cond,
78-
}
79-
)
80-
cloud_dfs.append(cloud_df)
48+
# Cloud shift (positive = right on pre-flip x-axis = upward after coord_flip)
49+
cloud_shift = 0.12
8150

82-
cloud_data = pd.concat(cloud_dfs, ignore_index=True)
83-
84-
# Create raincloud plot with horizontal orientation (coord_flip)
85-
# After flip: Cloud (half-violin) on TOP, boxplot centered, rain points BELOW
51+
# Plot - horizontal raincloud via coord_flip
52+
# Before flip: x=condition (categorical), y=reaction_time (numeric)
53+
# After flip: categories on y-axis, values on x-axis
8654
plot = (
87-
ggplot()
88-
# Half-violin (cloud) using geom_ribbon - extends from center to one side only
89-
+ geom_ribbon(
90-
data=cloud_data,
91-
mapping=aes(x="reaction_time", ymin="ymin", ymax="ymax", fill="condition"),
55+
ggplot(df, aes(x="condition", y="reaction_time", fill="condition", color="condition"))
56+
# Cloud (half-violin) - style="right" extends in positive x-direction = upward after flip
57+
+ geom_violin(
58+
aes(x=stage("condition", after_scale="x+{0}".format(cloud_shift))),
59+
style="right",
60+
trim=True,
61+
scale="width",
62+
size=0.4,
9263
alpha=0.85,
9364
show_legend=False,
9465
)
95-
# Box plot - centered, narrow, white fill
96-
+ geom_boxplot(
97-
data=df,
98-
mapping=aes(x="x_pos", y="reaction_time", group="condition"),
99-
width=0.08,
100-
outlier_shape="",
101-
fill="white",
102-
color="#333333",
103-
size=0.6,
104-
alpha=0.95,
105-
show_legend=False,
106-
)
107-
# Jittered points (rain) - below the center line (after flip)
108-
+ geom_point(
109-
data=df, mapping=aes(x="x_rain", y="reaction_time", color="condition"), size=2, alpha=0.6, show_legend=False
66+
# Boxplot - centered on category baseline
67+
+ geom_boxplot(width=0.08, outlier_shape="", fill="white", color="#333333", size=0.6, alpha=0.95, show_legend=False)
68+
# Rain (jittered points) - nudged in negative x-direction = downward after flip
69+
+ geom_jitter(
70+
aes(x=stage("condition", after_scale="x-0.18")), width=0.06, height=0, size=1.8, alpha=0.6, show_legend=False
11071
)
11172
+ scale_fill_manual(values=colors)
11273
+ scale_color_manual(values=colors)
113-
+ scale_x_continuous(breaks=[0, 1, 2], labels=["Treatment B", "Treatment A", "Control"])
11474
+ coord_flip()
115-
+ labs(x="Experimental Condition", y="Reaction Time (ms)", title="raincloud-basic · plotnine · pyplots.ai")
75+
+ labs(
76+
x="Experimental Condition", y="Reaction Time (ms)", title="raincloud-basic \u00b7 plotnine \u00b7 pyplots.ai"
77+
)
11678
+ theme_minimal()
11779
+ theme(
11880
figure_size=(16, 9),

plots/raincloud-basic/metadata/plotnine.yaml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
library: plotnine
22
specification_id: raincloud-basic
33
created: '2025-12-25T07:17:07Z'
4-
updated: '2025-12-25T07:34:53Z'
5-
generated_by: claude-opus-4-5-20251101
4+
updated: 2026-02-14T20:28:11+00:00
5+
generated_by: claude-opus-4-6
66
workflow_run: 20500986130
77
issue: 0
8-
python_version: 3.13.11
9-
library_version: 0.15.2
8+
python_version: "3.14"
9+
library_version: "0.15.3"
1010
preview_url: https://storage.googleapis.com/pyplots-images/plots/raincloud-basic/plotnine/plot.png
1111
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/raincloud-basic/plotnine/plot_thumb.png
1212
preview_html: null
13-
quality_score: 91
13+
quality_score: null
1414
impl_tags:
1515
dependencies:
1616
- scipy

plots/raincloud-basic/specification.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ A raincloud plot combines three visualization elements—a half-violin (the "clo
2020

2121
## Notes
2222

23-
- **Required Orientation**: Use HORIZONTAL orientation with categories on y-axis and values on x-axis
24-
- **Critical Layout**: The "cloud" (half-violin/KDE) must be on TOP (positive y offset), boxplot in MIDDLE, and "rain" (jittered points) BELOW (negative y offset) - like rain falling from a cloud
23+
- **Required Orientation**: Use HORIZONTAL orientation with categories on y-axis and values on x-axis. "Above" and "below" always refer to the y-direction (screen up/down), not the x-direction
24+
- **Critical Layout**: For each category on the y-axis: the "cloud" (half-violin/KDE) must extend ABOVE the category baseline (upward on screen), the boxplot sits centered ON the category baseline, and "rain" (jittered points) must appear BELOW the category baseline (downward on screen) - like rain falling from a cloud
2525
- Clip the violin to show only half (the "cloud" portion), not a full violin
2626
- Use moderate jitter (0.05-0.1) to spread rain points without excessive overlap
2727
- Apply transparency (alpha 0.5-0.7) to jittered rain points for visibility
2828
- Include median and quartile markers in box plot
29-
- The visual metaphor must be clear: cloud above, rain falling below
29+
- The visual metaphor must be clear: cloud rises upward (positive y-direction), rain falls downward (negative y-direction) from each category line

0 commit comments

Comments
 (0)