{"spec_id":"contour-decision-boundary","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\ncontour-decision-boundary: Decision Boundary Classifier Visualization\nLibrary: letsplot 4.9.0 | Python 3.13.13\nQuality: 84/100 | Updated: 2026-05-16\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom lets_plot import (\n    LetsPlot,\n    aes,\n    element_blank,\n    element_rect,\n    element_text,\n    geom_point,\n    geom_tile,\n    ggplot,\n    ggsave,\n    ggsize,\n    labs,\n    scale_color_manual,\n    scale_fill_manual,\n    scale_shape_manual,\n    theme,\n    theme_minimal,\n)\nfrom sklearn.datasets import make_moons\nfrom sklearn.neighbors import KNeighborsClassifier\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\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\"]\n\n# Data\nnp.random.seed(42)\nX, y = make_moons(n_samples=200, noise=0.25, random_state=42)\n\n# Train classifier\nclassifier = KNeighborsClassifier(n_neighbors=5)\nclassifier.fit(X, y)\n\n# Create mesh grid for decision boundary\nh = 0.02\nx_min, x_max = X[:, 0].min() - 0.5, X[:, 0].max() + 0.5\ny_min, y_max = X[:, 1].min() - 0.5, X[:, 1].max() + 0.5\nxx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))\n\n# Predict on mesh\nZ = classifier.predict(np.c_[xx.ravel(), yy.ravel()])\nZ = Z.reshape(xx.shape)\n\n# Create dataframes\nmesh_df = pd.DataFrame({\"X1\": xx.ravel(), \"X2\": yy.ravel(), \"Predicted\": Z.ravel().astype(str)})\ntrain_df = pd.DataFrame({\"X1\": X[:, 0], \"X2\": X[:, 1], \"Class\": y.astype(str)})\npredictions = classifier.predict(X)\ntrain_df[\"Correct\"] = np.where(predictions == y, \"Correct\", \"Incorrect\")\n\n# Plot\nplot = (\n    ggplot()\n    + geom_tile(aes(x=\"X1\", y=\"X2\", fill=\"Predicted\"), data=mesh_df, alpha=0.4)\n    + geom_point(aes(x=\"X1\", y=\"X2\", color=\"Class\", shape=\"Correct\"), data=train_df, size=5, stroke=1.5)\n    + scale_fill_manual(values=[IMPRINT[0], IMPRINT[1]], name=\"Predicted Class\")\n    + scale_color_manual(values=[IMPRINT[0], IMPRINT[1]], name=\"True Class\")\n    + scale_shape_manual(values=[16, 4], name=\"Classification\")\n    + labs(title=\"contour-decision-boundary · letsplot · anyplot.ai\", x=\"Feature X1\", y=\"Feature X2\")\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_blank(),\n        panel_grid_minor=element_blank(),\n        plot_title=element_text(size=24, color=INK),\n        axis_title=element_text(size=20, color=INK),\n        axis_text=element_text(size=16, 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        legend_position=\"right\",\n    )\n    + ggsize(1600, 900)\n)\n\n# Save\nggsave(plot, filename=f\"plot-{THEME}.png\", path=\".\", scale=3)\nggsave(plot, filename=f\"plot-{THEME}.html\", path=\".\")\n"}