{"spec_id":"line-pca-variance-cumulative","library":"seaborn","language":"python","code":"\"\"\" anyplot.ai\nline-pca-variance-cumulative: Cumulative Explained Variance for PCA Component Selection\nLibrary: seaborn 0.13.2 | Python 3.13.13\nQuality: 90/100 | Updated: 2026-05-29\n\"\"\"\n\nimport os\nimport sys as _sys\n\n\n# Remove the script's own directory from sys.path to prevent sibling files\n# (e.g. matplotlib.py, seaborn.py) from shadowing installed packages.\n_script_dir = os.path.dirname(os.path.abspath(__file__)) if \"__file__\" in vars() else os.getcwd()\n_sys.path = [p for p in _sys.path if os.path.realpath(p or \".\") != os.path.realpath(_script_dir)]\ndel _sys, _script_dir\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\nfrom matplotlib.lines import Line2D\nfrom matplotlib.patches import Patch\nfrom sklearn.decomposition import PCA\nfrom sklearn.preprocessing import StandardScaler\n\n\n# Theme tokens — Imprint palette\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\"\n\nIMPRINT_PALETTE = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\"]\nANYPLOT_AMBER = \"#DDCC77\"\n\n# Data — synthetic macroeconomic indicators (240 months × 25 indicators, 4 latent factors)\n# Represents macro-factor decomposition: growth, inflation, financial stress, trade\nnp.random.seed(42)\nn_periods, n_indicators = 240, 25\nfactor_names = [\"Growth\", \"Inflation\", \"Financial Stress\", \"Trade\"]\nfactors = np.random.randn(n_periods, 4)\nloadings = np.random.randn(4, n_indicators) * 1.8\nX = factors @ loadings + np.random.randn(n_periods, n_indicators) * 0.35\nX = StandardScaler().fit_transform(X)\n\npca = PCA()\npca.fit(X)\n\nn_components = np.arange(1, len(pca.explained_variance_ratio_) + 1)\nindividual_variance = pca.explained_variance_ratio_ * 100\ncumulative_variance = np.cumsum(individual_variance)\n\n# Find where cumulative variance crosses thresholds\nidx_90 = int(np.argmax(cumulative_variance >= 90))\nidx_95 = int(np.argmax(cumulative_variance >= 95))\ncomp_95 = n_components[idx_95]\nval_95 = cumulative_variance[idx_95]\n\n# Tidy dataframe — seaborn categorical x-axis\ndf = pd.DataFrame(\n    {\n        \"Component\": np.tile(n_components, 2),\n        \"Variance (%)\": np.concatenate([individual_variance, cumulative_variance]),\n        \"Measure\": ([\"Individual Variance\"] * len(n_components) + [\"Cumulative Variance\"] * len(n_components)),\n    }\n)\n\n# Seaborn theme with Imprint chrome\nsns.set_theme(\n    style=\"ticks\",\n    rc={\n        \"figure.facecolor\": PAGE_BG,\n        \"axes.facecolor\": PAGE_BG,\n        \"axes.edgecolor\": INK_SOFT,\n        \"axes.labelcolor\": INK,\n        \"text.color\": INK,\n        \"xtick.color\": INK_SOFT,\n        \"ytick.color\": INK_SOFT,\n        \"grid.color\": INK,\n        \"grid.alpha\": 0.15,\n        \"grid.linewidth\": 0.6,\n        \"legend.facecolor\": ELEVATED_BG,\n        \"legend.edgecolor\": INK_SOFT,\n        \"axes.spines.top\": False,\n        \"axes.spines.right\": False,\n        \"axes.grid.axis\": \"y\",\n        \"font.family\": \"sans-serif\",\n    },\n)\n\nfig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)\nax.set_facecolor(PAGE_BG)\n\n# Individual variance bars — Imprint position 2 (lavender) as secondary series\ndf_bar = df[df[\"Measure\"] == \"Individual Variance\"]\nsns.barplot(\n    data=df_bar, x=\"Component\", y=\"Variance (%)\", color=IMPRINT_PALETTE[1], alpha=0.35, width=0.65, legend=False, ax=ax\n)\n\n# Cumulative variance line — Imprint position 1 (#009E73) as first/primary series\ndf_line = df[df[\"Measure\"] == \"Cumulative Variance\"]\nsns.lineplot(\n    data=df_line,\n    x=\"Component\",\n    y=\"Variance (%)\",\n    color=IMPRINT_PALETTE[0],\n    linewidth=2.5,\n    marker=\"o\",\n    markersize=5,\n    markeredgewidth=1.5,\n    ax=ax,\n)\n# Hollow markers: PAGE_BG fill, Imprint green edge\nax.lines[0].set_markerfacecolor(PAGE_BG)\nax.lines[0].set_markeredgecolor(IMPRINT_PALETTE[0])\n\n# Threshold reference lines\nax.axhline(y=95, color=ANYPLOT_AMBER, linestyle=\"--\", linewidth=1.5, alpha=0.85, dashes=(6, 3))\nax.axhline(y=90, color=INK_SOFT, linestyle=\"--\", linewidth=1.2, alpha=0.55, dashes=(3, 2, 6, 2))\n\n# Threshold labels (left margin, outside plot data)\nax.text(-0.6, 95.8, \"95%\", fontsize=8, color=ANYPLOT_AMBER, fontweight=\"bold\", va=\"bottom\", ha=\"center\")\nax.text(-0.6, 89.2, \"90%\", fontsize=8, color=INK_SOFT, fontweight=\"bold\", va=\"top\", ha=\"center\")\n\n# Highlight 95% crossing — amber ring marker\nax.plot(\n    idx_95,\n    val_95,\n    \"o\",\n    markersize=13,\n    markerfacecolor=ANYPLOT_AMBER,\n    markeredgecolor=PAGE_BG,\n    markeredgewidth=1.5,\n    zorder=5,\n)\n\n# Annotation with curved arrow\nannotation_x = idx_95 + 3 if idx_95 < len(n_components) // 2 else idx_95 - 5\nax.annotate(\n    f\"{comp_95} components\\nexplain {val_95:.1f}%\",\n    xy=(idx_95, val_95),\n    xytext=(annotation_x, 52),\n    fontsize=8,\n    fontweight=\"bold\",\n    color=ANYPLOT_AMBER,\n    arrowprops={\"arrowstyle\": \"-|>\", \"color\": ANYPLOT_AMBER, \"lw\": 1.5, \"connectionstyle\": \"arc3,rad=-0.25\"},\n    bbox={\"boxstyle\": \"round,pad=0.3\", \"facecolor\": ELEVATED_BG, \"edgecolor\": ANYPLOT_AMBER, \"alpha\": 0.92},\n)\n\n# Subtle shading: below-90% region\nax.axhspan(0, 90, color=INK_MUTED, alpha=0.04)\n\n# Manual legend\nlegend_elements = [\n    Line2D(\n        [0],\n        [0],\n        color=IMPRINT_PALETTE[0],\n        linewidth=2.5,\n        marker=\"o\",\n        markersize=5,\n        markerfacecolor=PAGE_BG,\n        markeredgecolor=IMPRINT_PALETTE[0],\n        label=\"Cumulative variance\",\n    ),\n    Patch(facecolor=IMPRINT_PALETTE[1], alpha=0.35, label=\"Per-component variance\"),\n]\nax.legend(handles=legend_elements, fontsize=8, loc=\"lower right\", framealpha=0.92, edgecolor=INK_SOFT, fancybox=False)\nsns.despine(ax=ax, top=True, right=True)\n\n# Axis styling\nax.set_xlabel(\"Number of Principal Components\", fontsize=10, color=INK)\nax.set_ylabel(\"Explained Variance (%)\", fontsize=10, color=INK)\n\ntitle = \"line-pca-variance-cumulative · python · seaborn · anyplot.ai\"\nn = len(title)\ntitle_fs = round(12 * 67 / n) if n > 67 else 12\nax.set_title(title, fontsize=title_fs, fontweight=\"medium\", color=INK, pad=10)\n\nax.tick_params(axis=\"both\", labelsize=8, colors=INK_SOFT)\nax.set_ylim(0, 107)\n\n# Sparse x-tick labels so the axis stays readable for 25 components\nn_total = len(n_components)\nshow_positions = [i for i in range(n_total) if (i + 1) % 5 == 0 or i == 0]\nax.set_xticks(show_positions)\nax.set_xticklabels([str(n_components[i]) for i in show_positions])\n\nfor spine in (\"left\", \"bottom\"):\n    ax.spines[spine].set_color(INK_SOFT)\n\nfig.subplots_adjust(top=0.91, bottom=0.13, left=0.09, right=0.97)\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\n"}