{"spec_id":"line-growth-percentile","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\nline-growth-percentile: Pediatric Growth Chart with Percentile Curves\nLibrary: plotnine 0.15.7 | Python 3.13.14\nQuality: 87/100 | Updated: 2026-06-20\n\"\"\"\n\nimport os\nimport sys\n\n\n# Remove this file's directory from sys.path to prevent self-import\n# (file is named plotnine.py — same name as the library).\n_this_dir = os.path.dirname(os.path.abspath(__file__))\nsys.path = [p for p in sys.path if os.path.abspath(p or \".\") != _this_dir]\n\nimport numpy as np\nimport pandas as pd\nfrom plotnine import (\n    aes,\n    annotate,\n    element_blank,\n    element_line,\n    element_rect,\n    element_text,\n    geom_line,\n    geom_point,\n    geom_ribbon,\n    geom_text,\n    ggplot,\n    labs,\n    scale_alpha_manual,\n    scale_fill_manual,\n    scale_x_continuous,\n    scale_y_continuous,\n    theme,\n    theme_minimal,\n)\n\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\n\n# Imprint palette — semantic exception: blue for boys (domain convention)\nBOYS_BLUE = \"#4467A3\"  # Imprint position 3\nPATIENT_COLOR = \"#009E73\"  # Imprint position 1 — always first categorical series\n\n# Data — WHO-style weight-for-age reference for boys, 0–36 months\nnp.random.seed(42)\n\nage_months = np.arange(0, 37, 1)\n\n# Approximate WHO growth standards for boys 0–36 months\nmedian_weight = 3.3 + 0.7 * age_months - 0.008 * age_months**2 + 0.00005 * age_months**3\nsd_weight = 0.4 + 0.02 * age_months\n\npercentiles = {\n    \"P3\": median_weight - 1.881 * sd_weight,\n    \"P10\": median_weight - 1.282 * sd_weight,\n    \"P25\": median_weight - 0.674 * sd_weight,\n    \"P50\": median_weight,\n    \"P75\": median_weight + 0.674 * sd_weight,\n    \"P90\": median_weight + 1.282 * sd_weight,\n    \"P97\": median_weight + 1.881 * sd_weight,\n}\n\ndf_ref = pd.DataFrame({\"age\": age_months, **{k.lower(): v for k, v in percentiles.items()}})\n\n# Band data — graduated alpha: darker at extremes, lighter near median\nband_specs = [\n    (\"P3–P10\", \"p3\", \"p10\", 0.40),\n    (\"P10–P25\", \"p10\", \"p25\", 0.28),\n    (\"P25–P75\", \"p25\", \"p75\", 0.14),\n    (\"P75–P90\", \"p75\", \"p90\", 0.28),\n    (\"P90–P97\", \"p90\", \"p97\", 0.40),\n]\n\ndf_bands = pd.concat(\n    [\n        pd.DataFrame({\"age\": df_ref[\"age\"], \"ymin\": df_ref[lo], \"ymax\": df_ref[hi], \"band\": label})\n        for label, lo, hi, _ in band_specs\n    ],\n    ignore_index=True,\n)\nband_order = [s[0] for s in band_specs]\ndf_bands[\"band\"] = pd.Categorical(df_bands[\"band\"], categories=band_order, ordered=True)\n\nband_fill_map = {s[0]: BOYS_BLUE for s in band_specs}\nband_alpha_map = {s[0]: s[3] for s in band_specs}\n\n# Boundary percentile lines (long format, grouped by percentile)\npct_non_median = [\"P3\", \"P10\", \"P25\", \"P75\", \"P90\", \"P97\"]\ndf_boundary = pd.concat(\n    [pd.DataFrame({\"age\": df_ref[\"age\"], \"weight\": df_ref[p.lower()], \"percentile\": p}) for p in pct_non_median],\n    ignore_index=True,\n)\n\n# Median line — emphasized separately\ndf_median = pd.DataFrame({\"age\": df_ref[\"age\"], \"weight\": df_ref[\"p50\"]})\n\n# Individual patient — a healthy boy tracking around the 65th percentile\npatient_ages = np.array([0, 1, 2, 4, 6, 9, 12, 15, 18, 24, 30, 36])\npatient_weights = np.array([3.5, 4.6, 5.8, 7.2, 8.3, 9.5, 10.4, 11.2, 11.9, 13.1, 14.5, 15.6])\ndf_patient = pd.DataFrame({\"age\": patient_ages, \"weight\": patient_weights})\n\n# Percentile labels at right margin (age=36)\npct_all = [\"P3\", \"P10\", \"P25\", \"P50\", \"P75\", \"P90\", \"P97\"]\ndf_labels = pd.DataFrame({\"age\": [36] * 7, \"weight\": [percentiles[p][-1] for p in pct_all], \"label\": pct_all})\n\n# Plot\ntitle = \"line-growth-percentile · python · plotnine · anyplot.ai\"\nplot = (\n    ggplot()\n    + geom_ribbon(df_bands, aes(x=\"age\", ymin=\"ymin\", ymax=\"ymax\", fill=\"band\", alpha=\"band\"))\n    + scale_fill_manual(values=band_fill_map)\n    + scale_alpha_manual(values=band_alpha_map)\n    + geom_line(df_boundary, aes(x=\"age\", y=\"weight\", group=\"percentile\"), color=BOYS_BLUE, size=0.5, alpha=0.5)\n    + geom_line(df_median, aes(x=\"age\", y=\"weight\"), color=BOYS_BLUE, size=2.5)\n    + geom_text(df_labels, aes(x=\"age\", y=\"weight\", label=\"label\"), ha=\"left\", size=3.8, color=INK_SOFT, nudge_x=0.5)\n    + geom_line(df_patient, aes(x=\"age\", y=\"weight\"), color=PATIENT_COLOR, size=1.5)\n    + geom_point(df_patient, aes(x=\"age\", y=\"weight\"), color=PATIENT_COLOR, fill=PAGE_BG, size=4, stroke=1.0)\n    + annotate(\n        \"text\",\n        x=patient_ages[-1] + 0.8,\n        y=patient_weights[-1],\n        label=\"Patient\",\n        color=PATIENT_COLOR,\n        size=3.5,\n        ha=\"left\",\n    )\n    + labs(x=\"Age (months)\", y=\"Weight (kg)\", title=title)\n    + scale_x_continuous(breaks=range(0, 37, 3), limits=(0, 39))\n    + scale_y_continuous(breaks=range(2, 20, 2))\n    + theme_minimal()\n    + theme(\n        figure_size=(8, 4.5),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG),\n        panel_border=element_blank(),\n        panel_grid_major_x=element_blank(),\n        panel_grid_major_y=element_line(color=INK, size=0.3, alpha=0.15),\n        panel_grid_minor=element_blank(),\n        plot_title=element_text(size=12, color=INK),\n        axis_title=element_text(size=10, color=INK),\n        axis_text=element_text(size=8, color=INK_SOFT),\n        legend_position=\"none\",\n    )\n)\n\n# Save\nplot.save(f\"plot-{THEME}.png\", dpi=400, width=8, height=4.5, units=\"in\", verbose=False)\n"}