|
1 | | -""" pyplots.ai |
| 1 | +"""pyplots.ai |
2 | 2 | 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 |
5 | 5 | """ |
6 | 6 |
|
7 | 7 | import numpy as np |
|
13 | 13 | element_line, |
14 | 14 | element_text, |
15 | 15 | geom_boxplot, |
16 | | - geom_point, |
17 | | - geom_ribbon, |
| 16 | + geom_jitter, |
| 17 | + geom_violin, |
18 | 18 | ggplot, |
19 | 19 | labs, |
20 | 20 | scale_color_manual, |
21 | 21 | scale_fill_manual, |
22 | | - scale_x_continuous, |
| 22 | + stage, |
23 | 23 | theme, |
24 | 24 | theme_minimal, |
25 | 25 | ) |
26 | | -from scipy import stats |
27 | 26 |
|
28 | 27 |
|
29 | 28 | # Data - Reaction times (ms) for three experimental conditions |
30 | 29 | np.random.seed(42) |
31 | 30 |
|
32 | | -# Control group: normal distribution centered at 450ms |
33 | 31 | control = np.random.normal(450, 60, 80) |
34 | | - |
35 | | -# Treatment A: faster responses, centered at 380ms |
36 | 32 | treatment_a = np.random.normal(380, 50, 80) |
37 | | - |
38 | | -# Treatment B: bimodal distribution (some fast responders, some slow) |
39 | 33 | treatment_b = np.concatenate([np.random.normal(350, 40, 50), np.random.normal(500, 45, 30)]) |
40 | 34 |
|
41 | | -# Build dataframe with numeric x positions |
42 | 35 | df = pd.DataFrame( |
43 | 36 | { |
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 | + ), |
47 | 40 | "reaction_time": np.concatenate([control, treatment_a, treatment_b]), |
48 | 41 | } |
49 | 42 | ) |
| 43 | +df["condition"] = pd.Categorical(df["condition"], categories=["Treatment B", "Treatment A", "Control"], ordered=True) |
50 | 44 |
|
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 |
60 | 46 | colors = {"Control": "#306998", "Treatment A": "#FFD43B", "Treatment B": "#5BA85B"} |
61 | 47 |
|
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 |
81 | 50 |
|
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 |
86 | 54 | 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, |
92 | 63 | alpha=0.85, |
93 | 64 | show_legend=False, |
94 | 65 | ) |
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 |
110 | 71 | ) |
111 | 72 | + scale_fill_manual(values=colors) |
112 | 73 | + scale_color_manual(values=colors) |
113 | | - + scale_x_continuous(breaks=[0, 1, 2], labels=["Treatment B", "Treatment A", "Control"]) |
114 | 74 | + 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 | + ) |
116 | 78 | + theme_minimal() |
117 | 79 | + theme( |
118 | 80 | figure_size=(16, 9), |
|
0 commit comments