{"spec_id":"ma-differential-expression","library":"plotly","language":"python","code":"\"\"\" anyplot.ai\nma-differential-expression: MA Plot for Differential Expression\nLibrary: plotly 6.8.0 | Python 3.13.14\nQuality: 82/100 | Updated: 2026-06-21\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport plotly.graph_objects as go\nfrom scipy.signal import savgol_filter\n\n\n# Theme tokens — Imprint palette, theme-adaptive chrome\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\nGRID = \"rgba(26,26,23,0.15)\" if THEME == \"light\" else \"rgba(240,239,232,0.15)\"\n\n# Imprint palette assignments\nCOLOR_SIG = \"#009E73\"  # Imprint position 1 — significant genes (first series)\nCOLOR_LOESS = \"#4467A3\"  # Imprint position 3 — LOESS trend line\nCOLOR_THRESHOLD = \"#DDCC77\"  # Imprint amber — fold-change threshold markers\n\n# Data — simulated RNA-seq differential expression results\nnp.random.seed(42)\nn_genes = 15000\n\nmean_expression = np.concatenate([np.random.normal(4, 1.5, 5000), np.random.normal(9, 2.5, 10000)])\nmean_expression = np.clip(mean_expression, 0.5, 16)\n\nlog_fold_change = np.random.normal(0, 0.3, n_genes)\n\nn_de = 1200\nde_indices = np.random.choice(n_genes, n_de, replace=False)\nlog_fold_change[de_indices] = np.random.choice([-1, 1], n_de) * (np.random.exponential(0.8, n_de) + 1.0)\n\nnoise_scale = 0.5 / (1 + mean_expression * 0.3)\nlog_fold_change += np.random.normal(0, noise_scale)\n\nsignificant = np.abs(log_fold_change) > 1.0\nsignificant &= mean_expression > 2.0\nsignificant[de_indices] = np.abs(log_fold_change[de_indices]) > 0.8\n\ngene_names = [f\"Gene{i}\" for i in range(n_genes)]\ntop_gene_names = [\"BRCA1\", \"TP53\", \"MYC\", \"EGFR\", \"VEGFA\", \"IL6\", \"TNF\", \"STAT3\", \"KRAS\", \"CDK2\"]\ntop_de = np.argsort(np.abs(log_fold_change[significant]))[-10:]\nsig_indices = np.where(significant)[0]\nfor i, name in zip(top_de, top_gene_names, strict=False):\n    gene_names[sig_indices[i]] = name\n\nnon_sig_mask = ~significant\nsig_mask = significant\n\n# LOESS-like smoothing curve via Savitzky-Golay filter\nsort_idx = np.argsort(mean_expression)\nsorted_expr = mean_expression[sort_idx]\nsorted_lfc = log_fold_change[sort_idx]\nwindow = min(501, len(sorted_lfc) // 4 * 2 + 1)\nsmoothed = savgol_filter(sorted_lfc, window, 3)\n\n# Plot\nfig = go.Figure()\n\n# Non-significant genes — muted, recede into background\nfig.add_trace(\n    go.Scatter(\n        x=mean_expression[non_sig_mask],\n        y=log_fold_change[non_sig_mask],\n        mode=\"markers\",\n        marker={\"size\": 5, \"color\": INK_MUTED, \"opacity\": 0.3, \"line\": {\"width\": 0}},\n        name=\"Not significant\",\n        hovertemplate=\"A: %{x:.1f}<br>M: %{y:.2f}<extra>Not significant</extra>\",\n    )\n)\n\n# Significant genes — Imprint brand green (first series)\nfig.add_trace(\n    go.Scatter(\n        x=mean_expression[sig_mask],\n        y=log_fold_change[sig_mask],\n        mode=\"markers\",\n        marker={\"size\": 8, \"color\": COLOR_SIG, \"opacity\": 0.65, \"line\": {\"width\": 0.5, \"color\": PAGE_BG}},\n        name=\"Significant (padj < 0.05)\",\n        hovertemplate=\"A: %{x:.1f}<br>M: %{y:.2f}<extra>Significant</extra>\",\n    )\n)\n\n# LOESS smoothing curve — Imprint blue\nfig.add_trace(\n    go.Scatter(\n        x=sorted_expr,\n        y=smoothed,\n        mode=\"lines\",\n        line={\"color\": COLOR_LOESS, \"width\": 3},\n        name=\"LOESS trend\",\n        hoverinfo=\"skip\",\n    )\n)\n\n# Reference line at M = 0 (no fold change)\nfig.add_hline(y=0, line={\"color\": INK_SOFT, \"width\": 1.5, \"dash\": \"dot\"})\n\n# Fold-change threshold lines (amber = caution/threshold role)\nfig.add_hline(y=1, line={\"color\": COLOR_THRESHOLD, \"width\": 1.5, \"dash\": \"dash\"})\nfig.add_hline(y=-1, line={\"color\": COLOR_THRESHOLD, \"width\": 1.5, \"dash\": \"dash\"})\n\n# Threshold labels — right-justified on the threshold lines, inside plot\nfig.add_annotation(\n    x=15.2,\n    y=1,\n    text=\"2-fold up\",\n    showarrow=False,\n    font={\"size\": 12, \"color\": COLOR_THRESHOLD},\n    xanchor=\"right\",\n    yanchor=\"bottom\",\n    yshift=4,\n    bgcolor=ELEVATED_BG,\n    borderpad=2,\n)\nfig.add_annotation(\n    x=15.2,\n    y=-1,\n    text=\"2-fold down\",\n    showarrow=False,\n    font={\"size\": 12, \"color\": COLOR_THRESHOLD},\n    xanchor=\"right\",\n    yanchor=\"top\",\n    yshift=-4,\n    bgcolor=ELEVATED_BG,\n    borderpad=2,\n)\n\n# Label top DE genes with arrow connectors to avoid overlap with data points\nlabel_indices = [sig_indices[i] for i in top_de]\nexpr_mid = (mean_expression.max() + mean_expression.min()) / 2\nfor i, gene_idx in enumerate(label_indices):\n    lfc = log_fold_change[gene_idx]\n    expr = mean_expression[gene_idx]\n    # Right-side genes point left; left-side genes alternate direction to reduce crowding\n    ax = -32 if expr > expr_mid else (28 if i % 2 == 0 else -28)\n    ay = -35 if lfc > 0 else 35\n    fig.add_annotation(\n        x=expr,\n        y=lfc,\n        text=gene_names[gene_idx],\n        showarrow=True,\n        arrowhead=2,\n        arrowsize=0.8,\n        arrowwidth=1.2,\n        arrowcolor=INK_SOFT,\n        ax=ax,\n        ay=ay,\n        font={\"size\": 10, \"color\": INK},\n        bgcolor=ELEVATED_BG,\n        bordercolor=INK_SOFT,\n        borderwidth=0.5,\n        borderpad=3,\n        opacity=0.9,\n    )\n\ntitle_text = \"ma-differential-expression · python · plotly · anyplot.ai\"\ntitle_fontsize = max(11, round(16 * min(1.0, 67 / len(title_text))))\n\nfig.update_layout(\n    autosize=False,\n    title={\n        \"text\": title_text,\n        \"font\": {\"size\": title_fontsize, \"color\": INK, \"family\": \"Arial, sans-serif\"},\n        \"x\": 0.5,\n        \"xanchor\": \"center\",\n    },\n    xaxis={\n        \"title\": {\"text\": \"Mean Expression (A)\", \"font\": {\"size\": 12, \"color\": INK}},\n        \"tickfont\": {\"size\": 10, \"color\": INK_SOFT},\n        \"showgrid\": False,\n        \"zeroline\": False,\n        \"showline\": False,\n    },\n    yaxis={\n        \"title\": {\"text\": \"Log₂ Fold Change (M)\", \"font\": {\"size\": 12, \"color\": INK}},\n        \"tickfont\": {\"size\": 10, \"color\": INK_SOFT},\n        \"gridcolor\": GRID,\n        \"showgrid\": True,\n        \"zeroline\": False,\n        \"linecolor\": INK_SOFT,\n    },\n    paper_bgcolor=PAGE_BG,\n    plot_bgcolor=PAGE_BG,\n    font={\"color\": INK},\n    legend={\n        \"font\": {\"size\": 10, \"color\": INK_SOFT},\n        \"x\": 0.86,\n        \"y\": 0.98,\n        \"xanchor\": \"right\",\n        \"bgcolor\": ELEVATED_BG,\n        \"bordercolor\": INK_SOFT,\n        \"borderwidth\": 1,\n    },\n    margin={\"l\": 80, \"r\": 40, \"t\": 80, \"b\": 60},\n)\n\n# Save — canonical 3200×1800 landscape (width=800, height=450, scale=4)\nfig.write_image(f\"plot-{THEME}.png\", width=800, height=450, scale=4)\nfig.write_html(f\"plot-{THEME}.html\", include_plotlyjs=\"cdn\")\n"}