# 亚组效应森林图：均值差及 Welch 95% 置信区间
# 项目原创脚本，MIT 许可；模拟数据 CC0-1.0；图形 CC BY 4.0。
# 在全新 R 会话运行。每次运行会更新下方 output_dir 中的本示例文件。
# 图形标签使用英文以避免不同电脑缺少中文字体；可在参数和 labs() 中修改。

# 1. 依赖：首次使用时手动运行以下安装命令，脚本不会自动安装。
# install.packages(c("ggplot2", "jsonlite"))
required_packages <- c("ggplot2", "jsonlite")
missing_packages <- required_packages[!vapply(required_packages, requireNamespace, logical(1), quietly = TRUE)]
if (length(missing_packages)) stop("Please install: ", paste(missing_packages, collapse = ", "))
library(ggplot2)

# 2. 可修改参数：颜色、字体、导出尺寸和路径。输出路径相对于当前工作目录。
set.seed(20261014)
colors <- c("#007F96", "#B66A46")
font_family <- "sans"
figure_width <- 8
figure_height <- 5.5
output_dir <- file.path(getwd(), "rfig-forest-output")
dir.create(output_dir, recursive = TRUE, showWarnings = FALSE)
theme_set(theme_minimal(base_size = 12, base_family = font_family) +
 theme(plot.background = element_rect(fill = "white", color = NA), panel.grid.minor = element_blank(),
       plot.title = element_text(face = "bold", size = 15), legend.position = "bottom",
       plot.margin = margin(14, 18, 14, 14)))

# 3. 示例数据：每行一位受试者，分组和亚组互斥。 替换组别、亚组和结局字段；若改用 OR/RR/HR，须同步更改计算、无效线和坐标尺度。
# 使用自己的 CSV 时，将以下数据生成段替换为：
# data <- read.csv("your-data.csv", stringsAsFactors = FALSE)
# 再按本例要求设置分类顺序、检查字段、单位及缺失值。
data <- expand.grid(person = 1:60, group = c("Control", "Treatment"), sex = c("Female", "Male"))
data$id <- seq_len(nrow(data)); data$person <- NULL
data$value <- 12 + (data$sex == "Male") - 1.5 * (data$group == "Treatment") + rnorm(nrow(data), 0, 3)
stopifnot(!anyNA(data))

# 4. 统计计算与绘图。效应为 Treatment−Control 的未调整均值差，区间使用 Welch t 方法；无效线为 0，采用线性坐标。总体与亚组结果重叠，未做交互检验，不能依据亚组各自显著性推断效应不同。
estimate <- function(d, label) {
 a <- d$value[d$group == "Treatment"]; b <- d$value[d$group == "Control"]; test <- t.test(a, b)
 data.frame(label = label, estimate = mean(a)-mean(b), lower = test$conf.int[1], upper = test$conf.int[2], n = nrow(d))
}
effects <- rbind(estimate(data, "Overall"), estimate(subset(data, sex == "Female"), "Female"), estimate(subset(data, sex == "Male"), "Male"))
effects$label <- factor(effects$label, levels = c("Male", "Female", "Overall"))
p <- ggplot(effects, aes(estimate, label)) + geom_vline(xintercept = 0, linetype = "dashed", color = "#8DA5B6") +
 geom_segment(aes(x = lower, xend = upper, yend = label), linewidth = .8, color = colors[1]) + geom_point(size = 3.5, shape = 18, color = colors[1]) +
 labs(x = "Mean difference: Treatment - Control (95% CI)", y = NULL, title = "Overall and subgroup estimates")
metrics <- list(estimate = effects$estimate, lower = effects$lower, upper = effects$upper)
print(p)

# 5. 导出：CSV 与绘图所用 data 完全一致，preview 与科研图片均来自同一个 p。
write.csv(data, file.path(output_dir, "data.csv"), row.names = FALSE, fileEncoding = "UTF-8")
ggsave(file.path(output_dir, "preview.png"), p, width = figure_width, height = figure_height, dpi = 160, bg = "white")
ggsave(file.path(output_dir, "figure.png"), p, width = figure_width, height = figure_height, dpi = 300, bg = "white")
ggsave(file.path(output_dir, "figure.pdf"), p, width = figure_width, height = figure_height, device = grDevices::pdf, bg = "white")
ggsave(file.path(output_dir, "figure.tiff"), p, width = figure_width, height = figure_height, dpi = 300, compression = "lzw", bg = "white")
jsonlite::write_json(metrics, file.path(output_dir, "metrics.json"), auto_unbox = TRUE, digits = 16, pretty = TRUE)
