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

ggplot2 facet_wrap分组柱状图如何按分面显示占比而非计数

需求说明

使用的数据框包含两个因子变量:

  • cls:共3个水平
  • subset:共2个水平
    需要对比subset两个分组下不同cls类别的分布,要求y轴展示占比(百分比)替代原始计数,且占比需在单个subset分面组内独立计算,不能基于全数据集统计。

示例数据构造代码:

library(tidyverse)
data = data.frame(
  x = rnorm(1000),
  cls = factor(c(rep("A", 200), rep("B", 300), rep("C", 500))),
  subset = factor(c(rep("train", 900), rep("test", 100)))
)
问题复现

初始错误实现

初始代码直接在geom_bar中计算全局占比,未按分面分组单独统计,不符合需求:

ggplot(data, aes(x = cls, fill = cls)) + 
  geom_bar(aes(y = ..count.. / sum(..count..))) + 
  facet_wrap(~subset)

错误效果:
错误实现效果
可以看到test组所有类别占比加和仅为10%,是因为计算时用了全数据集1000条作为分母,没有按test组自身100条样本计算占比。

自定义函数报错问题

参考思路编写通用绘图函数时运行报错,原函数代码:

plot_train_vs_test = function(data, var, subset_colname){
  plot_data = data %>% 
    count(var, eval(subset_colname)) %>% 
    group_by(eval(subset_colname)) %>% 
    mutate(perc = n/sum(n))
  
  ggplot(plot_data, aes(x = var, y = perc, fill = var)) +
    geom_col() +
    scale_y_continuous(labels = scales::label_percent()) +
    facet_wrap(~eval(subset_colname))
}

plot_train_vs_test(data, "cls", "subset")

报错核心原因是dplyr和ggplot2的非标准评估逻辑下,直接传入字符串列名、用eval()简单包裹无法正确识别列。

正确实现方案

方案1:直接在绘图层计算分面内占比

不需要提前汇总数据,利用ggplot内置的分面标识变量PANEL,为每个分面单独计算分母总和:

ggplot(data, aes(x = cls, fill = cls)) +
  geom_bar(aes(y = after_stat(count / tapply(count, PANEL, sum)[PANEL]))) +
  scale_y_continuous(labels = scales::label_percent()) +
  facet_wrap(~subset) +
  labs(y = "组内占比")

实现逻辑:PANEL是ggplot生成分面时自动创建的分组变量,tapply(count, PANEL, sum)会返回每个分面的总样本数,匹配到对应分面的柱子作为分母,即可得到每个分面内的独立占比。

方案2:提前汇总数据(适配自定义函数场景)

使用tidyverse的.data代词引用字符串形式的列名,修复非标准评估导致的报错,修复后的通用函数:

plot_train_vs_test = function(data, var, subset_colname){
  plot_data = data %>% 
    # 用.data[[字符串]] 正确引用列名
    count(.data[[var]], .data[[subset_colname]]) %>% 
    group_by(.data[[subset_colname]]) %>% 
    mutate(perc = n/sum(n))
  
  ggplot(plot_data, aes(x = .data[[var]], y = perc, fill = .data[[var]])) +
    geom_col() +
    scale_y_continuous(labels = scales::label_percent()) +
    facet_wrap(~.data[[subset_colname]]) +
    labs(x = var, y = "组内占比", fill = var)
}

# 测试运行
plot_train_vs_test(data, "cls", "subset")

运行后两个分面内的类别占比各自加和为100%,符合需求。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 12:27:24