{"spec_id":"curve-bias-variance-tradeoff","library":"altair","language":"python","code":"\"\"\" anyplot.ai\ncurve-bias-variance-tradeoff: Bias-Variance Tradeoff Curve\nLibrary: altair 6.1.0 | Python 3.13.13\nQuality: 90/100 | Created: 2026-05-28\n\"\"\"\n\nimport os\nimport sys\n\n\n# Prevent self-import: this file is named altair.py, so remove its directory\n# from sys.path before importing the altair package.\n_this_dir = os.path.dirname(os.path.abspath(__file__))\nsys.path = [p for p in sys.path if p and os.path.abspath(p) != _this_dir]\n\nimport altair as alt\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\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\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\nIMPRINT_PALETTE = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\"]\n\n# Data — theoretical bias-variance tradeoff curves\nnp.random.seed(42)\nn = 80\ncomplexity = np.linspace(0.5, 10.0, n)\n\nbias_sq = 1.1 / (0.4 + complexity * 0.28)\nvariance = 0.032 * complexity**1.9\nirr_error = np.full(n, 0.12)\ntotal_error = bias_sq + variance + irr_error\n\nopt_idx = int(np.argmin(total_error))\nopt_complexity = float(complexity[opt_idx])\ny_max = float(total_error.max()) * 1.12\n\n# Long-form data for lines\ncurve_names = [\"Bias²\", \"Variance\", \"Total Error\", \"Irreducible Error\"]\ncurve_vals = [bias_sq, variance, total_error, irr_error]\nrows = []\nfor name, vals in zip(curve_names, curve_vals, strict=True):\n    for c, v in zip(complexity, vals, strict=True):\n        rows.append({\"complexity\": float(c), \"error\": float(v), \"curve\": name})\ndf = pd.DataFrame(rows)\n\ncolor_domain = curve_names\ncolor_range = IMPRINT_PALETTE[:4]\ndash_domain = curve_names\ndash_range = [[1, 0], [1, 0], [6, 3], [4, 4]]\n\n# Shaded regions — two separate dataframes, each with a fixed color, to avoid\n# merging their color channel with the lines layer's color encoding.\nshade_under_df = pd.DataFrame([{\"complexity\": 0.5, \"complexity_end\": opt_complexity, \"error\": 0.0, \"error_end\": y_max}])\nshade_over_df = pd.DataFrame([{\"complexity\": opt_complexity, \"complexity_end\": 10.0, \"error\": 0.0, \"error_end\": y_max}])\n\n# Direct curve labels\nlabel_df = pd.DataFrame(\n    [\n        {\"complexity\": 1.2, \"error\": 1.72, \"label\": \"Bias²\", \"curve\": \"Bias²\"},\n        {\"complexity\": 8.5, \"error\": 2.06, \"label\": \"Variance\", \"curve\": \"Variance\"},\n        {\"complexity\": 5.0, \"error\": 1.62, \"label\": \"Total Error\", \"curve\": \"Total Error\"},\n        {\"complexity\": 4.0, \"error\": 0.16, \"label\": \"Irreducible Error\", \"curve\": \"Irreducible Error\"},\n    ]\n)\n\n# Optimal complexity rule\nopt_df = pd.DataFrame({\"complexity\": [opt_complexity]})\n\n# Optimal label\nopt_label_df = pd.DataFrame(\n    [{\"complexity\": opt_complexity + 0.22, \"error\": float(total_error[opt_idx]) * 0.60, \"text\": \"Optimal\"}]\n)\n\n# Zone labels\nzone_df = pd.DataFrame(\n    [\n        {\"complexity\": (0.5 + opt_complexity) / 2, \"error\": y_max * 0.89, \"text\": \"← Underfitting\"},\n        {\"complexity\": (opt_complexity + 10.0) / 2, \"error\": y_max * 0.89, \"text\": \"Overfitting →\"},\n    ]\n)\n\n# Formula annotation\nformula_df = pd.DataFrame(\n    [{\"complexity\": 5.25, \"error\": y_max * 0.78, \"text\": \"Total Error = Bias² + Variance + Irreducible Error\"}]\n)\n\ntitle = \"curve-bias-variance-tradeoff · python · altair · anyplot.ai\"\nx_scale = alt.Scale(domain=[0.5, 10.0])\ny_scale = alt.Scale(domain=[0.0, y_max])\n\n# Layer 1: Shaded zones — fixed fill colors to avoid merging with lines scale\nshade_under_layer = (\n    alt.Chart(shade_under_df)\n    .mark_rect(fill=IMPRINT_PALETTE[0], opacity=0.07, stroke=None)\n    .encode(\n        x=alt.X(\"complexity:Q\", scale=x_scale),\n        x2=\"complexity_end:Q\",\n        y=alt.Y(\"error:Q\", scale=y_scale),\n        y2=\"error_end:Q\",\n    )\n)\nshade_over_layer = (\n    alt.Chart(shade_over_df)\n    .mark_rect(fill=IMPRINT_PALETTE[4], opacity=0.07, stroke=None)\n    .encode(\n        x=alt.X(\"complexity:Q\", scale=x_scale),\n        x2=\"complexity_end:Q\",\n        y=alt.Y(\"error:Q\", scale=y_scale),\n        y2=\"error_end:Q\",\n    )\n)\n\n# Layer 2: Curves\nlines_layer = (\n    alt.Chart(df)\n    .mark_line(strokeWidth=2.8)\n    .encode(\n        x=alt.X(\"complexity:Q\", title=\"Model Complexity\", scale=x_scale),\n        y=alt.Y(\"error:Q\", title=\"Prediction Error\", scale=y_scale),\n        color=alt.Color(\"curve:N\", scale=alt.Scale(domain=color_domain, range=color_range), legend=None),\n        strokeDash=alt.StrokeDash(\"curve:N\", scale=alt.Scale(domain=dash_domain, range=dash_range), legend=None),\n    )\n)\n\n# Layer 3: Optimal complexity vertical rule\nopt_rule_layer = (\n    alt.Chart(opt_df).mark_rule(strokeDash=[5, 4], strokeWidth=1.5, color=INK_SOFT).encode(x=\"complexity:Q\")\n)\n\n# Layer 4: Curve labels\nlabel_layer = (\n    alt.Chart(label_df)\n    .mark_text(fontSize=11, align=\"left\", fontWeight=\"normal\")\n    .encode(\n        x=\"complexity:Q\",\n        y=\"error:Q\",\n        text=\"label:N\",\n        color=alt.Color(\"curve:N\", scale=alt.Scale(domain=color_domain, range=color_range), legend=None),\n    )\n)\n\n# Layer 5: Zone labels\nzone_layer = (\n    alt.Chart(zone_df)\n    .mark_text(fontSize=10, color=INK_MUTED, fontStyle=\"italic\")\n    .encode(x=\"complexity:Q\", y=\"error:Q\", text=\"text:N\")\n)\n\n# Layer 6: Optimal label\nopt_label_layer = (\n    alt.Chart(opt_label_df)\n    .mark_text(fontSize=10, align=\"left\", color=INK_SOFT)\n    .encode(x=\"complexity:Q\", y=\"error:Q\", text=\"text:N\")\n)\n\n# Layer 7: Formula\nformula_layer = (\n    alt.Chart(formula_df).mark_text(fontSize=11, color=INK_SOFT).encode(x=\"complexity:Q\", y=\"error:Q\", text=\"text:N\")\n)\n\nchart = (\n    alt.layer(\n        shade_under_layer,\n        shade_over_layer,\n        lines_layer,\n        opt_rule_layer,\n        label_layer,\n        zone_layer,\n        opt_label_layer,\n        formula_layer,\n    )\n    .properties(width=620, height=320, background=PAGE_BG, title=alt.TitleParams(text=title, fontSize=16, color=INK))\n    .configure_view(fill=PAGE_BG, stroke=None)\n    .configure_axis(\n        domainColor=INK_SOFT,\n        tickColor=INK_SOFT,\n        gridColor=INK,\n        gridOpacity=0.12,\n        labelColor=INK_SOFT,\n        labelFontSize=10,\n        titleColor=INK,\n        titleFontSize=12,\n    )\n    .configure_axisX(grid=False)\n    .configure_legend(\n        fillColor=ELEVATED_BG,\n        strokeColor=INK_SOFT,\n        labelColor=INK_SOFT,\n        titleColor=INK,\n        labelFontSize=10,\n        titleFontSize=10,\n    )\n)\n\n# Save PNG\nchart.save(f\"plot-{THEME}.png\", scale_factor=4.0)\n\n# Pad to exact target dimensions (3200 × 1800)\nTW, TH = 3200, 1800\n_img = Image.open(f\"plot-{THEME}.png\").convert(\"RGB\")\n_w, _h = _img.size\nif _w > TW or _h > TH:\n    raise SystemExit(\n        f\"altair vl-convert produced {_w}×{_h}, exceeds target {TW}×{TH}. \"\n        \"Shrink chart .properties(width=, height=) values and re-render.\"\n    )\nif _w < TW or _h < TH:\n    _canvas = Image.new(\"RGB\", (TW, TH), PAGE_BG)\n    _canvas.paste(_img, ((TW - _w) // 2, (TH - _h) // 2))\n    _canvas.save(f\"plot-{THEME}.png\")\n\n# Save HTML\nchart.save(f\"plot-{THEME}.html\")\n"}