{"spec_id":"funnel-meta-analysis","library":"ggplot2","language":"r","code":"#' anyplot.ai\n#' funnel-meta-analysis: Meta-Analysis Funnel Plot for Publication Bias\n#' Library: ggplot2 3.5.1 | R 4.4.1\n#' Quality: 86/100 | Created: 2026-06-10\n\nlibrary(ggplot2)\nlibrary(ragg)\n\nset.seed(42)\n\n# --- Theme tokens ---\nTHEME       <- Sys.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG     <- if (THEME == \"light\") \"#FAF8F1\" else \"#1A1A17\"\nELEVATED_BG <- if (THEME == \"light\") \"#FFFDF6\" else \"#242420\"\nINK         <- if (THEME == \"light\") \"#1A1A17\" else \"#F0EFE8\"\nINK_SOFT    <- if (THEME == \"light\") \"#4A4A44\" else \"#B8B7B0\"\nINK_MUTED   <- if (THEME == \"light\") \"#6B6A63\" else \"#A8A79F\"\n\n# Imprint palette — first categorical series always #009E73\nIMPRINT_PALETTE <- c(\n  \"#009E73\",  # 1 — brand green\n  \"#C475FD\",  # 2 — lavender\n  \"#4467A3\",  # 3 — blue\n  \"#BD8233\",  # 4 — ochre\n  \"#AE3030\",  # 5 — matte red\n  \"#2ABCCD\",  # 6 — cyan\n  \"#954477\",  # 7 — rose\n  \"#99B314\"   # 8 — lime\n)\n\n# --- Data: 20 RCTs on antihypertensive treatment vs. placebo ---------------\nn_studies  <- 20\npooled_lor <- 0.45  # pooled log odds ratio (treatment reduces cardiovascular risk)\n\nse_values <- c(\n  0.08, 0.10, 0.12, 0.14, 0.17, 0.19, 0.21, 0.24, 0.27, 0.30,\n  0.33, 0.36, 0.39, 0.42, 0.45, 0.48, 0.52, 0.55, 0.59, 0.63\n)\n\n# Slight funnel asymmetry: small studies tend toward larger effect (publication bias)\nbias    <- 0.22 * se_values\nlog_ors <- pooled_lor + bias + rnorm(n_studies, mean = 0, sd = se_values * 0.75)\n\nstudies <- data.frame(\n  log_or    = log_ors,\n  std_error = se_values,\n  stringsAsFactors = FALSE\n)\n\n# Pseudo 95% CI funnel boundaries (pooled_lor ± 1.96 * SE)\nmax_se  <- max(se_values) * 1.08\nse_seq  <- seq(0, max_se, length.out = 300)\n\nfunnel_lines <- data.frame(\n  se   = rep(se_seq, 2),\n  x    = c(pooled_lor - 1.96 * se_seq, pooled_lor + 1.96 * se_seq),\n  side = rep(c(\"lower\", \"upper\"), each = length(se_seq))\n)\n\n# Closed polygon for shaded funnel region\nfunnel_poly <- data.frame(\n  x  = c(pooled_lor - 1.96 * se_seq, rev(pooled_lor + 1.96 * se_seq)),\n  se = c(se_seq, rev(se_seq))\n)\n\n# --- Plot ---\ntitle_str  <- \"funnel-meta-analysis · r · ggplot2 · anyplot.ai\"\ntitle_size <- round(12 * min(1.0, 67 / nchar(title_str)))\n\np <- ggplot() +\n  # Shaded funnel region\n  geom_polygon(\n    data  = funnel_poly,\n    aes(x = x, y = se),\n    fill  = IMPRINT_PALETTE[3],\n    alpha = 0.09,\n    color = NA\n  ) +\n  # Funnel boundary lines (pseudo 95% CI)\n  geom_line(\n    data      = funnel_lines,\n    aes(x = x, y = se, group = side),\n    color     = IMPRINT_PALETTE[3],\n    linewidth = 0.7,\n    linetype  = \"dashed\"\n  ) +\n  # Null effect reference line (log OR = 0)\n  geom_vline(\n    xintercept = 0,\n    color      = INK_MUTED,\n    linewidth  = 0.5,\n    linetype   = \"dotted\"\n  ) +\n  # Pooled effect line\n  geom_vline(\n    xintercept = pooled_lor,\n    color      = INK,\n    linewidth  = 0.8\n  ) +\n  # Study points (filled circles, edge matches page background for definition)\n  geom_point(\n    data   = studies,\n    aes(x = log_or, y = std_error),\n    fill   = IMPRINT_PALETTE[1],\n    color  = PAGE_BG,\n    shape  = 21,\n    size   = 3.5,\n    stroke = 0.7,\n    alpha  = 0.9\n  ) +\n  # Invert y-axis: SE = 0 (most precise) at top, largest SE at bottom\n  scale_y_reverse(\n    expand = expansion(mult = c(0.02, 0.08))\n  ) +\n  labs(\n    x        = \"Log Odds Ratio\",\n    y        = \"Standard Error\",\n    title    = title_str,\n    subtitle = \"Publication bias assessment — 20 RCTs on antihypertensive treatment vs. placebo\",\n    caption  = sprintf(\n      \"n = %d studies  ·  pooled log OR = %.2f  ·  dashed funnel = pseudo 95%% CI\",\n      n_studies, pooled_lor\n    )\n  ) +\n  theme_minimal(base_size = 8) +\n  theme(\n    plot.background  = element_rect(fill = PAGE_BG, color = PAGE_BG),\n    panel.background = element_rect(fill = PAGE_BG, color = NA),\n    panel.grid.major = element_line(color = INK_MUTED, linewidth = 0.25),\n    panel.grid.minor = element_blank(),\n    panel.border     = element_blank(),\n    axis.title       = element_text(color = INK, size = 10),\n    axis.text        = element_text(color = INK_SOFT, size = 8),\n    axis.line        = element_line(color = INK_SOFT, linewidth = 0.5),\n    plot.title       = element_text(color = INK, size = title_size, face = \"bold\"),\n    plot.subtitle    = element_text(color = INK_SOFT, size = 9),\n    plot.caption     = element_text(color = INK_MUTED, size = 7, hjust = 1),\n    plot.margin      = margin(t = 16, r = 20, b = 12, l = 14, unit = \"pt\")\n  )\n\n# --- Save ---\nggsave(\n  filename = sprintf(\"plot-%s.png\", THEME),\n  plot     = p,\n  device   = ragg::agg_png,\n  width    = 8,\n  height   = 4.5,\n  units    = \"in\",\n  dpi      = 400\n)\n"}