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

如何在R的ggplot2中实现两个图表的水平层级对齐?

问题描述

我在R中有一个名为df的数据框,包含3个问题的Likert数据和一个名为var的分组变量:

var_levels <- c(LETTERS[1:5])
n = 500
likert_levels = c(
  "Very \n Dissatisfied",
  "Dissatisfied",
  "Neutral",
  "Satisfied",
  "Very \n Satisfied"
)

df <- tibble(
  var = sample(var_levels, n, replace = TRUE),  
  val1 = sample(likert_levels, n, replace = TRUE),
  val2 = sample(likert_levels, n, replace = TRUE),
  val3 = sample(likert_levels, n, replace = TRUE)
)

统计每个var层级的样本量:

df_n = df%>%
  select(var)%>%
  group_by(var)%>%
  summarise(counts=n())

输出结果:

# A tibble: 5 × 2
  var   counts
  <chr>  <int>
1 A         91
2 B         77
3 C        122
4 D        104
5 E        106

随后将Likert数据做pivot_longer得到df2用于绘制Likert图,同时基于分组计数数据绘制条形图。用patchwork拼接Likert图p1和条形图p2后,发现两个图表的y轴层级未水平对齐。需要让Likert图中的每个层级与条形图的对应层级横向匹配,且不能使用df2数据(会导致计数错误)。

解决方案

问题根源在于两个图的y_sort因子顺序不一致:Likert图的y_sort是按group和prop_lower排序后的有序因子,而条形图的y_sort是自行拼接生成的,没有继承这个顺序。修正思路是直接从Likert图的数据源dat中提取已排序好的y_sort因子水平,同步给条形图的数据源,确保两者y轴的层级顺序完全一致。

修正后的关键代码

1. 修正条形图数据源dat_bar的生成

不再自行拼接y_sort,而是直接从dat中提取已有的y_sort(包含正确的因子顺序),并去重:

dat = dat%>%
  left_join(.,df_n,by="var") 

# 修正:直接使用dat中已排序的y_sort,确保因子顺序与p1一致
dat_bar = dat %>%
  select(y_sort, group, counts) %>%
  distinct(y_sort, group, counts)  # 按y_sort去重,保留正确顺序

2. 确保条形图的y轴使用相同的因子顺序

在绘制p2时,不需要额外修改scale_y_discrete,因为dat_bar$y_sort已经继承了dat中的有序因子属性,会自动和p1的y轴对齐。

完整修正后代码

# 原始数据生成
var_levels <- c(LETTERS[1:5])
n = 500
likert_levels = c(
  "Very \n Dissatisfied",
  "Dissatisfied",
  "Neutral",
  "Satisfied",
  "Very \n Satisfied"
)

df <- tibble(
  var = sample(var_levels, n, replace = TRUE),  
  val1 = sample(likert_levels, n, replace = TRUE),
  val2 = sample(likert_levels, n, replace = TRUE),
  val3 = sample(likert_levels, n, replace = TRUE)
)

# 统计分组样本量
df_n = df%>%
  select(var)%>%
  group_by(var)%>%
  summarise(counts=n())

# Likert图数据处理
df2 = df%>%
  pivot_longer(!var, names_to = "Categories", values_to = "likert_values")%>%
  select(-Categories)

dat <- df2 |>
  mutate(
    across(-var, ~ factor(.x, likert_levels))
  ) |>
  pivot_longer(-var, names_to = "group") |>
  count(var, value, group) |>
  complete(var, value, group, fill = list(n = 0)) |>
  mutate(
    prop = n / sum(n),
    prop_lower = sum(prop[value %in% likert_levels[1:2]]),
    prop_higher = sum(prop[value %in% likert_levels[4:5]]),
    .by = c(var, group)
  ) |>
  arrange(group, prop_lower) |>
  mutate(
    y_sort = paste(var, group, sep = "."),
    y_sort = fct_inorder(y_sort)
  )%>%
  select(-n)

top10 <- dat |>
  distinct(group, var, prop_lower) |>
  slice_max(prop_lower, n = 10, by = group)

dat <- dat |>
  semi_join(top10)

dat_tot <- dat |>
  distinct(group, var, y_sort, prop_lower, prop_higher) |>
  pivot_longer(-c(group, var, y_sort),
               names_to = c(".value", "name"),
               names_sep = "_"
  ) |>
  mutate(
    hjust_tot = ifelse(name == "lower", 1, 0),
    x_tot = ifelse(name == "lower", -0.6, 0.6)
  )

# 修正后的条形图数据源
dat = dat%>%
  left_join(.,df_n,by="var") 

dat_bar = dat %>%
  select(y_sort, group, counts) %>%
  distinct(y_sort, group, counts)

# 绘制Likert图p1
p1 <- ggplot(dat, aes(y = y_sort, x = prop, fill = value)) +
  geom_col(position = position_likert(reverse = FALSE)) +
  geom_text(
    aes(
      label = label_percent_abs(hide_below = .05, accuracy = 1)(prop),
      color = after_scale(hex_bw(.data$fill))
    ),
    position = position_likert(vjust = 0.5, reverse = FALSE),
    size = 3.5
  ) +
  geom_label(
    aes(
      x = x_tot,
      label = label_percent_abs(accuracy = 1)(prop),
      hjust = hjust_tot,
      fill = NULL
    ),
    data = dat_tot,
    size = 3.5,
    color = "black",
    fontface = "bold",
    label.size = 0,
    show.legend = FALSE
  ) +
  scale_y_discrete(labels = \(x) gsub("\\..*$", "", x)) +
  scale_x_continuous(
    labels = label_percent_abs(),
    expand = c(0, .15)
  ) +
  scale_fill_brewer(palette = "BrBG") +
  facet_wrap(~group,
             scales = "free_y", ncol = 1,
             strip.position = "right"
  ) +
  theme_light() +
  theme(
    legend.position = "bottom",
    panel.grid.major.y = element_blank(),
    strip.text = element_blank()
  ) +
  labs(x = NULL, y = NULL, fill = NULL)

# 绘制条形图p2
p2 <- ggplot(dat_bar, aes(y = y_sort, x = counts)) +
  geom_col() +
  geom_label(
    aes(
      label = label_number_abs(hide_below = .05, accuracy = 1)(counts)
    ),
    size = 3.5,
    hjust = 1,
    fill = NA,
    label.size = 0,
    color = "white"
  ) +
  scale_y_discrete(labels = \(x) gsub("\\..*$", "", x)) +
  scale_x_continuous(
    labels = label_number_abs(),
    expand = c(0, 0, 0, .05)
  )+
  theme_light() +
  theme(
    legend.position = "bottom",
    panel.grid.major.y = element_blank()
  ) +
  labs(x = NULL, y = NULL, fill = NULL)

# 拼接图表
library(patchwork)

p1 + p2 +
  plot_layout(
    axes = "collect", 
    guides = "collect") &
  theme(legend.position = "bottom")

关键说明

  • 核心是让dat_bar的y_sort完全继承dat中通过fct_inorder(y_sort)生成的有序因子,确保两个图的y轴层级顺序完全一致。
  • 避免自行拼接y_sort,因为这样会丢失原有的因子排序信息,导致对齐失败。
  • 不需要修改patchwork的布局参数,只要两个图的y轴因子顺序一致,横向就会自动对齐。

内容的提问来源于stack exchange,提问作者Homer Jay Simpson

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 11:14:52