{"spec_id":"silhouette-basic","library":"altair","language":"python","code":"\"\"\" anyplot.ai\nsilhouette-basic: Silhouette Plot\nLibrary: altair 6.1.0 | Python 3.13.13\nQuality: 62/100 | Updated: 2026-05-10\n\"\"\"\n\nimport altair as alt\nimport numpy as np\nimport pandas as pd\nfrom sklearn.cluster import KMeans\nfrom sklearn.datasets import load_iris\nfrom sklearn.metrics import silhouette_samples, silhouette_score\n\n\n# Data - Use iris dataset for realistic clustering example\nnp.random.seed(42)\niris = load_iris()\nX = iris.data\n\n# Perform K-means clustering with 3 clusters\nn_clusters = 3\nkmeans = KMeans(n_clusters=n_clusters, random_state=42, n_init=10)\ncluster_labels = kmeans.fit_predict(X)\n\n# Calculate silhouette scores\nsilhouette_avg = silhouette_score(X, cluster_labels)\nsample_silhouette_values = silhouette_samples(X, cluster_labels)\n\n# Build dataframe with samples sorted by cluster and silhouette score\n# Horizontal bars: y-axis is sample position, x-axis is silhouette score\nrecords = []\ny_lower = 0\n\nfor cluster_id in range(n_clusters):\n    cluster_mask = cluster_labels == cluster_id\n    cluster_silhouette_values = sample_silhouette_values[cluster_mask]\n    cluster_silhouette_values.sort()\n\n    cluster_size = len(cluster_silhouette_values)\n    cluster_avg = np.mean(cluster_silhouette_values)\n\n    for i, sil_value in enumerate(cluster_silhouette_values):\n        records.append(\n            {\n                \"y\": y_lower + i,\n                \"y2\": y_lower + i + 0.8,  # Bar thickness\n                \"silhouette\": sil_value,\n                \"cluster\": f\"Cluster {cluster_id} (avg: {cluster_avg:.2f})\",\n                \"cluster_id\": cluster_id,\n            }\n        )\n\n    y_lower += cluster_size + 5  # Gap between clusters\n\ndf = pd.DataFrame(records)\n\n# Color palette - Python Blue as primary, then distinct colors\ncolors = [\"#306998\", \"#FFD43B\", \"#E34C26\"]\n\n# Create horizontal silhouette bars using mark_rect for proper horizontal bars\nbars = (\n    alt.Chart(df)\n    .mark_rect()\n    .encode(\n        x=alt.X(\"silhouette:Q\", title=\"Silhouette Coefficient\", scale=alt.Scale(domain=[-0.15, 1.0])),\n        x2=alt.value(0),  # Bars start from 0 and extend to silhouette value\n        y=alt.Y(\"y:Q\", axis=None),\n        y2=\"y2:Q\",\n        color=alt.Color(\n            \"cluster:N\",\n            title=\"Cluster\",\n            scale=alt.Scale(domain=df[\"cluster\"].unique().tolist(), range=colors),\n            legend=alt.Legend(titleFontSize=18, labelFontSize=16, symbolSize=200),\n        ),\n        tooltip=[\n            alt.Tooltip(\"cluster:N\", title=\"Cluster\"),\n            alt.Tooltip(\"silhouette:Q\", title=\"Silhouette Score\", format=\".3f\"),\n        ],\n    )\n)\n\n# Vertical line for average silhouette score\navg_line_data = pd.DataFrame({\"avg_silhouette\": [silhouette_avg]})\navg_line = (\n    alt.Chart(avg_line_data)\n    .mark_rule(color=\"#E63946\", strokeWidth=3, strokeDash=[8, 4])\n    .encode(\n        x=alt.X(\"avg_silhouette:Q\"), tooltip=[alt.Tooltip(\"avg_silhouette:Q\", title=\"Average Silhouette\", format=\".3f\")]\n    )\n)\n\n# Annotation for average line\navg_text = (\n    alt.Chart(pd.DataFrame({\"x\": [silhouette_avg + 0.02], \"y\": [5], \"text\": [f\"Avg: {silhouette_avg:.3f}\"]}))\n    .mark_text(fontSize=18, fontWeight=\"bold\", color=\"#E63946\", align=\"left\")\n    .encode(x=\"x:Q\", y=\"y:Q\", text=\"text:N\")\n)\n\n# Combine layers\nchart = (\n    alt.layer(bars, avg_line, avg_text)\n    .properties(width=1600, height=900, title=alt.Title(\"silhouette-basic · altair · pyplots.ai\", fontSize=28))\n    .configure_axis(labelFontSize=18, titleFontSize=22)\n    .configure_legend(titleFontSize=18, labelFontSize=16)\n)\n\n# Save as PNG (4800x2700 at scale_factor=3)\nchart.save(\"plot.png\", scale_factor=3.0)\n\n# Save as HTML for interactivity\nchart.save(\"plot.html\")\n"}