{"spec_id":"line-pca-variance-cumulative","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\nline-pca-variance-cumulative: Cumulative Explained Variance for PCA Component Selection\nLibrary: letsplot 4.10.1 | Python 3.13.13\nQuality: 90/100 | Updated: 2026-05-29\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom lets_plot import *\nfrom sklearn.datasets import load_wine\nfrom sklearn.decomposition import PCA\nfrom sklearn.preprocessing import StandardScaler\n\n\nLetsPlot.setup_html()\n\n# Theme tokens — Imprint palette, theme-adaptive chrome\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\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\nGRID = \"#D8D7D0\" if THEME == \"light\" else \"#303030\"\n\nBRAND = \"#009E73\"  # Imprint position 1 — cumulative line (first series)\nANYPLOT_AMBER = \"#DDCC77\"  # semantic anchor — caution, 90% threshold\nMATTE_RED = \"#AE3030\"  # Imprint position 5 — alert, 95% threshold\n\n# Data — PCA on Wine dataset (13 chemical features)\nwine = load_wine()\nX = StandardScaler().fit_transform(wine.data)\npca = PCA()\npca.fit(X)\n\nn_components = np.arange(1, len(pca.explained_variance_ratio_) + 1)\nindividual_var = pca.explained_variance_ratio_ * 100\ncumulative_var = np.cumsum(individual_var)\n\ndf_cumulative = pd.DataFrame(\n    {\"Component\": n_components, \"Cumulative\": np.round(cumulative_var, 2), \"Individual\": np.round(individual_var, 2)}\n)\ndf_individual = pd.DataFrame({\"Component\": n_components, \"Individual\": np.round(individual_var, 2)})\n\n# Thresholds and component crossings\nthreshold_90, threshold_95 = 90.0, 95.0\ncomp_90 = int(n_components[cumulative_var >= threshold_90][0])\ncomp_95 = int(n_components[cumulative_var >= threshold_95][0])\n\n# Elbow: component with max perpendicular distance from start→end chord\nc1, v1 = 1, cumulative_var[0]\ncn, vn = int(n_components[-1]), cumulative_var[-1]\ndx, dy = cn - c1, vn - v1\nperp_dist = abs(dy * n_components - dx * cumulative_var + cn * v1 - vn * c1) / np.sqrt(dx**2 + dy**2)\nelbow_comp = int(n_components[np.argmax(perp_dist)])\nelbow_cum = float(cumulative_var[elbow_comp - 1])\n\ndf_elbow = pd.DataFrame({\"Component\": [elbow_comp], \"Cumulative\": [elbow_cum], \"label\": [f\"PC {elbow_comp}\"]})\n\ntitle = \"line-pca-variance-cumulative · python · letsplot · anyplot.ai\"\n\nanyplot_chrome = theme(\n    plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n    panel_background=element_rect(fill=PAGE_BG),\n    panel_grid_major_x=element_blank(),\n    panel_grid_minor=element_blank(),\n    panel_grid_major_y=element_line(color=GRID, size=0.4),\n    axis_title=element_text(color=INK, size=12),\n    axis_text=element_text(color=INK_SOFT, size=10),\n    axis_line=element_line(color=INK_SOFT),\n    plot_title=element_text(color=INK, size=16),\n    legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n    legend_text=element_text(color=INK_SOFT, size=10),\n    legend_title=element_text(color=INK),\n)\n\nplot = (\n    ggplot()\n    # Secondary: individual variance bars (muted, behind the cumulative line)\n    + geom_bar(\n        data=df_individual,\n        mapping=aes(x=\"Component\", y=\"Individual\"),\n        stat=\"identity\",\n        fill=INK_MUTED,\n        alpha=0.28,\n        width=0.65,\n    )\n    # Threshold reference lines\n    + geom_hline(yintercept=threshold_90, linetype=\"dashed\", color=ANYPLOT_AMBER, size=1.0)\n    + geom_hline(yintercept=threshold_95, linetype=\"dashed\", color=MATTE_RED, size=1.0)\n    + geom_vline(xintercept=comp_90, linetype=\"dotted\", color=ANYPLOT_AMBER, size=0.8, alpha=0.65)\n    + geom_vline(xintercept=comp_95, linetype=\"dotted\", color=MATTE_RED, size=0.8, alpha=0.65)\n    # Threshold labels (right-aligned near edge)\n    + geom_text(\n        data=pd.DataFrame({\"x\": [12.8], \"y\": [threshold_90 + 2.0], \"label\": [\"90%\"]}),\n        mapping=aes(x=\"x\", y=\"y\", label=\"label\"),\n        color=ANYPLOT_AMBER,\n        size=4,\n        hjust=1,\n    )\n    + geom_text(\n        data=pd.DataFrame({\"x\": [12.8], \"y\": [threshold_95 + 2.0], \"label\": [\"95%\"]}),\n        mapping=aes(x=\"x\", y=\"y\", label=\"label\"),\n        color=MATTE_RED,\n        size=4,\n        hjust=1,\n    )\n    # Primary: cumulative variance line (Imprint brand green, first series)\n    + geom_line(data=df_cumulative, mapping=aes(x=\"Component\", y=\"Cumulative\"), size=2.0, color=BRAND)\n    # Points with lets-plot native tooltips (hover shows both cumulative and individual)\n    + geom_point(\n        data=df_cumulative,\n        mapping=aes(x=\"Component\", y=\"Cumulative\"),\n        size=5,\n        color=BRAND,\n        alpha=0.9,\n        tooltips=layer_tooltips()\n        .line(\"PC @Component\")\n        .line(\"Cumulative: @Cumulative%\")\n        .line(\"Individual: @Individual%\"),\n    )\n    # Elbow annotation: hollow ring marker + label (spec: annotate elbow if detectable)\n    + geom_point(\n        data=df_elbow, mapping=aes(x=\"Component\", y=\"Cumulative\"), size=9, shape=21, color=INK, fill=ELEVATED_BG\n    )\n    + geom_text(\n        data=df_elbow, mapping=aes(x=\"Component\", y=\"Cumulative\", label=\"label\"), color=INK_SOFT, size=4, hjust=-0.25\n    )\n    + scale_x_continuous(breaks=list(n_components))\n    + scale_y_continuous(breaks=list(range(0, 101, 10)), limits=[0, 106])\n    + labs(title=title, x=\"Number of Principal Components\", y=\"Explained Variance (%)\")\n    + theme_minimal()\n    + anyplot_chrome\n    + ggsize(800, 450)\n)\n\n# Save\nggsave(plot, f\"plot-{THEME}.png\", path=\".\", scale=4)\nggsave(plot, f\"plot-{THEME}.html\", path=\".\")\n"}