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

ggplot2水平条形图双侧堆叠及y轴排序实现问题

水平双向堆叠条形图的ggplot2实现修正方案

需求说明

  • 将df1、df3的age数值置于图表右侧,df2、df4的age数值置于左侧
  • y轴的who类别按df1$age的总和从大到小排序
  • df3的age数值堆叠在df1对应的条形之上,df4的age数值堆叠在df2对应的条形之上

示例数据集

df1 <- data.frame(who = rep(LETTERS[1:5], each = 5), 
        age = 1:25)
df2 <- data.frame(who = rep(LETTERS[1:5], each = 5), 
        age = c(rep(1, 5), rep(5, 5), 1, 12, 3, 2,1, 1:5, 
                6:10))
df3 <- data.frame(who = rep(LETTERS[1:5], each = 4), 
        age = c(rep(2, 10), rep(3, 10)))
df4 <- data.frame(who = rep(LETTERS[1:5], each = 5), 
        age = c(rep(1, 5), rep(5, 5), 1, 1, 3, 2, 1, 1:5, 
                6:10))

myList <- list(df1, df2, df3, df4)
names(myList) <- c("df1", "df2", "df3", "df4")

当前代码问题

现有代码的颜色、分组设置逻辑正确,但存在两个核心问题:

  • 未正确映射正负值到x轴,导致左侧无条形显示
  • 堆叠逻辑错误,df3/df4未正确堆叠在df1/df2之上

当前代码:

p <- dplyr::bind_rows("DF 1" = myList[["df1"]],
                      "DF 2" = myList[["df2"]],
                      "DF 3" = myList[["df3"]],
                      "DF 4" = myList[["df4"]],
                      .id = "ID") %>%
  count(ID, who, wt = age, name = "age") %>%
  arrange(match(ID, c("DF 1", "DF 2", "DF 3", "DF 4"))) 
  %>%
  mutate(who = reorder(who, (ID == "DF 1") * age, sum), 
    value = if_else((ID == "DF 2" | 
                       ID == "DF 4"), -age, age)
  ) %>%
  ggplot(aes(y = who, x = age, fill = ID, group = 1)) +
  geom_col(position = "stack", alpha = .6, width = .6) +
  geom_vline(xintercept = 0) +
  scale_x_continuous(labels = abs) + 
  scale_y_discrete(expand = c(0,1.4)) + 
  scale_fill_manual(values = c("#74D055FF","#481568FF", 
                               "#F1ED6FFF", "#F1ED6FFF"),
                    breaks = c("DF 1", "DF 2", "DF 3", 
                               "DF 4")) +
  theme_bw()

修正后的完整代码

library(dplyr)
library(ggplot2)

p <- dplyr::bind_rows("DF 1" = myList[["df1"]],
                      "DF 2" = myList[["df2"]],
                      "DF 3" = myList[["df3"]],
                      "DF 4" = myList[["df4"]],
                      .id = "ID") %>%
  # 按ID和who分组计算age总和
  count(ID, who, wt = age, name = "age") %>%
  # 新增side字段区分左右侧,用于分组堆叠
  mutate(side = if_else(ID %in% c("DF 1", "DF 3"), "right", "left"),
         # 计算正负值,左侧为负,右侧为正
         value = if_else(side == "left", -age, age),
         # 严格按df1的age总和排序who
         who = reorder(who, -age[ID == "DF 1"], sum)) %>%
  ggplot(aes(y = who, x = value, fill = ID, group = interaction(who, side))) +
  # 使用position_stack确保同侧边的条形正确堆叠
  geom_col(position = position_stack(reverse = FALSE), alpha = .6, width = .6) +
  geom_vline(xintercept = 0, linetype = "solid", color = "black") +
  scale_x_continuous(labels = abs, expand = c(0.05, 0)) +
  scale_y_discrete(expand = c(0, 0.5)) +
  scale_fill_manual(values = c("#74D055FF","#481568FF", "#F1ED6FFF", "#F1ED6FFF"),
                    breaks = c("DF 1", "DF 2", "DF 3", "DF 4")) +
  labs(x = "Age Sum", y = "Who") +
  theme_bw()

print(p)

关键修正点说明

  1. x轴映射修正:将x = age改为x = value,利用预先计算的正负值实现左右侧的区分,让左侧的df2/df4条形正确显示在0轴左侧。
  2. 堆叠分组优化:新增side字段并设置group = interaction(who, side),确保每个who的左右侧分别独立堆叠,避免跨侧的数值干扰。
  3. 排序逻辑明确:修改reorder的参数为-age[ID == "DF 1"],确保who严格按照df1的age总和降序排列。
  4. 堆叠参数调整:使用position_stack(reverse = FALSE)确保df3堆叠在df1之上、df4堆叠在df2之上,符合需求的堆叠顺序。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 12:47:22