You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在ggplot2中为每个变量创建独立Y轴并可视化均值与标准差

问题描述

需要绘制一款类似平行坐标图的描述统计图表,按性别分层展示各变量的均值及标准差,但无法实现为每个变量创建独立Y轴的效果,现有数据及代码如下:

library(dplyr)
library(tidyr)
library(ggplot2)

my_data <- data.frame(
  sex = c("m", "w", "m", "w", "m", "w"),
  age = c(25, 30, 22, 35, 28, 46),
  testosterone = c(450, 200, 400, 300, 500, 350),
  cognition = c(75, 80, 70, 85, 78, 90),
  estrogen = c(20, 40, 15, 50, 10, 45)
)

numeric_vars <- c("age", "testosterone", "cognition", "estrogen")

# 计算各性别下各变量的均值及标准差
df_means <- my_data %>%
  group_by(sex) %>%
  summarise(across(all_of(numeric_vars), mean, na.rm = TRUE))

df_sds <- my_data %>%
  group_by(sex) %>%
  summarise(across(all_of(numeric_vars), sd, na.rm = TRUE))

# 重塑数据格式
df_melted <- reshape2::melt(df_means, id.vars = 'sex')
df_sds_melted <- reshape2::melt(df_sds, id.vars = 'sex')

# 合并均值和标准差数据
df_combined <- merge(df_melted, df_sds_melted, by = c('sex', 'variable'))
names(df_combined)[names(df_combined) == 'value.x'] <- 'mean'
names(df_combined)[names(df_combined) == 'value.y'] <- 'sd'

# 绘制未标准化的平行坐标图
ggplot(df_combined, aes(x = variable, y = mean, group = sex, color = sex)) +
  geom_line() +
  geom_ribbon(aes(ymin = mean - sd, ymax = mean + sd, fill = sex), alpha = 0.2) +
  scale_color_manual(values = c("m" = "#007BC3", "w" = "#EA5451")) +
  scale_fill_manual(values = c("m" = "#007BC3", "w" = "#EA5451")) +
  theme_minimal() 
解决方案

ggplot本身不推荐使用多独立Y轴(易造成视觉误导),但可以通过以下两种方式满足需求:

方式1:分面展示(推荐)

将每个变量拆分为独立面板,每个面板自带独立Y轴,同时保留性别分组的均值线和标准差区间,视觉清晰且无误导性:

library(dplyr)
library(tidyr)
library(ggplot2)

my_data <- data.frame(
  sex = c("m", "w", "m", "w", "m", "w"),
  age = c(25, 30, 22, 35, 28, 46),
  testosterone = c(450, 200, 400, 300, 500, 350),
  cognition = c(75, 80, 70, 85, 78, 90),
  estrogen = c(20, 40, 15, 50, 10, 45)
)

numeric_vars <- c("age", "testosterone", "cognition", "estrogen")

# 一步计算均值、标准差并整理数据格式
df_combined <- my_data %>%
  group_by(sex) %>%
  summarise(across(all_of(numeric_vars), list(mean = mean, sd = sd), na.rm = TRUE)) %>%
  pivot_longer(-sex, names_to = c("variable", ".value"), names_sep = "_")

# 分面绘制,每个变量使用独立Y轴
ggplot(df_combined, aes(x = sex, y = mean, color = sex)) +
  geom_line(aes(group = sex), linewidth = 1, position = position_dodge(0.2)) +
  geom_errorbar(aes(ymin = mean - sd, ymax = mean + sd), width = 0.2, position = position_dodge(0.2)) +
  facet_wrap(~variable, scales = "free_y") +
  scale_color_manual(values = c("m" = "#007BC3", "w" = "#EA5451")) +
  theme_minimal() +
  labs(y = "均值(±标准差)")

方式2:同一画布添加多Y轴(不推荐)

如果坚持要在同一图中展示所有变量和多Y轴,需要先将变量标准化到同一区间,再手动添加每个变量的原始刻度,代码如下:

library(dplyr)
library(tidyr)
library(ggplot2)
library(grid)

my_data <- data.frame(
  sex = c("m", "w", "m", "w", "m", "w"),
  age = c(25, 30, 22, 35, 28, 46),
  testosterone = c(450, 200, 400, 300, 500, 350),
  cognition = c(75, 80, 70, 85, 78, 90),
  estrogen = c(20, 40, 15, 50, 10, 45)
)

numeric_vars <- c("age", "testosterone", "cognition", "estrogen")

# 计算均值、标准差并整理数据
df_combined <- my_data %>%
  group_by(sex) %>%
  summarise(across(all_of(numeric_vars), list(mean = mean, sd = sd), na.rm = TRUE)) %>%
  pivot_longer(-sex, names_to = c("variable", ".value"), names_sep = "_")

# 为每个变量分配X轴位置
x_pos <- setNames(1:4, numeric_vars)
df_combined$x <- x_pos[df_combined$variable]

# 将每个变量的均值和标准差标准化到0-1区间
scale_func <- function(x) (x - min(x)) / (max(x) - min(x))
df_scaled <- df_combined %>%
  group_by(variable) %>%
  mutate(mean_scaled = scale_func(mean),
         sd_scaled = sd / (max(mean) - min(mean)))

# 绘制主图(标准化后的数据)
p <- ggplot(df_scaled, aes(x = x, y = mean_scaled, group = sex, color = sex)) +
  geom_line(linewidth = 1) +
  geom_ribbon(aes(ymin = mean_scaled - sd_scaled, ymax = mean_scaled + sd_scaled, fill = sex), alpha = 0.2) +
  scale_x_continuous(breaks = x_pos, labels = names(x_pos)) +
  scale_color_manual(values = c("m" = "#007BC3", "w" = "#EA5451")) +
  scale_fill_manual(values = c("m" = "#007BC3", "w" = "#EA5451")) +
  theme_minimal() +
  theme(axis.title.y = element_blank())

# 为每个变量添加独立Y轴
for(var in numeric_vars){
  var_data <- df_combined %>% filter(variable == var)
  y_min <- min(var_data$mean - var_data$sd)
  y_max <- max(var_data$mean + var_data$sd)
  
  # 添加变量名称标注
  p <- p + annotation_custom(
    textGrob(var, rot = 90, gp = gpar(fontsize = 10)),
    xmin = x_pos[var], xmax = x_pos[var],
    ymin = -0.1, ymax = -0.1
  )
  
  # 添加对应Y轴刻度
  p <- p + scale_y_continuous(
    sec.axis = sec_axis(
      trans = ~ .*(y_max - y_min) + y_min,
      breaks = seq(floor(y_min), ceiling(y_max), length.out = 3),
      labels = function(x) round(x, 1)
    )
  )
}

# 调整右侧Y轴文字大小
p + theme(axis.text.y.right = element_text(size = 8))

内容的提问来源于stack exchange,提问作者Vivien

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 12:05:05