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

ggplot2双Y轴geom_line绘图报错及分组年度总和值连线实现技术问询

问题1:解决数值运算错误

你遇到的第一个错误 "Error in p - lim2[1] : argument of binary operator is not numerical" 根源出在数据框的创建上——用cbind()组合列的时候,因为year是字符类型,所有列被统一转换成了字符型,导致value列不是数值,后续的缩放函数自然没法做减法运算。

修复这个问题很简单,不要用cbind()+as.data.frame(),直接用data.frame()创建数据框,它会自动识别各列的类型:

year <- c("2000", "2000", "2000", "2000", "2025", "2025", "2025", "2025", "2050", "2050", "2050", "2050")
group <- c("a","a","b","b","a","a","b","b","a","a","b","b")
value <- c(1,1.5,2,2.5,2,2.2,3,3.3,4,4.4,5,5.3)
# 直接用data.frame创建,自动识别类型
d <- data.frame(year, group, value)

另外,你最初用stat_summary(fun = "sum")直接放到geom_line的y参数里的写法也不对,stat_summary需要配合stat参数或者单独使用,不过后来你改成新增sum列的方式是正确的方向。


问题2:绘制总和值的连线

你后来的代码里geom_line没生效,是因为把它放在了ggplot对象外面,没有和前面的pd对象用+拼接起来。另外,用mutate(sum = sum(value))会给每一行都添加组内总和,数据里每个(year, group)组合有重复的sum值,直接画连线会因为重复点导致线条异常,最好先单独汇总出每个(year, group)的总和,再用这个汇总数据来画点和线。

给你调整后的完整代码:

# 先定义缩放函数(和你原来的一致)
y1.lim <- c(0, 15)
y2.lim <- c(0, 25)
variable_scaler <- function(p, lim1, lim2){
  to_zero <- p-lim2[1]
  y1_range <- lim1[2]-lim1[1]
  y2_range <- lim2[2]-lim2[1]
  scaled <- to_zero*y1_range/y2_range
  from_zero <- scaled + lim1[1]
  return(from_zero)
}
axis_scaler <- function(p, lim1, lim2){
  to_zero <- p-lim1[1]
  y1_range <- lim1[2]-lim1[1]
  y2_range <- lim2[2]-lim2[1]
  scaled <- to_zero*y2_range/y1_range
  from_zero <- scaled + lim2[1]
  return(from_zero)
}

# 1. 创建原始数据(修复类型问题)
year <- c("2000", "2000", "2000", "2000", "2025", "2025", "2025", "2025", "2050", "2050", "2050", "2050")
group <- c("a","a","b","b","a","a","b","b","a","a","b","b")
value <- c(1,1.5,2,2.5,2,2.2,3,3.3,4,4.4,5,5.3)
d <- data.frame(year, group, value)

# 2. 单独汇总(year, group)的总和,避免重复值
sum_df <- d %>% 
  group_by(year, group) %>% 
  summarise(sum_val = sum(value), .groups = "drop")  # .groups="drop"取消分组状态

# 3. 绘图:原始数据画箱线图,汇总数据画点和线
pd <- d %>% 
  ggplot() + 
  # 原始数据的箱线图
  geom_boxplot(aes(x = year, y = value, color = group, group = interaction(year, group)), 
               alpha = 0.1, width = 0.75) + 
  # 汇总数据的点(注意用sum_df的数据)
  geom_point(data = sum_df, 
             aes(x = year, y = variable_scaler(sum_val, y1.lim, y2.lim), color = group),
             position = position_dodge(width = 0.75), size = 2) + 
  # 汇总数据的线(同样用sum_df,group要设为group,保证同组跨年份连线)
  geom_line(data = sum_df, 
            aes(x = year, y = variable_scaler(sum_val, y1.lim, y2.lim), color = group, group = group),
            position = position_dodge(width = 0.75)) + 
  # 双Y轴设置
  scale_y_continuous(limits = y1.lim, breaks = c(0, 5, 10, 15),
                     sec.axis = sec_axis(~axis_scaler(., y1.lim, y2.lim), 
                                        breaks = c(0,5, 10,15, 20, 25), 
                                        name = 'sum')) +
  labs(color = "Group")  # 可选:给图例改个名字

plot(pd)

关键调整点:

  1. 用summarise替代mutate生成汇总数据,每个(year, group)只保留一行总和,避免重复值干扰连线。
  2. geom_line的group参数设为group,这样ggplot会把同group的不同年份的点连起来,而不是按interaction(year, group)分组(那样每个年份的点都是单独组,没法连线)。
  3. 把geom_line正确加到ggplot对象里,用+拼接。
  4. 绘图时给geom_point和geom_line指定data = sum_df,明确用汇总数据来画总和的点和线,和原始数据的箱线图区分开。

这样就能正常画出每个分组的年度总和点,以及跨年份的连线了。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 19:17:29