Skip to content

Commit 13ba55d

Browse files
feat(matplotlib): implement volcano-basic (#2939)
## Implementation: `volcano-basic` - matplotlib Implements the **matplotlib** version of `volcano-basic`. **File:** `plots/volcano-basic/implementations/matplotlib.py` **Parent Issue:** #2924 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20612778978)* --------- 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 71f7f29 commit 13ba55d

2 files changed

Lines changed: 144 additions & 0 deletions

File tree

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
""" pyplots.ai
2+
volcano-basic: Volcano Plot for Statistical Significance
3+
Library: matplotlib 3.10.8 | Python 3.13.11
4+
Quality: 91/100 | Created: 2025-12-31
5+
"""
6+
7+
import matplotlib.pyplot as plt
8+
import numpy as np
9+
10+
11+
# Data - simulated differential expression results
12+
np.random.seed(42)
13+
n_genes = 2000
14+
15+
# Generate log2 fold changes (centered around 0)
16+
log2_fc = np.random.normal(0, 1.5, n_genes)
17+
18+
# Generate p-values (most non-significant, some significant)
19+
# Use exponential distribution for realistic p-value spread
20+
base_pvalues = np.random.exponential(0.3, n_genes)
21+
base_pvalues = np.clip(base_pvalues, 1e-50, 1.0)
22+
23+
# Make genes with large fold changes more likely to be significant
24+
significance_boost = np.abs(log2_fc) / 3
25+
pvalues = base_pvalues * np.exp(-significance_boost * 5)
26+
pvalues = np.clip(pvalues, 1e-50, 1.0)
27+
28+
# Convert to -log10(p-value)
29+
neg_log10_pval = -np.log10(pvalues)
30+
31+
# Significance thresholds
32+
pval_threshold = 1.3 # -log10(0.05)
33+
fc_threshold = 1.0 # log2(2) = 1
34+
35+
# Classify points
36+
sig_up = (neg_log10_pval > pval_threshold) & (log2_fc > fc_threshold)
37+
sig_down = (neg_log10_pval > pval_threshold) & (log2_fc < -fc_threshold)
38+
non_sig = ~sig_up & ~sig_down
39+
40+
# Create figure
41+
fig, ax = plt.subplots(figsize=(16, 9))
42+
43+
# Plot non-significant points first (gray)
44+
ax.scatter(
45+
log2_fc[non_sig], neg_log10_pval[non_sig], c="#888888", s=50, alpha=0.5, label="Not significant", edgecolors="none"
46+
)
47+
48+
# Plot significant down-regulated (blue - Python Blue)
49+
ax.scatter(
50+
log2_fc[sig_down], neg_log10_pval[sig_down], c="#306998", s=80, alpha=0.7, label="Down-regulated", edgecolors="none"
51+
)
52+
53+
# Plot significant up-regulated (gold - Python Yellow)
54+
ax.scatter(
55+
log2_fc[sig_up],
56+
neg_log10_pval[sig_up],
57+
c="#FFD43B",
58+
s=80,
59+
alpha=0.7,
60+
label="Up-regulated",
61+
edgecolors="white",
62+
linewidths=0.5,
63+
)
64+
65+
# Add threshold lines
66+
ax.axhline(y=pval_threshold, color="#333333", linestyle="--", linewidth=2, alpha=0.7)
67+
ax.axvline(x=fc_threshold, color="#333333", linestyle="--", linewidth=2, alpha=0.7)
68+
ax.axvline(x=-fc_threshold, color="#333333", linestyle="--", linewidth=2, alpha=0.7)
69+
70+
# Label top significant genes
71+
top_up_idx = np.where(sig_up)[0]
72+
if len(top_up_idx) > 0:
73+
top_up_scores = neg_log10_pval[top_up_idx] + np.abs(log2_fc[top_up_idx])
74+
top_up = top_up_idx[np.argsort(top_up_scores)[-5:]]
75+
for idx in top_up:
76+
ax.annotate(
77+
f"Gene_{idx}",
78+
(log2_fc[idx], neg_log10_pval[idx]),
79+
fontsize=12,
80+
ha="left",
81+
va="bottom",
82+
xytext=(5, 5),
83+
textcoords="offset points",
84+
)
85+
86+
top_down_idx = np.where(sig_down)[0]
87+
if len(top_down_idx) > 0:
88+
top_down_scores = neg_log10_pval[top_down_idx] + np.abs(log2_fc[top_down_idx])
89+
top_down = top_down_idx[np.argsort(top_down_scores)[-5:]]
90+
for idx in top_down:
91+
ax.annotate(
92+
f"Gene_{idx}",
93+
(log2_fc[idx], neg_log10_pval[idx]),
94+
fontsize=12,
95+
ha="right",
96+
va="bottom",
97+
xytext=(-5, 5),
98+
textcoords="offset points",
99+
)
100+
101+
# Styling
102+
ax.set_xlabel("Log₂ Fold Change", fontsize=20)
103+
ax.set_ylabel("-Log₁₀ (p-value)", fontsize=20)
104+
ax.set_title("volcano-basic · matplotlib · pyplots.ai", fontsize=24)
105+
ax.tick_params(axis="both", labelsize=16)
106+
ax.legend(fontsize=16, loc="upper right", framealpha=0.9)
107+
ax.grid(True, alpha=0.3, linestyle="--")
108+
109+
# Set axis limits with padding
110+
x_max = max(abs(log2_fc.min()), abs(log2_fc.max())) * 1.1
111+
ax.set_xlim(-x_max, x_max)
112+
ax.set_ylim(0, neg_log10_pval.max() * 1.1)
113+
114+
plt.tight_layout()
115+
plt.savefig("plot.png", dpi=300, bbox_inches="tight")
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
library: matplotlib
2+
specification_id: volcano-basic
3+
created: '2025-12-31T05:31:28Z'
4+
updated: '2025-12-31T05:37:53Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20612778978
7+
issue: 2924
8+
python_version: 3.13.11
9+
library_version: 3.10.8
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/volcano-basic/matplotlib/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/volcano-basic/matplotlib/plot_thumb.png
12+
preview_html: null
13+
quality_score: 91
14+
review:
15+
strengths:
16+
- Excellent implementation of all spec requirements including threshold lines, color
17+
coding, and gene annotations
18+
- Scientifically accurate data simulation with p-values correlated to fold change
19+
magnitude
20+
- Clean, readable code following KISS principles
21+
- Proper axis formatting with subscript notation (Log₂, Log₁₀)
22+
- Symmetric axis limits for better visual interpretation
23+
- Good alpha transparency for handling overlapping points
24+
weaknesses:
25+
- Marker sizes could be larger (s=100-200 recommended for 2000 points density)
26+
- No use of distinctive matplotlib features like PathEffects for label backgrounds
27+
or custom colormaps
28+
- Legend placement in upper right could potentially overlap with high-significance
29+
up-regulated points

0 commit comments

Comments
 (0)