|
1 | 1 | """ anyplot.ai |
2 | 2 | ecdf-basic: Basic ECDF Plot |
3 | | -Library: plotnine 0.15.3 | Python 3.14.4 |
4 | | -Quality: 91/100 | Updated: 2026-04-24 |
| 3 | +Library: plotnine 0.15.7 | Python 3.13.14 |
| 4 | +Quality: 89/100 | Updated: 2026-06-25 |
5 | 5 | """ |
6 | 6 |
|
7 | 7 | import os |
| 8 | +import sys |
8 | 9 |
|
9 | 10 | import numpy as np |
10 | 11 | import pandas as pd |
11 | | -from plotnine import ( |
| 12 | + |
| 13 | + |
| 14 | +# Avoid shadowing the plotnine library when this file is run directly |
| 15 | +_cwd = os.getcwd() |
| 16 | +sys.path = [p for p in sys.path if os.path.abspath(p) != _cwd] |
| 17 | + |
| 18 | +from plotnine import ( # noqa: E402 |
12 | 19 | aes, |
| 20 | + annotate, |
13 | 21 | element_blank, |
14 | 22 | element_line, |
15 | 23 | element_rect, |
16 | 24 | element_text, |
17 | 25 | geom_hline, |
18 | | - geom_step, |
19 | 26 | geom_vline, |
20 | 27 | ggplot, |
21 | 28 | ggsave, |
22 | 29 | labs, |
23 | 30 | scale_x_continuous, |
24 | 31 | scale_y_continuous, |
| 32 | + stat_ecdf, |
25 | 33 | theme, |
26 | 34 | theme_minimal, |
27 | 35 | ) |
28 | 36 |
|
29 | 37 |
|
30 | | -# Theme |
| 38 | +# Theme tokens (Imprint palette) |
31 | 39 | THEME = os.getenv("ANYPLOT_THEME", "light") |
32 | 40 | PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" |
33 | 41 | ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420" |
34 | 42 | INK = "#1A1A17" if THEME == "light" else "#F0EFE8" |
35 | 43 | INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0" |
36 | | -BRAND = "#009E73" |
| 44 | +INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F" |
| 45 | +BRAND = "#009E73" # Imprint palette position 1 |
37 | 46 |
|
38 | | -# Data: test scores — normal distribution centered at 50 |
| 47 | +# Data: test scores — normal distribution |
39 | 48 | np.random.seed(42) |
40 | 49 | scores = np.random.randn(200) * 15 + 50 |
| 50 | +q1 = float(np.percentile(scores, 25)) |
| 51 | +median_score = float(np.median(scores)) |
| 52 | +q3 = float(np.percentile(scores, 75)) |
41 | 53 |
|
42 | | -# Compute ECDF |
43 | | -sorted_scores = np.sort(scores) |
44 | | -ecdf_y = np.arange(1, len(sorted_scores) + 1) / len(sorted_scores) |
45 | | -median_score = np.median(sorted_scores) |
| 54 | +df = pd.DataFrame({"score": scores}) |
46 | 55 |
|
47 | | -df = pd.DataFrame({"score": sorted_scores, "ecdf": ecdf_y}) |
| 56 | +title = "ecdf-basic · python · plotnine · anyplot.ai" |
48 | 57 |
|
49 | | -# Plot |
| 58 | +# Plot — stat_ecdf computes ECDF internally via plotnine's stat layer |
50 | 59 | plot = ( |
51 | | - ggplot(df, aes(x="score", y="ecdf")) |
52 | | - + geom_hline(yintercept=0.5, color=INK_SOFT, size=0.6, linetype="dotted", alpha=0.7) |
53 | | - + geom_vline(xintercept=median_score, color=INK_SOFT, size=0.6, linetype="dotted", alpha=0.7) |
54 | | - + geom_step(color=BRAND, size=2) |
55 | | - + labs(x="Test Score (points)", y="Cumulative Proportion", title="ecdf-basic · plotnine · anyplot.ai") |
| 60 | + ggplot(df, aes(x="score")) |
| 61 | + # IQR shaded band — highlights the interquartile range (middle 50%) |
| 62 | + + annotate("rect", xmin=q1, xmax=q3, ymin=0.0, ymax=1.0, fill=BRAND, alpha=0.06) |
| 63 | + # Quartile reference crosshairs (Q1, median, Q3) |
| 64 | + + geom_hline(yintercept=0.25, color=INK_SOFT, size=0.5, linetype="dotted", alpha=0.55) |
| 65 | + + geom_hline(yintercept=0.50, color=INK_SOFT, size=0.6, linetype="dotted", alpha=0.70) |
| 66 | + + geom_hline(yintercept=0.75, color=INK_SOFT, size=0.5, linetype="dotted", alpha=0.55) |
| 67 | + + geom_vline(xintercept=q1, color=INK_SOFT, size=0.5, linetype="dotted", alpha=0.55) |
| 68 | + + geom_vline(xintercept=median_score, color=INK_SOFT, size=0.6, linetype="dotted", alpha=0.70) |
| 69 | + + geom_vline(xintercept=q3, color=INK_SOFT, size=0.5, linetype="dotted", alpha=0.55) |
| 70 | + # ECDF step line — rendered on top of reference elements |
| 71 | + + stat_ecdf(geom="step", color=BRAND, size=1.1) |
| 72 | + # Quartile annotations — teach readers how to extract percentile information |
| 73 | + + annotate("text", x=q1 + 1.5, y=0.16, label=f"Q1: {q1:.1f}", color=INK_SOFT, size=3.5, ha="left") |
| 74 | + + annotate( |
| 75 | + "text", x=median_score + 1.5, y=0.08, label=f"Median: {median_score:.1f}", color=INK_SOFT, size=4.0, ha="left" |
| 76 | + ) |
| 77 | + + annotate("text", x=q3 + 1.5, y=0.80, label=f"Q3: {q3:.1f}", color=INK_SOFT, size=3.5, ha="left") |
| 78 | + + labs(x="Test Score (points)", y="Cumulative Proportion", title=title) |
56 | 79 | + scale_x_continuous(expand=(0.01, 0)) |
57 | 80 | + scale_y_continuous(limits=(0, 1), breaks=np.arange(0, 1.1, 0.1), expand=(0.01, 0)) |
58 | 81 | + theme_minimal() |
59 | 82 | + theme( |
60 | | - figure_size=(16, 9), |
| 83 | + figure_size=(8, 4.5), |
61 | 84 | plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG), |
62 | 85 | panel_background=element_rect(fill=PAGE_BG, color=PAGE_BG), |
63 | 86 | panel_border=element_blank(), |
64 | | - panel_grid_major=element_line(color=INK, size=0.3, alpha=0.10), |
| 87 | + panel_grid_major=element_line(color=INK, size=0.3, alpha=0.15), |
65 | 88 | panel_grid_minor=element_blank(), |
66 | 89 | axis_line=element_line(color=INK_SOFT, size=0.6), |
67 | 90 | axis_ticks=element_line(color=INK_SOFT, size=0.5), |
68 | | - text=element_text(color=INK, size=14), |
69 | | - plot_title=element_text(color=INK, size=24, weight="medium", ha="left"), |
70 | | - axis_title=element_text(color=INK, size=20), |
71 | | - axis_text=element_text(color=INK_SOFT, size=16), |
| 91 | + text=element_text(color=INK, size=7), |
| 92 | + plot_title=element_text(color=INK, size=12, weight="medium", ha="left"), |
| 93 | + axis_title=element_text(color=INK, size=10), |
| 94 | + axis_text=element_text(color=INK_SOFT, size=8), |
72 | 95 | legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT), |
73 | | - legend_text=element_text(color=INK_SOFT, size=16), |
74 | | - legend_title=element_text(color=INK, size=16), |
| 96 | + legend_text=element_text(color=INK_SOFT, size=8), |
| 97 | + legend_title=element_text(color=INK, size=8), |
75 | 98 | ) |
76 | 99 | ) |
77 | 100 |
|
78 | | -ggsave(plot, filename=f"plot-{THEME}.png", dpi=300, width=16, height=9) |
| 101 | +ggsave(plot, filename=f"plot-{THEME}.png", dpi=400, width=8, height=4.5) |
0 commit comments