{"spec_id":"confusion-matrix","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\nconfusion-matrix: Confusion Matrix Heatmap\nLibrary: letsplot 4.9.0 | Python 3.13.13\nQuality: 86/100 | Updated: 2026-05-09\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom lets_plot import (\n    LetsPlot,\n    aes,\n    coord_fixed,\n    element_blank,\n    element_rect,\n    element_text,\n    geom_text,\n    geom_tile,\n    ggplot,\n    ggsave,\n    ggsize,\n    labs,\n    scale_fill_gradient,\n    theme,\n    theme_minimal,\n)\n\n\nLetsPlot.setup_html()\n\n# Theme tokens (see prompts/default-style-guide.md)\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 - Multi-class classification results for image classifier\nnp.random.seed(42)\n\nclass_names = [\"Cat\", \"Dog\", \"Bird\", \"Fish\"]\nn_classes = len(class_names)\n\n# Create a realistic confusion matrix with strong diagonal\n# and some realistic misclassification patterns\nconfusion_data = np.array(\n    [\n        [45, 8, 3, 2],  # Cat: sometimes confused with Dog\n        [6, 52, 4, 1],  # Dog: sometimes confused with Cat\n        [2, 3, 38, 5],  # Bird: sometimes confused with Fish\n        [1, 2, 7, 41],  # Fish: sometimes confused with Bird\n    ]\n)\n\n# Build long-form data for geom_tile\nrows = []\nfor i, true_label in enumerate(class_names):\n    for j, pred_label in enumerate(class_names):\n        count = confusion_data[i, j]\n        rows.append(\n            {\"True Label\": true_label, \"Predicted Label\": pred_label, \"Count\": count, \"true_idx\": i, \"pred_idx\": j}\n        )\n\ndf = pd.DataFrame(rows)\n\n# Calculate percentages for annotation (row normalization = recall)\ntotal_per_row = confusion_data.sum(axis=1, keepdims=True)\npercentages = (confusion_data / total_per_row * 100).astype(int)\ndf[\"Percentage\"] = [percentages[r[\"true_idx\"], r[\"pred_idx\"]] for _, r in df.iterrows()]\ndf[\"Label\"] = df.apply(lambda r: f\"{r['Count']}\\n({r['Percentage']}%)\", axis=1)\n\n# Set category order for proper matrix layout\ndf[\"True Label\"] = pd.Categorical(df[\"True Label\"], categories=class_names[::-1], ordered=True)\ndf[\"Predicted Label\"] = pd.Categorical(df[\"Predicted Label\"], categories=class_names, ordered=True)\n\n# Determine text color based on count (theme-adaptive)\nmax_count = df[\"Count\"].max()\nif THEME == \"light\":\n    df[\"text_color\"] = df[\"Count\"].apply(lambda c: \"#1A1A17\" if c > max_count * 0.4 else \"#4A4A44\")\nelse:\n    df[\"text_color\"] = df[\"Count\"].apply(lambda c: \"#F0EFE8\" if c > max_count * 0.4 else \"#B8B7B0\")\n\n# Color gradient for sequential data (Blues)\nif THEME == \"light\":\n    low_color = \"#E7F0F9\"\n    high_color = \"#08519C\"\nelse:\n    low_color = \"#1F3D5C\"\n    high_color = \"#74B3E5\"\n\n# Create confusion matrix heatmap\nplot = (\n    ggplot(df, aes(x=\"Predicted Label\", y=\"True Label\", fill=\"Count\"))\n    + geom_tile(color=INK_SOFT, size=1.5, tooltips=\"none\")\n    + geom_text(aes(label=\"Label\", color=\"text_color\"), size=14, fontface=\"bold\")\n    + scale_fill_gradient(low=low_color, high=high_color, name=\"Count\")\n    + labs(x=\"Predicted Label\", y=\"True Label\", title=\"confusion-matrix · letsplot · anyplot.ai\")\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        plot_title=element_text(size=28, face=\"bold\", color=INK),\n        axis_title=element_text(size=22, color=INK),\n        axis_text=element_text(size=18, color=INK_SOFT),\n        legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n        legend_title=element_text(size=18, color=INK),\n        legend_text=element_text(size=14, color=INK_SOFT),\n        panel_grid=element_blank(),\n    )\n    + ggsize(1200, 1200)\n    + coord_fixed()\n)\n\n# Save as PNG (scale 3x for 3600x3600 px)\nggsave(plot, f\"plot-{THEME}.png\", path=\".\", scale=3)\n\n# Save interactive HTML\nggsave(plot, f\"plot-{THEME}.html\", path=\".\")\n"}