使用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"))
问题原因
- 分组计算错误:
csum和pos是在全局数据上计算的,没有按pt分组后再计算,导致每个分面的文本位置引用了全局累加值,完全错乱。 - 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"))
修改说明
- 分组计算位置:将
group_by(pt)移到mutate之前,确保csum和pos是每个pt组内的独立计算值,文本位置准确对应饼图扇区。 - 简化coord_polar:去掉自定义的
cp对象,直接使用coord_polar(theta = "y"),配合scales="free"实现各分面饼图的独立尺度。 - 排序优化:添加
arrange(desc(opioid))保证每个组内的扇区顺序一致,避免同一opioid在不同分面位置混乱。
内容的提问来源于stack exchange,提问作者Li Ma
相关产品推荐
相关产品推荐

