{"spec_id":"forest-basic","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\nforest-basic: Meta-Analysis Forest Plot\nLibrary: plotnine 0.15.4 | Python 3.13.13\nQuality: 92/100 | Updated: 2026-05-11\n\"\"\"\n\nimport os\nimport sys\n\n\nsys.path = [p for p in sys.path if p != \"\" and \"/forest-basic\" not in p]\n\nimport pandas as pd\nfrom plotnine import (\n    aes,\n    element_blank,\n    element_line,\n    element_rect,\n    element_text,\n    geom_errorbarh,\n    geom_point,\n    geom_polygon,\n    geom_text,\n    geom_vline,\n    ggplot,\n    labs,\n    scale_size_identity,\n    scale_x_continuous,\n    scale_y_continuous,\n    theme,\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\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\nBRAND = \"#009E73\"\n\n# Data: Meta-analysis of RCTs comparing treatment vs control\nstudies = pd.DataFrame(\n    {\n        \"study\": [\n            \"Smith 2018\",\n            \"Johnson 2019\",\n            \"Williams 2019\",\n            \"Brown 2020\",\n            \"Davis 2020\",\n            \"Miller 2021\",\n            \"Wilson 2021\",\n            \"Moore 2022\",\n            \"Taylor 2022\",\n            \"Anderson 2023\",\n        ],\n        \"effect_size\": [-0.45, -0.22, -0.38, -0.15, -0.52, -0.31, -0.08, -0.41, -0.25, -0.35],\n        \"ci_lower\": [-0.72, -0.48, -0.61, -0.42, -0.81, -0.55, -0.35, -0.68, -0.51, -0.58],\n        \"ci_upper\": [-0.18, 0.04, -0.15, 0.12, -0.23, -0.07, 0.19, -0.14, 0.01, -0.12],\n        \"weight\": [9.8, 11.2, 10.5, 8.7, 7.3, 10.9, 9.1, 8.4, 11.8, 12.3],\n    }\n)\n\n# Calculate pooled estimate (weighted mean)\npooled_effect = (studies[\"effect_size\"] * studies[\"weight\"]).sum() / studies[\"weight\"].sum()\npooled_se = 0.08\npooled_lower = pooled_effect - 1.96 * pooled_se\npooled_upper = pooled_effect + 1.96 * pooled_se\n\n# Create y positions (studies listed top to bottom, pooled at bottom)\nstudies[\"y_pos\"] = range(len(studies), 0, -1)\n\n# Scale marker sizes for visibility (based on weight, scaled for canvas)\nstudies[\"marker_size\"] = studies[\"weight\"] * 0.5\n\n# Create diamond for pooled estimate\ndiamond_y = 0\ndiamond = pd.DataFrame(\n    {\n        \"x\": [pooled_lower, pooled_effect, pooled_upper, pooled_effect],\n        \"y\": [diamond_y, diamond_y + 0.3, diamond_y, diamond_y - 0.3],\n    }\n)\n\n# Create label data for study names and effect sizes\nstudies[\"label\"] = (\n    studies[\"effect_size\"].round(2).astype(str)\n    + \" [\"\n    + studies[\"ci_lower\"].round(2).astype(str)\n    + \", \"\n    + studies[\"ci_upper\"].round(2).astype(str)\n    + \"]\"\n)\n\n# Fixed positions for text columns\nx_left = -1.4\nx_right = 0.55\n\n# Add fixed positions to dataframe\nstudies[\"x_left\"] = x_left\nstudies[\"x_right\"] = x_right\n\n# Pooled estimate label data\npooled_label_left = pd.DataFrame({\"x\": [x_left], \"y\": [diamond_y], \"label\": [\"Pooled\"]})\npooled_label_right = pd.DataFrame(\n    {\"x\": [x_right], \"y\": [diamond_y], \"label\": [f\"{pooled_effect:.2f} [{pooled_lower:.2f}, {pooled_upper:.2f}]\"]}\n)\n\n# Plot\nplot = (\n    ggplot()\n    + geom_vline(xintercept=0, linetype=\"dashed\", color=INK_SOFT, size=1, alpha=0.6)\n    + geom_errorbarh(aes(y=\"y_pos\", xmin=\"ci_lower\", xmax=\"ci_upper\"), data=studies, height=0.25, size=1.2, color=BRAND)\n    + geom_point(aes(x=\"effect_size\", y=\"y_pos\", size=\"marker_size\"), data=studies, color=BRAND, fill=BRAND)\n    + scale_size_identity()\n    + geom_polygon(aes(x=\"x\", y=\"y\"), data=diamond, fill=BRAND, color=BRAND, size=1.2, alpha=0.7)\n    + geom_text(aes(x=\"x_left\", y=\"y_pos\", label=\"study\"), data=studies, ha=\"left\", size=12, color=INK)\n    + geom_text(aes(x=\"x_right\", y=\"y_pos\", label=\"label\"), data=studies, ha=\"left\", size=10, color=INK_SOFT)\n    + geom_text(\n        aes(x=\"x\", y=\"y\", label=\"label\"), data=pooled_label_left, ha=\"left\", size=12, fontweight=\"bold\", color=INK\n    )\n    + geom_text(\n        aes(x=\"x\", y=\"y\", label=\"label\"), data=pooled_label_right, ha=\"left\", size=10, fontweight=\"bold\", color=INK_SOFT\n    )\n    + labs(x=\"Mean Difference (Treatment - Control)\", y=\"\", title=\"forest-basic · plotnine · anyplot.ai\")\n    + scale_x_continuous(breaks=[-0.8, -0.6, -0.4, -0.2, 0, 0.2, 0.4], limits=(-1.5, 1.3))\n    + scale_y_continuous(breaks=[], limits=(-1, 11.5))\n    + theme(\n        figure_size=(16, 9),\n        panel_background=element_rect(fill=PAGE_BG, color=None),\n        plot_background=element_rect(fill=PAGE_BG, color=None),\n        panel_grid_major_x=element_line(color=INK, size=0.3, alpha=0.10),\n        panel_grid_major_y=element_blank(),\n        panel_grid_minor=element_blank(),\n        axis_text_x=element_text(size=16, color=INK_SOFT),\n        axis_text_y=element_blank(),\n        axis_title_x=element_text(size=20, color=INK),\n        axis_title_y=element_blank(),\n        plot_title=element_text(size=24, ha=\"center\", color=INK),\n        axis_ticks_major_y=element_blank(),\n        legend_position=\"none\",\n        panel_border=element_rect(color=INK_SOFT, fill=None),\n    )\n)\n\n# Save\nplot.save(f\"plot-{THEME}.png\", dpi=300, verbose=False)\n"}