{"spec_id":"heatmap-clustered","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\nheatmap-clustered: Clustered Heatmap\nLibrary: letsplot 4.9.0 | Python 3.13.13\nQuality: 92/100 | Updated: 2026-05-09\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom lets_plot import *\nfrom lets_plot.export import ggsave\nfrom scipy.cluster.hierarchy import dendrogram, linkage\n\n\nLetsPlot.setup_html()\n\n# Theme tokens\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\"\n\n# Data - Gene expression data for clustering analysis\nnp.random.seed(42)\n\n# Generate gene names and sample names\nn_genes = 20\nn_samples = 15\ngene_names = [f\"Gene_{i + 1:02d}\" for i in range(n_genes)]\nsample_names = [f\"Sample_{chr(65 + i)}\" for i in range(n_samples)]\n\n# Create gene expression data with cluster structure\nexpression_data = np.random.randn(n_genes, n_samples)\n\n# Add cluster structure for genes\nfor i in range(0, 7):\n    expression_data[i, 0:5] += 2.5\n    expression_data[i, 10:15] -= 1.5\n\nfor i in range(7, 14):\n    expression_data[i, 5:10] += 2.5\n    expression_data[i, 0:5] -= 1.0\n\nfor i in range(14, 20):\n    expression_data[i, 10:15] += 2.5\n    expression_data[i, 5:10] -= 1.5\n\n# Add some noise variation\nexpression_data += np.random.randn(n_genes, n_samples) * 0.3\n\n# Hierarchical clustering of rows (genes) and columns (samples)\nrow_linkage = linkage(expression_data, method=\"ward\")\ncol_linkage = linkage(expression_data.T, method=\"ward\")\n\n# Get dendrogram ordering\nrow_dendro = dendrogram(row_linkage, no_plot=True)\ncol_dendro = dendrogram(col_linkage, no_plot=True)\nrow_order = row_dendro[\"leaves\"]\ncol_order = col_dendro[\"leaves\"]\n\n# Reorder data based on clustering\nreordered_data = expression_data[row_order, :][:, col_order]\nreordered_genes = [gene_names[i] for i in row_order]\nreordered_samples = [sample_names[i] for i in col_order]\n\n# Create long-form data for heatmap\nheatmap_rows = []\nfor i, gene in enumerate(reordered_genes):\n    for j, sample in enumerate(reordered_samples):\n        heatmap_rows.append(\n            {\"Gene\": gene, \"Sample\": sample, \"Expression\": reordered_data[i, j], \"gene_idx\": i, \"sample_idx\": j}\n        )\n\nheatmap_df = pd.DataFrame(heatmap_rows)\nheatmap_df[\"Gene\"] = pd.Categorical(heatmap_df[\"Gene\"], categories=reordered_genes[::-1], ordered=True)\nheatmap_df[\"Sample\"] = pd.Categorical(heatmap_df[\"Sample\"], categories=reordered_samples, ordered=True)\n\n# Extract column dendrogram segments\ncol_segments = []\ncol_icoord = np.array(col_dendro[\"icoord\"])\ncol_dcoord = np.array(col_dendro[\"dcoord\"])\nfor i in range(len(col_icoord)):\n    xs = col_icoord[i]\n    ys = col_dcoord[i]\n    col_segments.append((xs[0], ys[0], xs[1], ys[1]))\n    col_segments.append((xs[1], ys[1], xs[2], ys[2]))\n    col_segments.append((xs[2], ys[2], xs[3], ys[3]))\ncol_seg_df = pd.DataFrame(col_segments, columns=[\"x\", \"y\", \"xend\", \"yend\"])\n\n# Extract row dendrogram segments\nrow_segments = []\nrow_icoord = np.array(row_dendro[\"icoord\"])\nrow_dcoord = np.array(row_dendro[\"dcoord\"])\nfor i in range(len(row_icoord)):\n    xs = row_icoord[i]\n    ys = row_dcoord[i]\n    row_segments.append((xs[0], ys[0], xs[1], ys[1]))\n    row_segments.append((xs[1], ys[1], xs[2], ys[2]))\n    row_segments.append((xs[2], ys[2], xs[3], ys[3]))\nrow_seg_df = pd.DataFrame(row_segments, columns=[\"x\", \"y\", \"xend\", \"yend\"])\n\n# Scale dendrogram coordinates to match heatmap\ncol_seg_df[\"x\"] = (col_seg_df[\"x\"] / 10) - 0.5\ncol_seg_df[\"xend\"] = (col_seg_df[\"xend\"] / 10) - 0.5\nmax_col_height = max(col_seg_df[\"y\"].max(), col_seg_df[\"yend\"].max())\ncol_seg_df[\"y\"] = col_seg_df[\"y\"] / max_col_height * 4\ncol_seg_df[\"yend\"] = col_seg_df[\"yend\"] / max_col_height * 4\n\n# Row dendrogram: swap x and y for horizontal orientation\nrow_seg_df_rotated = pd.DataFrame(\n    {\n        \"x\": row_seg_df[\"y\"],\n        \"y\": (row_seg_df[\"x\"] / 10) - 0.5,\n        \"xend\": row_seg_df[\"yend\"],\n        \"yend\": (row_seg_df[\"xend\"] / 10) - 0.5,\n    }\n)\nmax_row_height = max(row_seg_df_rotated[\"x\"].max(), row_seg_df_rotated[\"xend\"].max())\nrow_seg_df_rotated[\"x\"] = row_seg_df_rotated[\"x\"] / max_row_height * 4\nrow_seg_df_rotated[\"xend\"] = row_seg_df_rotated[\"xend\"] / max_row_height * 4\n\n# Theme for plots\nanyplot_theme = theme(\n    plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n    panel_background=element_rect(fill=PAGE_BG),\n    panel_grid=element_blank(),\n    axis_title=element_text(color=INK, size=20),\n    axis_text=element_text(color=INK_SOFT),\n    axis_line=element_line(color=INK_SOFT, size=0.5),\n    plot_title=element_text(color=INK, size=24, face=\"bold\"),\n    legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n    legend_text=element_text(color=INK_SOFT, size=16),\n    legend_title=element_text(color=INK, size=16),\n)\n\n# Create heatmap plot\nheatmap_plot = (\n    ggplot(heatmap_df, aes(x=\"Sample\", y=\"Gene\", fill=\"Expression\"))\n    + geom_tile(width=0.95, height=0.95)\n    + scale_fill_gradient2(low=\"#A6611A\", mid=\"#F5F5F5\", high=\"#018571\", midpoint=0, name=\"Expression\\n(z-score)\")\n    + labs(x=\"Samples\", y=\"Genes\", title=\"heatmap-clustered · letsplot · anyplot.ai\")\n    + theme_minimal()\n    + theme(\n        plot_title=element_text(size=24, face=\"bold\"),\n        axis_title_x=element_text(size=20),\n        axis_title_y=element_text(size=20),\n        axis_text_x=element_text(size=16, angle=45, hjust=1),\n        axis_text_y=element_text(size=16),\n        legend_title=element_text(size=16),\n        legend_text=element_text(size=16),\n        panel_grid=element_blank(),\n    )\n    + anyplot_theme\n)\n\n# Create column dendrogram (top)\ncol_dendro_plot = (\n    ggplot(col_seg_df)\n    + geom_segment(aes(x=\"x\", y=\"y\", xend=\"xend\", yend=\"yend\"), size=1.2, color=INK_SOFT)\n    + scale_x_continuous(limits=[-0.5, n_samples - 0.5], expand=[0, 0])\n    + scale_y_continuous(expand=[0.02, 0])\n    + theme_void()\n    + theme(plot_margin=[0, 0, 0, 0])\n)\n\n# Create row dendrogram (left) - needs to be mirrored\nrow_seg_df_rotated[\"x\"] = 4 - row_seg_df_rotated[\"x\"]\nrow_seg_df_rotated[\"xend\"] = 4 - row_seg_df_rotated[\"xend\"]\n\nrow_dendro_plot = (\n    ggplot(row_seg_df_rotated)\n    + geom_segment(aes(x=\"x\", y=\"y\", xend=\"xend\", yend=\"yend\"), size=1.2, color=INK_SOFT)\n    + scale_x_continuous(expand=[0, 0.02])\n    + scale_y_continuous(limits=[-0.5, n_genes - 0.5], expand=[0, 0])\n    + theme_void()\n    + theme(plot_margin=[0, 0, 0, 0])\n)\n\n# Combine plots using ggbunch\nrow_dendro_w = 0.12\ncol_dendro_h = 0.18\nheatmap_w = 0.88\nheatmap_h = 0.82\n\nbunch = ggbunch(\n    [row_dendro_plot, col_dendro_plot, heatmap_plot],\n    [\n        (0, col_dendro_h, row_dendro_w, heatmap_h),\n        (row_dendro_w, 0, heatmap_w, col_dendro_h),\n        (row_dendro_w, col_dendro_h, heatmap_w, heatmap_h),\n    ],\n) + ggsize(1600, 900)\n\n# Save as PNG (scale 3x for 4800x2700)\nggsave(bunch, f\"plot-{THEME}.png\", path=\".\", scale=3)\n\n# Save interactive HTML\nggsave(bunch, f\"plot-{THEME}.html\", path=\".\")\n"}