{"spec_id":"confusion-matrix","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\nconfusion-matrix: Confusion Matrix Heatmap\nLibrary: plotnine 0.15.4 | Python 3.13.13\nQuality: 90/100 | Updated: 2026-05-09\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom plotnine import (\n    aes,\n    coord_fixed,\n    element_blank,\n    element_rect,\n    element_text,\n    geom_text,\n    geom_tile,\n    ggplot,\n    labs,\n    scale_color_identity,\n    scale_fill_gradient,\n    theme,\n    theme_minimal,\n)\n\n\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\"\n\n# Data - realistic multi-class classification results\nnp.random.seed(42)\n\n# Class names for a sentiment analysis classifier\nclass_names = [\"Negative\", \"Neutral\", \"Positive\"]\nn_classes = len(class_names)\n\n# Create a realistic confusion matrix with:\n# - Good diagonal (correct predictions)\n# - Some confusion between adjacent sentiment classes\nconfusion_data = np.array(\n    [\n        [85, 12, 3],  # Negative: mostly correct, some confused with Neutral\n        [8, 72, 20],  # Neutral: harder to classify, confused with both\n        [4, 15, 81],  # Positive: mostly correct, some confused with Neutral\n    ]\n)\n\n# Convert to long format for plotnine\nrows = []\nfor i, true_class in enumerate(class_names):\n    for j, pred_class in enumerate(class_names):\n        rows.append({\"True Label\": true_class, \"Predicted Label\": pred_class, \"Count\": confusion_data[i, j]})\n\ndf = pd.DataFrame(rows)\n\n# Set categorical order (reverse for y-axis to have first class at top)\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# Add text color based on count (theme-adaptive: light text on dark cells, dark text on light cells)\nthreshold = (confusion_data.max() + confusion_data.min()) / 2\nlight_text = \"#F0EFE8\" if THEME == \"light\" else \"#FAF8F1\"\ndark_text = \"#1A1A17\" if THEME == \"light\" else \"#E8E8E0\"\ndf[\"text_color\"] = df[\"Count\"].apply(lambda x: light_text if x >= threshold else dark_text)\n\n# Create the confusion matrix heatmap\nplot = (\n    ggplot(df, aes(x=\"Predicted Label\", y=\"True Label\", fill=\"Count\"))\n    + geom_tile(color=INK_SOFT, size=2)\n    + geom_text(aes(label=\"Count\", color=\"text_color\"), size=20, fontweight=\"bold\", show_legend=False)\n    + scale_fill_gradient(\n        low=\"#e8f4f8\" if THEME == \"light\" else \"#2a3f4f\",\n        high=\"#08519c\" if THEME == \"light\" else \"#2ABCCD\",\n        name=\"Sample Count\",\n    )\n    + scale_color_identity()\n    + labs(title=\"confusion-matrix · plotnine · anyplot.ai\", x=\"Predicted Label\", y=\"True Label\")\n    + coord_fixed(ratio=1)\n    + theme_minimal()\n    + theme(\n        figure_size=(12, 12),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG),\n        text=element_text(size=14, color=INK),\n        axis_title=element_text(size=22, weight=\"bold\", color=INK),\n        axis_text=element_text(size=18, color=INK_SOFT),\n        plot_title=element_text(size=24, weight=\"bold\", ha=\"center\", color=INK),\n        legend_title=element_text(size=18, color=INK),\n        legend_text=element_text(size=16, color=INK_SOFT),\n        legend_background=element_rect(fill=PAGE_BG if THEME == \"light\" else \"#242420\", color=INK_SOFT),\n        panel_grid_major=element_blank(),\n        panel_grid_minor=element_blank(),\n    )\n)\n\n# Save the plot\nplot.save(f\"plot-{THEME}.png\", dpi=300, verbose=False)\n"}