|
| 1 | +""" pyplots.ai |
| 2 | +volcano-basic: Volcano Plot for Statistical Significance |
| 3 | +Library: bokeh 3.8.1 | Python 3.13.11 |
| 4 | +Quality: 91/100 | Created: 2025-12-31 |
| 5 | +""" |
| 6 | + |
| 7 | +import numpy as np |
| 8 | +from bokeh.io import export_png |
| 9 | +from bokeh.models import ColumnDataSource, Span |
| 10 | +from bokeh.plotting import figure |
| 11 | + |
| 12 | + |
| 13 | +# Data - Simulated differential expression results |
| 14 | +np.random.seed(42) |
| 15 | +n_genes = 2000 |
| 16 | + |
| 17 | +# Generate log2 fold changes (effect sizes) |
| 18 | +log2_fc = np.random.normal(0, 1.5, n_genes) |
| 19 | + |
| 20 | +# Generate p-values: most genes non-significant, some highly significant |
| 21 | +# Use inverse relationship with fold change magnitude for realism |
| 22 | +base_pvals = np.random.uniform(0.001, 1, n_genes) |
| 23 | +# Genes with larger fold changes tend to have lower p-values |
| 24 | +fold_effect = np.abs(log2_fc) / np.max(np.abs(log2_fc)) |
| 25 | +pvals = base_pvals * (1 - 0.7 * fold_effect) + 0.01 * np.random.random(n_genes) |
| 26 | +pvals = np.clip(pvals, 1e-50, 1) |
| 27 | + |
| 28 | +neg_log10_pval = -np.log10(pvals) |
| 29 | + |
| 30 | +# Significance thresholds |
| 31 | +pval_threshold = -np.log10(0.05) # ~1.3 |
| 32 | +fc_threshold = 1.0 # log2(2) = 1 |
| 33 | + |
| 34 | +# Classify points by significance |
| 35 | +significant_up = (neg_log10_pval > pval_threshold) & (log2_fc > fc_threshold) |
| 36 | +significant_down = (neg_log10_pval > pval_threshold) & (log2_fc < -fc_threshold) |
| 37 | + |
| 38 | +# Create separate data sources for each category (enables proper legend) |
| 39 | +source_up = ColumnDataSource(data={"x": log2_fc[significant_up], "y": neg_log10_pval[significant_up]}) |
| 40 | + |
| 41 | +source_down = ColumnDataSource(data={"x": log2_fc[significant_down], "y": neg_log10_pval[significant_down]}) |
| 42 | + |
| 43 | +source_ns = ColumnDataSource( |
| 44 | + data={"x": log2_fc[~(significant_up | significant_down)], "y": neg_log10_pval[~(significant_up | significant_down)]} |
| 45 | +) |
| 46 | + |
| 47 | +# Create figure |
| 48 | +p = figure( |
| 49 | + width=4800, |
| 50 | + height=2700, |
| 51 | + title="volcano-basic · bokeh · pyplots.ai", |
| 52 | + x_axis_label="Log₂ Fold Change", |
| 53 | + y_axis_label="-Log₁₀ (P-value)", |
| 54 | +) |
| 55 | + |
| 56 | +# Plot points by category (non-significant first, then significant on top) |
| 57 | +p.scatter(x="x", y="y", source=source_ns, color="#AAAAAA", size=18, alpha=0.5, legend_label="Not significant") |
| 58 | + |
| 59 | +p.scatter(x="x", y="y", source=source_down, color="#306998", size=25, alpha=0.7, legend_label="Down-regulated") |
| 60 | + |
| 61 | +p.scatter(x="x", y="y", source=source_up, color="#D62728", size=25, alpha=0.7, legend_label="Up-regulated") |
| 62 | + |
| 63 | +# Add threshold lines |
| 64 | +# Horizontal line for p-value threshold |
| 65 | +hline = Span( |
| 66 | + location=pval_threshold, dimension="width", line_color="#333333", line_dash="dashed", line_width=3, line_alpha=0.7 |
| 67 | +) |
| 68 | +p.add_layout(hline) |
| 69 | + |
| 70 | +# Vertical lines for fold change thresholds |
| 71 | +vline_pos = Span( |
| 72 | + location=fc_threshold, dimension="height", line_color="#333333", line_dash="dashed", line_width=3, line_alpha=0.7 |
| 73 | +) |
| 74 | +p.add_layout(vline_pos) |
| 75 | + |
| 76 | +vline_neg = Span( |
| 77 | + location=-fc_threshold, dimension="height", line_color="#333333", line_dash="dashed", line_width=3, line_alpha=0.7 |
| 78 | +) |
| 79 | +p.add_layout(vline_neg) |
| 80 | + |
| 81 | +# Styling - scaled for 4800x2700 canvas |
| 82 | +p.title.text_font_size = "36pt" |
| 83 | +p.title.align = "center" |
| 84 | +p.xaxis.axis_label_text_font_size = "28pt" |
| 85 | +p.yaxis.axis_label_text_font_size = "28pt" |
| 86 | +p.xaxis.major_label_text_font_size = "22pt" |
| 87 | +p.yaxis.major_label_text_font_size = "22pt" |
| 88 | +p.xaxis.axis_line_width = 2 |
| 89 | +p.yaxis.axis_line_width = 2 |
| 90 | +p.xaxis.major_tick_line_width = 2 |
| 91 | +p.yaxis.major_tick_line_width = 2 |
| 92 | + |
| 93 | +# Grid styling |
| 94 | +p.grid.grid_line_alpha = 0.3 |
| 95 | +p.grid.grid_line_dash = "dotted" |
| 96 | + |
| 97 | +# Background |
| 98 | +p.background_fill_color = "#FAFAFA" |
| 99 | +p.border_fill_color = "#FFFFFF" |
| 100 | + |
| 101 | +# Legend styling |
| 102 | +p.legend.location = "top_right" |
| 103 | +p.legend.label_text_font_size = "22pt" |
| 104 | +p.legend.glyph_height = 40 |
| 105 | +p.legend.glyph_width = 40 |
| 106 | +p.legend.spacing = 15 |
| 107 | +p.legend.padding = 20 |
| 108 | +p.legend.background_fill_alpha = 0.8 |
| 109 | +p.legend.border_line_width = 2 |
| 110 | +p.legend.border_line_color = "#CCCCCC" |
| 111 | + |
| 112 | +# Save |
| 113 | +export_png(p, filename="plot.png") |
0 commit comments