{"spec_id":"silhouette-basic","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\nsilhouette-basic: Silhouette Plot\nLibrary: plotnine 0.15.4 | Python 3.13.13\nQuality: 89/100 | Updated: 2026-05-10\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom plotnine import (\n    aes,\n    element_blank,\n    element_line,\n    element_rect,\n    element_text,\n    geom_segment,\n    geom_text,\n    geom_vline,\n    ggplot,\n    labs,\n    scale_color_manual,\n    theme,\n    theme_minimal,\n    xlim,\n)\nfrom sklearn.cluster import KMeans\nfrom sklearn.datasets import load_iris\nfrom sklearn.metrics import silhouette_samples, silhouette_score\n\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# Okabe-Ito palette\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\"]\n\n# Data - Clustering iris dataset into 3 groups\nnp.random.seed(42)\niris = load_iris()\nX = iris.data\nn_clusters = 3\n\n# Perform K-means clustering\nkmeans = KMeans(n_clusters=n_clusters, random_state=42, n_init=10)\ncluster_labels = kmeans.fit_predict(X)\n\n# Calculate silhouette scores\nsilhouette_vals = silhouette_samples(X, cluster_labels)\navg_silhouette = silhouette_score(X, cluster_labels)\n\n# Build dataframe for plotting - sort samples within each cluster by silhouette score\ndata_rows = []\ny_position = 0\ncluster_centers = []\ncluster_avg_scores = []\n\nfor cluster_idx in range(n_clusters):\n    # Get samples in this cluster\n    mask = cluster_labels == cluster_idx\n    cluster_silhouettes = silhouette_vals[mask]\n    cluster_silhouettes_sorted = np.sort(cluster_silhouettes)\n\n    # Calculate cluster average\n    cluster_avg = cluster_silhouettes.mean()\n    cluster_avg_scores.append(cluster_avg)\n\n    # Track the center position for annotation\n    cluster_start = y_position\n\n    # Add each sample as a row\n    for sil_val in cluster_silhouettes_sorted:\n        data_rows.append({\"y\": y_position, \"silhouette\": sil_val, \"cluster\": f\"Cluster {cluster_idx}\"})\n        y_position += 1\n\n    cluster_end = y_position - 1\n    cluster_centers.append((cluster_start + cluster_end) / 2)\n\n    # Add small gap between clusters\n    y_position += 8\n\ndf = pd.DataFrame(data_rows)\ndf[\"x_start\"] = 0  # Starting x position for horizontal bars\n\n# Create annotation dataframe for cluster labels\nannotation_df = pd.DataFrame(\n    {\n        \"y\": cluster_centers,\n        \"x\": [-0.08] * n_clusters,\n        \"label\": [f\"Cluster {i}\\n(avg: {cluster_avg_scores[i]:.2f})\" for i in range(n_clusters)],\n    }\n)\n\n# Create average line label dataframe\navg_label_df = pd.DataFrame(\n    {\"x\": [avg_silhouette + 0.02], \"y\": [max(df[\"y\"]) * 0.95], \"label\": [f\"Avg: {avg_silhouette:.2f}\"]}\n)\n\n# Create the silhouette plot using horizontal segments\nplot = (\n    ggplot()\n    + geom_segment(aes(x=\"x_start\", xend=\"silhouette\", y=\"y\", yend=\"y\", color=\"cluster\"), data=df, size=1.5)\n    + geom_vline(xintercept=avg_silhouette, color=INK_SOFT, linetype=\"dashed\", size=1.2, alpha=0.6)\n    + geom_text(aes(x=\"x\", y=\"y\", label=\"label\"), data=annotation_df, size=12, ha=\"right\", color=INK_SOFT)\n    + geom_text(aes(x=\"x\", y=\"y\", label=\"label\"), data=avg_label_df, size=11, ha=\"left\", color=INK_SOFT)\n    + scale_color_manual(values=IMPRINT)\n    + labs(\n        x=\"Silhouette Coefficient\",\n        y=\"Sample Index (sorted within cluster)\",\n        title=\"silhouette-basic · plotnine · anyplot.ai\",\n    )\n    + xlim(-0.25, 1.0)\n    + theme_minimal()\n    + theme(\n        figure_size=(16, 9),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG),\n        panel_grid_major_y=element_blank(),\n        panel_grid_minor_y=element_blank(),\n        panel_grid_major_x=element_line(color=INK, size=0.3, alpha=0.10),\n        panel_grid_minor_x=element_line(color=INK, size=0.2, alpha=0.05),\n        panel_border=element_rect(color=INK_SOFT, fill=None, size=0.5),\n        plot_title=element_text(size=24, color=INK),\n        axis_title=element_text(size=20, color=INK),\n        axis_text_x=element_text(size=16, color=INK_SOFT),\n        axis_text_y=element_blank(),\n        axis_ticks_major_y=element_blank(),\n        legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n        legend_text=element_text(size=16, color=INK_SOFT),\n        legend_title=element_text(size=18, color=INK),\n        legend_position=\"right\",\n    )\n)\n\n# Save as PNG\nplot.save(f\"plot-{THEME}.png\", dpi=300, verbose=False)\n"}