求助:ggplot2绘制哑铃图时箭头方向错误的解决方法
解决ggplot2哑铃图箭头方向反转问题
我用ggplot2制作基线(baseline)与终线(endline)指标对比的哑铃图,完成数据预处理和绘图后发现箭头方向不符合预期——期望箭头从左到右表示时间流逝(基线→终线),但实际方向相反。尝试调整数据排序无效,怀疑是geom_path的设置问题,相关数据与代码如下:
原始数据
structure(list(category = c("food security", "food security", "child survival", "child survival", "boosted incomes", "boosted incomes", "empowered churches", "empowered churches", "food security", "food security", "child survival", "child survival", "boosted incomes", "boosted incomes", "empowered churches", "empowered churches" ), metric = c("Never going to bed hungry", "Children at a healthy weight", "Poor health is not a \"serious problem\"", "No children sent to work in the city", "Low income is not a \"serious problem\"", "At least one stable income source", "Spiritual support is not a \"serious problem\"", "Women who feel hope for the future", "Never going to bed hungry", "Children at a healthy weight", "Poor health is not a \"serious problem\"", "No children sent to work in the city", "Low income is not a \"serious problem\"", "At least one stable income source", "Spiritual support is not a \"serious problem\"", "Women who feel hope for the future" ), round = c("baseline", "baseline", "baseline", "baseline", "baseline", "baseline", "baseline", "baseline", "endline", "endline", "endline", "endline", "endline", "endline", "endline", "endline" ), percentage = c(0.53, 0.79, 0.8, 0.8, 0.75, 0.72, 0.75, 0.53, 0.98, 0.93, 0.95, 0.96, 0.93, 0.94, 0.97, 0.84)), class = c("tbl_df", "tbl", "data.frame"), row.names = c(NA, -16L))
数据预处理代码
mydata <- mydata %>% mutate(bump_percentage = if_else(round == "baseline", percentage - .03, percentage + .03)) %>% mutate(bump_percentage = bump_percentage*100, percentage = percentage*100) %>% arrange(category, round) %>% mutate(category = factor(category, c("food security", "child survival", "boosted incomes", "empowered churches"))) midpoints <- mydata %>% group_by(metric) %>% summarise(midpoint = mean(percentage)) mydata <- mydata %>% inner_join(midpoints, by = "metric")
原始绘图代码
ggplot(mydata, aes(x=percentage, y=metric, color=category)) + geom_line(color="gray", size=1.75) + geom_path(aes(x = midpoint), arrow = arrow(angle = 30, length = unit(.15, "inches"), type = "closed"), color = "gray", ) + geom_point(size=2) + scale_color_viridis(discrete = T) + geom_text(aes(label=glue("{percentage}%"), x=bump_percentage), color = "dimgray", size=3) + theme_fivethirtyeight() + facet_grid(category ~ ., scales = "free_y", switch = "y") + theme(legend.title=element_blank(), axis.text.x = element_text(color = "dimgray"), panel.grid.major.x = element_line(color="gray", size=.2), panel.grid.major.y = element_line(color="gray", size=.2, linetype = "dashed"), strip.placement = "outside", panel.spacing.y = unit(0, "mm"), strip.text = element_blank(), legend.justification = 1) + scale_x_continuous(labels=glue("{seq(50,100,10)}%"))
解决方案
问题根源
原始代码中用geom_path(aes(x = midpoint))绘制箭头的逻辑错误:每个指标对应的两行数据(基线+终线)的midpoint是同一个数值,这条“路径”实际是在同一x位置重复绘制,箭头方向仅由数据行顺序决定,无法实现从基线百分比指向终线百分比的需求。
修改步骤
- 转换数据为宽格式:方便直接获取每个指标的基线和终线百分比,用于指定箭头的起点和终点。
- 替换
geom_path为geom_segment:geom_segment可以明确指定线段的起点(x=baseline)和终点(x=endline),完美适配哑铃图的箭头需求。
修改后的完整代码
数据预处理新增宽格式转换
# 基于原预处理后的数据,转换为宽格式 mydata_wide <- mydata %>% select(category, metric, round, percentage) %>% pivot_wider(names_from = round, values_from = percentage)
调整后的绘图代码
ggplot(mydata, aes(x=percentage, y=metric, color=category)) + # 保留原哑铃连接线 geom_line(color="gray", size=1.75) + # 用geom_segment绘制从基线到终线的箭头 geom_segment(data = mydata_wide, aes(x = baseline, xend = endline, y = metric, yend = metric), arrow = arrow(angle = 30, length = unit(.15, "inches"), type = "closed"), color = "gray") + geom_point(size=2) + scale_color_viridis(discrete = TRUE) + geom_text(aes(label=glue("{percentage}%"), x=bump_percentage), color = "dimgray", size=3) + theme_fivethirtyeight() + facet_grid(category ~ ., scales = "free_y", switch = "y") + theme(legend.title=element_blank(), axis.text.x = element_text(color = "dimgray"), panel.grid.major.x = element_line(color="gray", size=.2), panel.grid.major.y = element_line(color="gray", size=.2, linetype = "dashed"), strip.placement = "outside", panel.spacing.y = unit(0, "mm"), strip.text = element_blank(), legend.justification = 1) + scale_x_continuous(labels=glue("{seq(50,100,10)}%"))
额外说明
如果不想转换宽格式,也可以通过在长数据中按metric分组后手动指定起点终点,但宽格式的方式更直观易懂,便于后续维护。
内容的提问来源于stack exchange,提问作者bricevk
相关产品推荐
相关产品推荐

