{"spec_id":"upset-basic","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\nupset-basic: UpSet Plot for Multi-Set Intersection Analysis\nLibrary: plotnine 0.15.4 | Python 3.13.13\nQuality: 86/100 | Created: 2026-05-13\n\"\"\"\n\nimport os\nimport sys\n\n\n# Prevent this file from shadowing the installed plotnine package\nsys.path = [p for p in sys.path if p not in (\"\", os.path.dirname(os.path.abspath(__file__)))]\n\nimport numpy as np\nimport pandas as pd\nfrom plotnine import (\n    aes,\n    element_rect,\n    element_text,\n    geom_point,\n    geom_rect,\n    geom_segment,\n    geom_text,\n    ggplot,\n    labs,\n    scale_x_continuous,\n    scale_y_continuous,\n    theme,\n    theme_void,\n)\n\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\nBRAND = \"#009E73\"\nDOT_DIM = \"#C8C7BF\" if THEME == \"light\" else \"#3D3D38\"\n\n# ── Data ──────────────────────────────────────────────────────────────────────\nnp.random.seed(42)\n\nexperiments = [\"RNA-seq A\", \"RNA-seq B\", \"ChIP-seq\", \"ATAC-seq\", \"Proteomics\"]\nn_sets = len(experiments)\nn_genes = 600\n\nprobs = [0.45, 0.40, 0.30, 0.25, 0.20]\nmembership = np.column_stack([np.random.binomial(1, p, n_genes) for p in probs])\nno_set_rows = membership.sum(axis=1) == 0\nfor idx in np.where(no_set_rows)[0]:\n    membership[idx, np.random.randint(0, n_sets)] = 1\n\nset_sizes = membership.sum(axis=0)\n\nintersections = {}\nfor row in membership:\n    key = tuple(row.astype(int))\n    intersections[key] = intersections.get(key, 0) + 1\n\nsorted_ints = sorted(intersections.items(), key=lambda x: -x[1])\nn_cols = 14\ntop_ints = sorted_ints[:n_cols]\n\n# ── Layout coordinates ────────────────────────────────────────────────────────\n# Set rows: set 0 at y=n_sets (top), set n_sets-1 at y=1 (bottom)\nset_row_y = {i: float(n_sets - i) for i in range(n_sets)}\n\nmax_count = top_ints[0][1]\nBAR_BASE = float(n_sets) + 2.0\nBAR_MAX_H = float(n_sets) * 2.2\nbar_scale = BAR_MAX_H / max_count\n\n# Set size bars: horizontal bars extending left from SET_BAR_RIGHT\nmax_set_size = float(max(set_sizes))\nSET_BAR_MAXW = 3.2\nSET_BAR_RIGHT = -0.5\nset_bar_scale = SET_BAR_MAXW / max_set_size\n\n# Set name labels right-aligned between set bars and dot matrix\nSET_NAME_X = 0.75\n\n# ── Component DataFrames ──────────────────────────────────────────────────────\n\n# Intersection bars (geom_rect, one per column)\nint_bars_df = pd.DataFrame(\n    [\n        {\n            \"xmin\": float(i + 1) - 0.35,\n            \"xmax\": float(i + 1) + 0.35,\n            \"ymin\": BAR_BASE,\n            \"ymax\": BAR_BASE + count * bar_scale,\n        }\n        for i, (key, count) in enumerate(top_ints)\n    ]\n)\n\n# Count labels above each bar\ncount_labels_df = pd.DataFrame(\n    [\n        {\"x\": float(i + 1), \"y\": BAR_BASE + count * bar_scale + 0.4, \"label\": str(count)}\n        for i, (key, count) in enumerate(top_ints)\n    ]\n)\n\n# Connecting segments for multi-set intersections\nsegs_rows = []\nfor col_i, (key, _) in enumerate(top_ints):\n    active_ys = [set_row_y[s] for s, v in enumerate(key) if v == 1]\n    if len(active_ys) > 1:\n        segs_rows.append({\"x\": float(col_i + 1), \"xend\": float(col_i + 1), \"y\": min(active_ys), \"yend\": max(active_ys)})\nsegs_df = pd.DataFrame(segs_rows) if segs_rows else pd.DataFrame(columns=[\"x\", \"xend\", \"y\", \"yend\"])\n\n# Dot matrix: all set × column combinations\ndots_rows = [\n    {\"x\": float(col_i + 1), \"y\": set_row_y[s], \"active\": bool(v)}\n    for col_i, (key, _) in enumerate(top_ints)\n    for s, v in enumerate(key)\n]\ndots_df = pd.DataFrame(dots_rows)\nactive_dots = dots_df[dots_df[\"active\"]].copy()\ndim_dots = dots_df[~dots_df[\"active\"]].copy()\n\n# Set size horizontal bars\nset_bars_df = pd.DataFrame(\n    [\n        {\n            \"xmin\": SET_BAR_RIGHT - set_sizes[i] * set_bar_scale,\n            \"xmax\": SET_BAR_RIGHT,\n            \"ymin\": set_row_y[i] - 0.28,\n            \"ymax\": set_row_y[i] + 0.28,\n        }\n        for i in range(n_sets)\n    ]\n)\n\n# Set size number labels (to the left of bars)\nset_size_df = pd.DataFrame(\n    [\n        {\"x\": SET_BAR_RIGHT - set_sizes[i] * set_bar_scale - 0.15, \"y\": set_row_y[i], \"label\": str(int(set_sizes[i]))}\n        for i in range(n_sets)\n    ]\n)\n\n# Set name labels (right-aligned between set bars and dot matrix)\nset_name_df = pd.DataFrame([{\"x\": SET_NAME_X, \"y\": set_row_y[i], \"label\": experiments[i]} for i in range(n_sets)])\n\n# Section header: \"Intersection Size\" above bars\nsection_header_df = pd.DataFrame(\n    [{\"x\": float(n_cols + 1) / 2.0 + 0.5, \"y\": BAR_BASE + BAR_MAX_H + 1.1, \"label\": \"Intersection Size\"}]\n)\n\n# ── Axis limits ───────────────────────────────────────────────────────────────\nx_lo = SET_BAR_RIGHT - SET_BAR_MAXW - 1.0\nx_hi = float(n_cols) + 0.8\ny_lo = 0.2\ny_hi = BAR_BASE + BAR_MAX_H + 1.8\n\n# ── Plot ──────────────────────────────────────────────────────────────────────\nplot = (\n    ggplot()\n    # Intersection bars\n    + geom_rect(\n        data=int_bars_df,\n        mapping=aes(xmin=\"xmin\", xmax=\"xmax\", ymin=\"ymin\", ymax=\"ymax\"),\n        fill=BRAND,\n        color=BRAND,\n        alpha=0.92,\n    )\n    # Count labels above bars\n    + geom_text(data=count_labels_df, mapping=aes(x=\"x\", y=\"y\", label=\"label\"), color=INK_SOFT, size=9, va=\"bottom\")\n    # Connecting lines in dot matrix\n    + geom_segment(data=segs_df, mapping=aes(x=\"x\", xend=\"xend\", y=\"y\", yend=\"yend\"), color=BRAND, size=2.2)\n    # Inactive (dim) dots\n    + geom_point(data=dim_dots, mapping=aes(x=\"x\", y=\"y\"), color=DOT_DIM, fill=DOT_DIM, size=4.0)\n    # Active (highlighted) dots\n    + geom_point(data=active_dots, mapping=aes(x=\"x\", y=\"y\"), color=BRAND, fill=BRAND, size=5.5)\n    # Set size horizontal bars\n    + geom_rect(\n        data=set_bars_df,\n        mapping=aes(xmin=\"xmin\", xmax=\"xmax\", ymin=\"ymin\", ymax=\"ymax\"),\n        fill=INK_SOFT,\n        color=INK_SOFT,\n        alpha=0.65,\n    )\n    # Set size number labels\n    + geom_text(data=set_size_df, mapping=aes(x=\"x\", y=\"y\", label=\"label\"), color=INK_MUTED, size=8.5, ha=\"right\")\n    # Set name labels\n    + geom_text(data=set_name_df, mapping=aes(x=\"x\", y=\"y\", label=\"label\"), color=INK, size=10.5, ha=\"right\")\n    # Section header\n    + geom_text(data=section_header_df, mapping=aes(x=\"x\", y=\"y\", label=\"label\"), color=INK_SOFT, size=10, va=\"bottom\")\n    + labs(title=\"upset-basic · plotnine · anyplot.ai\")\n    + scale_x_continuous(limits=(x_lo, x_hi), expand=(0, 0))\n    + scale_y_continuous(limits=(y_lo, y_hi), expand=(0, 0))\n    + theme_void()\n    + theme(\n        figure_size=(16, 9),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        plot_title=element_text(color=INK, size=20, ha=\"center\"),\n    )\n)\n\n# ── Save ──────────────────────────────────────────────────────────────────────\nplot.save(f\"plot-{THEME}.png\", dpi=300, verbose=False)\n"}