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

使用ggplot2的facet_wrap绘制多饼图显示异常求助

问题:facet_wrap绘制多饼图显示异常

尝试用facet_wrap()将5个饼图整合到同一图形中,但饼图及文本标签显示异常,当前异常效果为各分面饼图比例错乱,文本位置偏移:

问题截图

问题代码

library(ggplot2)
library(dplyr)
library(ggrepel)
library(RColorBrewer) # 补充原代码缺失的包

plot_df <- data.frame(
  opioid = c("Codeine", "Hydromorphone", "Oxycodone", "Tramadol", "Fentanyl", "Meperidine", 
             "Morphine", "Sufentanil", "Remifentanil", "Fentanyl", "Methadone", "Oxymorphone", 
             "Sufentanil", "Remifentanil", "Buprenorphine", "Fentanyl", "Methadone", "Oxymorphone", 
             "Pentazocine", "Sufentanil", "Hydrocodone", "Morphine", "Oxycodone", "Tramadol"),
  occur_time = c(1802,1291,2767,2364,3486,126,2667,441,387,2369,1023,147,194,261,370,1947,722,89,168,134,535,776,903,835),
  percentage = c(21.91,15.70,33.65,28.75,49.05,1.77,37.53,6.21,5.45,59.31,25.61,3.68,4.86,6.53,10.79,56.76,21.05,2.59,4.90,3.91,17.55,25.45,29.62,27.39),
  pt = c("Hypertension","Hypertension","Hypertension","Hypertension",
         "Hypotension","Hypotension","Hypotension","Hypotension","Hypotension",
         "Cardiac Arrest","Cardiac Arrest","Cardiac Arrest","Cardiac Arrest","Cardiac Arrest",
         "Cardio-Respiratory Arrest","Cardio-Respiratory Arrest","Cardio-Respiratory Arrest",
         "Cardio-Respiratory Arrest","Cardio-Respiratory Arrest","Cardio-Respiratory Arrest",
         "Myocardial Infarction","Myocardial Infarction","Myocardial Infarction","Myocardial Infarction")
)

valid_colors <- c(brewer.pal(name = "Pastel1", n = 9), brewer.pal(name = "Accent", n = 5))
opioid_colors <- setNames(
  valid_colors,
  unique(plot_df$opioid)
)

tmp_df <- plot_df %>%
  mutate(csum = rev(cumsum(rev(occur_time))),
         pos = occur_time / 2 + lead(csum, 1),
         pos = if_else(is.na(pos), occur_time / 2, pos)) %>%
  group_by(pt)

cp <- coord_polar(theta = "y")
cp$is_free <- function() TRUE

ggplot(tmp_df, aes(x = "", y = occur_time, fill = opioid)) +
  geom_col(width = 1, color = "white") +
  cp +
  facet_wrap(~ pt, scales = "free_y") +
  geom_text_repel(aes(label = paste0(opioid, "\n", occur_time),
                      y = pos),
                  size = 3, nudge_x = 1.5, show.legend = FALSE, segment.color = "grey50") +
  scale_fill_manual(values = opioid_colors) +
  theme_void() +
  theme(strip.text = element_text(size = 14, face = "bold"),
        legend.position = "right",
        plot.title = element_text(hjust = 0.5),
        aspect.ratio = 1) +
  guides(fill = guide_legend(title = "Opioid"))

问题原因

  1. 分组计算错误:csum和pos是在全局数据上计算的,没有按pt分组后再计算,导致每个分面的文本位置引用了全局累加值,完全错乱。
  2. coord_polar自定义修改冗余:虽然设置is_free是为了适配自由尺度,但可以通过更简洁的方式处理,且原代码的分组时机错误。

修正方案

核心是按pt分组后计算每个饼图的文本位置,同时调整绘图参数确保饼图显示正常:

library(ggplot2)
library(dplyr)
library(ggrepel)
library(RColorBrewer)

plot_df <- data.frame(
  opioid = c("Codeine", "Hydromorphone", "Oxycodone", "Tramadol", "Fentanyl", "Meperidine", 
             "Morphine", "Sufentanil", "Remifentanil", "Fentanyl", "Methadone", "Oxymorphone", 
             "Sufentanil", "Remifentanil", "Buprenorphine", "Fentanyl", "Methadone", "Oxymorphone", 
             "Pentazocine", "Sufentanil", "Hydrocodone", "Morphine", "Oxycodone", "Tramadol"),
  occur_time = c(1802,1291,2767,2364,3486,126,2667,441,387,2369,1023,147,194,261,370,1947,722,89,168,134,535,776,903,835),
  percentage = c(21.91,15.70,33.65,28.75,49.05,1.77,37.53,6.21,5.45,59.31,25.61,3.68,4.86,6.53,10.79,56.76,21.05,2.59,4.90,3.91,17.55,25.45,29.62,27.39),
  pt = c("Hypertension","Hypertension","Hypertension","Hypertension",
         "Hypotension","Hypotension","Hypotension","Hypotension","Hypotension",
         "Cardiac Arrest","Cardiac Arrest","Cardiac Arrest","Cardiac Arrest","Cardiac Arrest",
         "Cardio-Respiratory Arrest","Cardio-Respiratory Arrest","Cardio-Respiratory Arrest",
         "Cardio-Respiratory Arrest","Cardio-Respiratory Arrest","Cardio-Respiratory Arrest",
         "Myocardial Infarction","Myocardial Infarction","Myocardial Infarction","Myocardial Infarction")
)

valid_colors <- c(brewer.pal(name = "Pastel1", n = 9), brewer.pal(name = "Accent", n = 5))
opioid_colors <- setNames(valid_colors, unique(plot_df$opioid))

# 关键修改:先分组,再计算组内的累加和文本位置
tmp_df <- plot_df %>%
  group_by(pt) %>%
  arrange(desc(opioid)) %>% # 排序保证累加顺序一致
  mutate(
    csum = rev(cumsum(rev(occur_time))),
    pos = occur_time / 2 + lead(csum, 1),
    pos = if_else(is.na(pos), occur_time / 2, pos)
  ) %>%
  ungroup() # 取消分组避免ggplot自动分组冲突

ggplot(tmp_df, aes(x = "", y = occur_time, fill = opioid)) +
  geom_col(width = 1, color = "white") +
  coord_polar(theta = "y") +
  facet_wrap(~ pt, scales = "free") + # 使用scales="free"适配各分面尺度
  geom_text_repel(
    aes(label = paste0(opioid, "\n", occur_time), y = pos),
    size = 3, nudge_x = 1.2, show.legend = FALSE, segment.color = "grey50"
  ) +
  scale_fill_manual(values = opioid_colors) +
  theme_void() +
  theme(
    strip.text = element_text(size = 14, face = "bold"),
    legend.position = "right",
    plot.title = element_text(hjust = 0.5),
    aspect.ratio = 1
  ) +
  guides(fill = guide_legend(title = "Opioid"))

修改说明

  1. 分组计算位置:将group_by(pt)移到mutate之前,确保csum和pos是每个pt组内的独立计算值,文本位置准确对应饼图扇区。
  2. 简化coord_polar:去掉自定义的cp对象,直接使用coord_polar(theta = "y"),配合scales="free"实现各分面饼图的独立尺度。
  3. 排序优化:添加arrange(desc(opioid))保证每个组内的扇区顺序一致,避免同一opioid在不同分面位置混乱。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 07:07:03