Skip to content

Commit 4151df1

Browse files
feat(plotnine): implement errorbar-basic (#9521)
## Implementation: `errorbar-basic` - python/plotnine Implements the **python/plotnine** version of `errorbar-basic`. **File:** `plots/errorbar-basic/implementations/python/plotnine.py` **Parent Issue:** #973 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/anyplot/actions/runs/28473397165)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Markus Neusinger <2921697+MarkusNeusinger@users.noreply.github.com>
1 parent 2f88785 commit 4151df1

2 files changed

Lines changed: 196 additions & 153 deletions

File tree

Lines changed: 73 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
""" anyplot.ai
22
errorbar-basic: Basic Error Bar Plot
3-
Library: plotnine 0.15.3 | Python 3.14.4
4-
Quality: 85/100 | Updated: 2026-04-25
3+
Library: plotnine 0.15.7 | Python 3.13.14
4+
Quality: 84/100 | Updated: 2026-06-30
55
"""
66

77
import os
@@ -13,12 +13,13 @@
1313
element_line,
1414
element_rect,
1515
element_text,
16-
geom_errorbar,
17-
geom_point,
16+
geom_crossbar,
1817
ggplot,
1918
labs,
2019
position_dodge,
2120
scale_color_manual,
21+
scale_fill_manual,
22+
scale_x_discrete,
2223
theme,
2324
theme_minimal,
2425
)
@@ -29,76 +30,83 @@
2930
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
3031
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
3132
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
33+
INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F"
3234

3335
IMPRINT = ["#009E73", "#C475FD", "#4467A3"]
3436

35-
# Data — three lab methods measured across six samples
37+
# Data — three analytical methods measuring nitrate concentration across six sampling sites
3638
data = pd.DataFrame(
3739
{
38-
"sample": [
39-
"Sample A",
40-
"Sample B",
41-
"Sample C",
42-
"Sample D",
43-
"Sample E",
44-
"Sample F",
45-
"Sample A",
46-
"Sample B",
47-
"Sample C",
48-
"Sample D",
49-
"Sample E",
50-
"Sample F",
51-
"Sample A",
52-
"Sample B",
53-
"Sample C",
54-
"Sample D",
55-
"Sample E",
56-
"Sample F",
57-
],
58-
"method": (["Method A"] * 6 + ["Method B"] * 6 + ["Method C"] * 6),
59-
"measurement": [
60-
42.5,
61-
38.2,
62-
55.1,
63-
47.8,
64-
33.6,
65-
51.3,
66-
44.8,
67-
40.1,
68-
53.6,
40+
"site": ["Site A", "Site B", "Site C", "Site D", "Site E", "Site F"] * 3,
41+
"method": ["Standard"] * 6 + ["Enhanced"] * 6 + ["Automated"] * 6,
42+
"concentration": [
43+
# Standard: baseline sensitivity, tight precision
44+
36.2,
45+
39.8,
46+
42.1,
47+
37.5,
48+
43.6,
49+
40.4,
50+
# Enhanced: ~35% higher detection
51+
48.5,
52+
52.3,
53+
55.7,
6954
49.2,
70-
35.9,
71-
52.7,
72-
41.2,
73-
39.5,
74-
56.3,
75-
46.4,
76-
34.8,
77-
50.1,
55+
56.1,
56+
51.8,
57+
# Automated: highest values, greatest variability
58+
61.4,
59+
65.8,
60+
69.2,
61+
58.6,
62+
67.3,
63+
63.5,
64+
],
65+
"se": [
66+
1.8,
67+
2.1,
68+
1.6,
69+
2.3,
70+
1.9,
71+
2.0, # Standard: SE ±1.6–2.3
72+
3.2,
73+
2.8,
74+
3.5,
75+
3.0,
76+
2.6,
77+
3.4, # Enhanced: SE ±2.6–3.5
78+
5.1,
79+
6.3,
80+
4.8,
81+
7.2,
82+
5.5,
83+
6.0, # Automated: SE ±4.8–7.2
7884
],
79-
"error": [3.2, 4.1, 2.8, 5.5, 3.8, 4.2, 2.6, 3.4, 3.1, 4.2, 3.0, 3.6, 3.9, 4.5, 2.5, 5.1, 4.3, 4.0],
8085
}
8186
)
8287

83-
data["ymin"] = data["measurement"] - data["error"]
84-
data["ymax"] = data["measurement"] + data["error"]
88+
data["ymin"] = data["concentration"] - data["se"]
89+
data["ymax"] = data["concentration"] + data["se"]
90+
91+
# Sort sites by Standard-method concentration so all three methods trend upward left→right
92+
site_order = data[data["method"] == "Standard"].set_index("site")["concentration"].sort_values().index.tolist()
8593

86-
# Order samples by mean measurement to create visual hierarchy
87-
sample_order = data.groupby("sample")["measurement"].mean().sort_values().index.tolist()
88-
data["sample"] = pd.Categorical(data["sample"], categories=sample_order, ordered=True)
94+
dodge = position_dodge(width=0.6)
8995

90-
dodge = position_dodge(width=0.55)
96+
title = "errorbar-basic · python · plotnine · anyplot.ai"
97+
subtitle = "Automated sampling shows 3–4× wider uncertainty than Standard across all sites"
9198

9299
plot = (
93-
ggplot(data, aes(x="sample", y="measurement", color="method", group="method"))
94-
+ geom_errorbar(aes(ymin="ymin", ymax="ymax"), width=0.35, size=1.4, position=dodge)
95-
+ geom_point(size=5, position=dodge)
100+
ggplot(data, aes(x="site", y="concentration", color="method", fill="method", group="method"))
101+
+ geom_crossbar(aes(ymin="ymin", ymax="ymax"), width=0.18, fatten=3, size=0.9, alpha=0.2, position=dodge)
96102
+ scale_color_manual(values=IMPRINT, name="Method")
97-
+ labs(x="Experimental Sample", y="Measurement Value (mg/L)", title="errorbar-basic · plotnine · anyplot.ai")
103+
+ scale_fill_manual(values=IMPRINT, name="Method")
104+
+ scale_x_discrete(limits=site_order)
105+
+ labs(x="Sampling Site", y="Nitrate Concentration (mg/L)", title=title, subtitle=subtitle)
98106
+ theme_minimal()
99107
+ theme(
100-
figure_size=(16, 9),
101-
text=element_text(size=14, color=INK_SOFT),
108+
figure_size=(8, 4.5),
109+
text=element_text(size=7, color=INK_SOFT),
102110
plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),
103111
panel_background=element_rect(fill=PAGE_BG, color=PAGE_BG),
104112
panel_border=element_blank(),
@@ -108,15 +116,16 @@
108116
axis_line_x=element_line(color=INK_SOFT, size=0.6),
109117
axis_line_y=element_blank(),
110118
axis_ticks=element_blank(),
111-
axis_title=element_text(size=20, color=INK),
112-
axis_text=element_text(size=16, color=INK_SOFT),
113-
plot_title=element_text(size=24, color=INK, weight="bold"),
114-
legend_background=element_rect(fill=ELEVATED_BG, color=ELEVATED_BG),
119+
axis_title=element_text(size=10, color=INK),
120+
axis_text=element_text(size=8, color=INK_SOFT),
121+
plot_title=element_text(size=12, color=INK, weight="bold"),
122+
plot_subtitle=element_text(size=9, color=INK_MUTED),
123+
legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT, size=0.4),
115124
legend_key=element_rect(fill=PAGE_BG, color=PAGE_BG),
116-
legend_text=element_text(size=16, color=INK_SOFT),
117-
legend_title=element_text(size=16, color=INK),
125+
legend_text=element_text(size=8, color=INK_SOFT),
126+
legend_title=element_text(size=8, color=INK),
118127
legend_position="right",
119128
)
120129
)
121130

122-
plot.save(f"plot-{THEME}.png", dpi=300, width=16, height=9, verbose=False)
131+
plot.save(f"plot-{THEME}.png", dpi=400, width=8, height=4.5, units="in", verbose=False)

0 commit comments

Comments
 (0)