{"spec_id":"histogram-epidemic","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\nhistogram-epidemic: Epidemic Curve (Epi Curve)\nLibrary: plotnine 0.15.4 | Python 3.13.13\nQuality: 91/100 | Updated: 2026-06-02\n\"\"\"\n\nimport os\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_col,\n    geom_line,\n    geom_text,\n    geom_vline,\n    ggplot,\n    labs,\n    scale_fill_manual,\n    scale_x_date,\n    scale_y_continuous,\n    theme,\n    theme_minimal,\n)\n\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\"\n\n# Imprint categorical palette — positions 1-3 for stacked case types; position 4 for cumulative line\nIMPRINT_PALETTE = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\"]\n\n# Data — two-wave epidemic model (point-source → propagated transmission)\nnp.random.seed(42)\n\nstart_date = pd.Timestamp(\"2024-01-15\")\ndates = pd.date_range(start_date, periods=90, freq=\"D\")\ndays = np.arange(90)\n\nconfirmed_rate = (\n    45 * np.exp(-0.5 * ((days - 18) / 5) ** 2) + 25 * np.exp(-0.5 * ((days - 55) / 10) ** 2) + np.random.poisson(2, 90)\n)\nprobable_rate = (\n    15 * np.exp(-0.5 * ((days - 20) / 6) ** 2) + 10 * np.exp(-0.5 * ((days - 57) / 11) ** 2) + np.random.poisson(1, 90)\n)\nsuspect_rate = (\n    8 * np.exp(-0.5 * ((days - 22) / 7) ** 2) + 5 * np.exp(-0.5 * ((days - 60) / 12) ** 2) + np.random.poisson(1, 90)\n)\n\nconfirmed = np.maximum(confirmed_rate.astype(int), 0)\nprobable = np.maximum(probable_rate.astype(int), 0)\nsuspect = np.maximum(suspect_rate.astype(int), 0)\n\ndf = pd.DataFrame(\n    {\n        \"onset_date\": np.tile(dates, 3),\n        \"case_count\": np.concatenate([confirmed, probable, suspect]),\n        \"case_type\": [\"Confirmed\"] * 90 + [\"Probable\"] * 90 + [\"Suspect\"] * 90,\n    }\n)\ndf[\"case_type\"] = pd.Categorical(df[\"case_type\"], categories=[\"Suspect\", \"Probable\", \"Confirmed\"], ordered=True)\n\ndaily_totals = df.groupby(\"onset_date\")[\"case_count\"].sum().reset_index()\ndaily_totals[\"cumulative\"] = daily_totals[\"case_count\"].cumsum()\nmax_daily = daily_totals[\"case_count\"].max()\nmax_cumulative = int(daily_totals[\"cumulative\"].max())\ndaily_totals[\"cumulative_scaled\"] = daily_totals[\"cumulative\"] / max_cumulative * max_daily\n\nlockdown_date = pd.Timestamp(\"2024-02-10\")\nvaccination_date = pd.Timestamp(\"2024-03-01\")\ninterventions = pd.DataFrame(\n    {\"date\": [lockdown_date, vaccination_date], \"label\": [\"Lockdown\", \"Vaccination\\ncampaign\"]}\n)\n\nwave1_idx = daily_totals.loc[daily_totals[\"onset_date\"] < \"2024-03-01\", \"case_count\"].idxmax()\nwave2_idx = daily_totals.loc[daily_totals[\"onset_date\"] >= \"2024-03-01\", \"case_count\"].idxmax()\nwave1_date = daily_totals.loc[wave1_idx, \"onset_date\"]\nwave1_peak = daily_totals.loc[wave1_idx, \"case_count\"]\nwave2_date = daily_totals.loc[wave2_idx, \"onset_date\"]\nwave2_peak = daily_totals.loc[wave2_idx, \"case_count\"]\n\ncumul_label = f\"Cumulative: {max_cumulative:,} cases →\"\n\n# Plot\ntitle = \"histogram-epidemic · python · plotnine · anyplot.ai\"\n\nplot = (\n    ggplot(df, aes(x=\"onset_date\", y=\"case_count\"))\n    + geom_col(aes(fill=\"case_type\"), width=1.0)\n    + geom_line(\n        data=daily_totals,\n        mapping=aes(x=\"onset_date\", y=\"cumulative_scaled\"),\n        color=IMPRINT_PALETTE[3],\n        size=1.5,\n        alpha=0.9,\n    )\n    + geom_vline(\n        data=interventions, mapping=aes(xintercept=\"date\"), linetype=\"dashed\", color=INK_SOFT, size=0.5, alpha=0.8\n    )\n    + geom_text(\n        data=interventions,\n        mapping=aes(x=\"date\", label=\"label\"),\n        y=max_daily * 0.88,\n        ha=\"left\",\n        nudge_x=1.5,\n        size=4,\n        color=INK_MUTED,\n        fontstyle=\"italic\",\n    )\n    + annotate(\n        \"text\",\n        x=dates[-1],\n        y=daily_totals[\"cumulative_scaled\"].iloc[-1] * 1.03,\n        label=cumul_label,\n        ha=\"right\",\n        va=\"bottom\",\n        size=3.5,\n        color=IMPRINT_PALETTE[3],\n        fontstyle=\"italic\",\n        fontweight=\"bold\",\n    )\n    + annotate(\n        \"text\",\n        x=wave1_date,\n        y=wave1_peak + 3,\n        label=f\"Wave 1 peak\\n{wave1_peak} cases/day\",\n        ha=\"center\",\n        va=\"bottom\",\n        size=3.5,\n        color=INK,\n        fontweight=\"bold\",\n    )\n    + annotate(\n        \"text\",\n        x=wave2_date,\n        y=wave2_peak + 3,\n        label=f\"Wave 2 peak\\n{wave2_peak} cases/day\",\n        ha=\"center\",\n        va=\"bottom\",\n        size=3.5,\n        color=INK,\n        fontweight=\"bold\",\n    )\n    + scale_fill_manual(\n        values={\"Confirmed\": IMPRINT_PALETTE[0], \"Probable\": IMPRINT_PALETTE[1], \"Suspect\": IMPRINT_PALETTE[2]}\n    )\n    + scale_x_date(date_breaks=\"2 weeks\", date_labels=\"%b %d\")\n    + scale_y_continuous(expand=(0, 0, 0.12, 0))\n    + labs(x=\"Date of Symptom Onset\", y=\"Number of New Cases (per day)\", fill=\"Case Classification\", title=title)\n    + theme_minimal()\n    + theme(\n        figure_size=(8, 4.5),\n        text=element_text(size=7, color=INK),\n        axis_title=element_text(size=10, color=INK),\n        axis_text=element_text(size=8, color=INK_SOFT),\n        axis_text_x=element_text(rotation=45, ha=\"right\", color=INK_SOFT),\n        plot_title=element_text(size=13, color=INK, fontweight=\"bold\"),\n        legend_text=element_text(size=8, color=INK_SOFT),\n        legend_title=element_text(size=8, color=INK),\n        legend_position=\"top\",\n        legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n        panel_border=element_blank(),\n        panel_grid_major_x=element_blank(),\n        panel_grid_minor=element_blank(),\n        panel_grid_major_y=element_line(color=INK, size=0.3, alpha=0.15),\n        axis_line_x=element_line(color=INK_SOFT, size=0.5),\n        axis_line_y=element_line(color=INK_SOFT, size=0.5),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG),\n    )\n)\n\n# Save\nplot.save(f\"plot-{THEME}.png\", dpi=400, width=8, height=4.5, units=\"in\", verbose=False)\n"}