{"spec_id":"ma-differential-expression","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\nma-differential-expression: MA Plot for Differential Expression\nLibrary: letsplot 4.10.1 | Python 3.13.14\nQuality: 91/100 | Updated: 2026-06-21\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom lets_plot import (\n    LetsPlot,\n    aes,\n    coord_cartesian,\n    element_blank,\n    element_line,\n    element_rect,\n    element_text,\n    geom_hline,\n    geom_label,\n    geom_point,\n    geom_segment,\n    geom_smooth,\n    ggplot,\n    ggsize,\n    labs,\n    layer_tooltips,\n    scale_color_manual,\n    theme,\n    theme_minimal,\n)\nfrom lets_plot.export import ggsave\n\n\nLetsPlot.setup_html()\n\n# Theme tokens (see prompts/default-style-guide.md \"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\"\n\n# Imprint palette — semantic: green = gain/up-regulated, red = loss/down-regulated\nUP_COLOR = \"#009E73\"  # Imprint position 1: brand green (growth/gain)\nDOWN_COLOR = \"#AE3030\"  # Imprint position 5: matte red (loss/error)\nANYPLOT_AMBER = \"#DDCC77\"  # caution anchor — LOESS bias indicator\n\n# Data\nnp.random.seed(42)\nn_genes = 15000\n\nmean_expression = np.random.uniform(0.5, 15, n_genes)\n\n# Expression-dependent variance creates funnel shape (wider spread at low expression)\nlog_fold_change = np.random.normal(0, 0.3, n_genes)\nlow_expr_bias = 0.4 * np.exp(-0.3 * mean_expression)\nlog_fold_change += np.random.normal(0, low_expr_bias)\n\n# Differentially expressed genes — only among moderately expressed genes\nn_up, n_down = 420, 380\nup_idx = np.random.choice(np.where(mean_expression > 2)[0], n_up, replace=False)\ndown_idx = np.random.choice(np.setdiff1d(np.where(mean_expression > 2)[0], up_idx), n_down, replace=False)\nlog_fold_change[up_idx] = np.random.uniform(1.2, 5.5, n_up)\nlog_fold_change[down_idx] = np.random.uniform(-5.5, -1.2, n_down)\n\np_values = np.ones(n_genes)\np_values[up_idx] = np.random.uniform(1e-20, 0.01, n_up)\np_values[down_idx] = np.random.uniform(1e-20, 0.01, n_down)\n\nsignificant = p_values < 0.05\nstatus = np.where(~significant, \"Not significant\", np.where(log_fold_change > 0, \"Up-regulated\", \"Down-regulated\"))\n\ndf = pd.DataFrame(\n    {\n        \"A\": mean_expression,\n        \"M\": log_fold_change,\n        \"status\": pd.Categorical(status, categories=[\"Down-regulated\", \"Not significant\", \"Up-regulated\"]),\n    }\n)\ndf_nonsig = df[df[\"status\"] == \"Not significant\"]\ndf_sig = df[df[\"status\"] != \"Not significant\"]\n\n# Top gene labels — real cancer biology gene names\nreal_names_up = [\"FOXM1\", \"CDK1\", \"MYC\", \"EGFR\"]\nreal_names_down = [\"CDKN1A\", \"RB1\", \"BRCA2\", \"TP53\"]\ntop_up = up_idx[np.argsort(log_fold_change[up_idx])[-4:]]\ntop_down = down_idx[np.argsort(log_fold_change[down_idx])[:4]]\ntop_idx = np.concatenate([top_up, top_down])\n\nnudges = [(-1.2, 0.6), (0.8, 0.9), (-1.0, 1.4), (1.2, 0.5), (0.9, -0.6), (-1.1, -0.9), (-0.6, -1.4), (1.0, -0.5)]\ndf_labels = pd.DataFrame(\n    {\n        \"A\": mean_expression[top_idx],\n        \"M\": log_fold_change[top_idx],\n        \"gene\": real_names_up + real_names_down,\n        \"label_x\": mean_expression[top_idx] + [n[0] for n in nudges],\n        \"label_y\": log_fold_change[top_idx] + [n[1] for n in nudges],\n        \"regulation\": [\"Up-regulated\"] * 4 + [\"Down-regulated\"] * 4,\n    }\n)\n\n# Plot — ggplot(df) as base for idiomatic single-data approach\nplot = (\n    ggplot(df, aes(x=\"A\", y=\"M\"))\n    + geom_hline(yintercept=0, color=INK, size=0.8)\n    + geom_hline(yintercept=1, color=INK_SOFT, size=0.5, linetype=\"dashed\")\n    + geom_hline(yintercept=-1, color=INK_SOFT, size=0.5, linetype=\"dashed\")\n    + geom_point(data=df_nonsig, color=INK_MUTED, size=1.0, alpha=0.20)\n    + geom_point(\n        aes(color=\"status\"),\n        data=df_sig,\n        size=2.5,\n        alpha=0.70,\n        tooltips=layer_tooltips()\n        .line(\"@status\")\n        .line(\"Mean expr: @A\")\n        .line(\"Log₂FC: @M\")\n        .format(\"A\", \".1f\")\n        .format(\"M\", \".2f\"),\n    )\n    + geom_smooth(color=ANYPLOT_AMBER, size=1.5, se=False, method=\"loess\", span=0.3)\n    + geom_segment(\n        aes(x=\"A\", y=\"M\", xend=\"label_x\", yend=\"label_y\"), data=df_labels, color=INK_SOFT, size=0.4, linetype=\"dotted\"\n    )\n    + geom_label(\n        aes(x=\"label_x\", y=\"label_y\", label=\"gene\", color=\"regulation\"),\n        data=df_labels,\n        size=4,\n        fill=ELEVATED_BG,\n        alpha=0.90,\n        label_padding=0.3,\n        label_r=0.2,\n        label_size=0.5,\n        show_legend=False,\n    )\n    + scale_color_manual(values={\"Up-regulated\": UP_COLOR, \"Down-regulated\": DOWN_COLOR}, name=\"Regulation\")\n    + labs(\n        x=\"Mean Expression (A)\",\n        y=\"Log₂ Fold Change (M)\",\n        title=\"ma-differential-expression · python · letsplot · anyplot.ai\",\n    )\n    + coord_cartesian(xlim=[0, 16])\n    + ggsize(800, 450)\n    + theme_minimal()\n    + theme(\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG),\n        panel_grid_major=element_line(color=INK_SOFT, size=0.2),\n        panel_grid_major_x=element_blank(),\n        panel_grid_minor=element_blank(),\n        axis_title=element_text(color=INK, size=12),\n        axis_text=element_text(color=INK_SOFT, size=10),\n        axis_line=element_line(color=INK_SOFT),\n        plot_title=element_text(color=INK, size=16),\n        legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n        legend_text=element_text(color=INK_SOFT, size=10),\n        legend_title=element_text(color=INK, size=10),\n        legend_position=\"bottom\",\n        plot_margin=[30, 20, 10, 20],\n    )\n)\n\n# Save\nggsave(plot, f\"plot-{THEME}.png\", path=\".\", scale=4)\nggsave(plot, f\"plot-{THEME}.html\", path=\".\")\n"}