ggplot, facet, piechart:将文本放在饼图切片的中间
Posted
技术标签:
【中文标题】ggplot, facet, piechart:将文本放在饼图切片的中间【英文标题】:ggplot, facet, piechart: placing text in the middle of pie chart slices 【发布时间】:2013-04-17 13:02:47 【问题描述】:我正在尝试使用 ggplot 生成多面饼图,但在将文本放置在每个切片的中间时遇到了问题:
dat = read.table(text = "Channel Volume Cnt
AGENT high 8344
AGENT medium 5448
AGENT low 23823
KiosK high 19275
KIOSK medium 13554
KIOSK low 38293", header=TRUE)
vis = ggplot(data=dat, aes(x=factor(1), y=Cnt, fill=Volume)) +
geom_bar(stat="identity", position="fill") +
coord_polar(theta="y") +
facet_grid(Channel~.) +
geom_text(aes(x=factor(1), y=Cnt, label=Cnt, ymax=Cnt),
position=position_fill(width=1))
输出:
应该调整geom_text
的哪些参数,以便将数字标签放置在饼图切片的中间?
相关问题是 Pie plot getting its text on top of each other,但它不处理带有 facet 的大小写。
更新:按照上面问题中的 Paul Hiemstra 建议和方法,我将代码更改如下:
---> pie_text = dat$Cnt/2 + c(0,cumsum(dat$Cnt)[-length(dat$Cnt)])
vis = ggplot(data=dat, aes(x=factor(1), y=Cnt, fill=Volume)) +
geom_bar(stat="identity", position="fill") +
coord_polar(theta="y") +
facet_grid(Channel~.) +
geom_text(aes(x=factor(1),
---> y=pie_text,
label=Cnt, ymax=Cnt), position=position_fill(width=1))
正如我所料,调整文本坐标是绝对的,但它需要在构面数据内:
【问题讨论】:
我对这个问题的最新解决方案是尽可能避免使用饼图 :-) 【参考方案1】:我想反对在 ggplot2 中制作馅饼的传统方法,即在极坐标中绘制堆叠的条形图。虽然我很欣赏这种方法在数学上的优雅,但当情节看起来不像它应该的样子时,它确实会引起各种头痛。特别是,精确调整饼图的大小可能很困难。 (如果你不明白我的意思,试着制作一个一直延伸到绘图面板边缘的饼图。)
我更喜欢使用来自 ggforce 的 geom_arc_bar()
在正常的笛卡尔坐标系中绘制饼图。它需要在前端做一些额外的工作,因为我们必须自己计算角度,但这很容易,而且我们得到的控制水平非常值得。
我在之前的答案here 和here. 中使用过这种方法
数据(来自问题):
dat = read.table(text = "Channel Volume Cnt
AGENT high 8344
AGENT medium 5448
AGENT low 23823
KIOSK high 19275
KIOSK medium 13554
KIOSK low 38293", header=TRUE)
饼图绘制代码:
library(ggplot2)
library(ggforce)
library(dplyr)
# calculate the start and end angles for each pie
dat_pies <- left_join(dat,
dat %>%
group_by(Channel) %>%
summarize(Cnt_total = sum(Cnt))) %>%
group_by(Channel) %>%
mutate(end_angle = 2*pi*cumsum(Cnt)/Cnt_total, # ending angle for each pie slice
start_angle = lag(end_angle, default = 0), # starting angle for each pie slice
mid_angle = 0.5*(start_angle + end_angle)) # middle of each pie slice, for the text label
rpie = 1 # pie radius
rlabel = 0.6 * rpie # radius of the labels; a number slightly larger than 0.5 seems to work better,
# but 0.5 would place it exactly in the middle as the question asks for.
# draw the pies
ggplot(dat_pies) +
geom_arc_bar(aes(x0 = 0, y0 = 0, r0 = 0, r = rpie,
start = start_angle, end = end_angle, fill = Volume)) +
geom_text(aes(x = rlabel*sin(mid_angle), y = rlabel*cos(mid_angle), label = Cnt),
hjust = 0.5, vjust = 0.5) +
coord_fixed() +
scale_x_continuous(limits = c(-1, 1), name = "", breaks = NULL, labels = NULL) +
scale_y_continuous(limits = c(-1, 1), name = "", breaks = NULL, labels = NULL) +
facet_grid(Channel~.)
为了说明为什么我认为这种方法比传统的 (coord_polar()
) 方法强大得多,假设我们希望将标签放在饼的外面而不是里面。这会产生一些问题,例如我们必须根据标签落在馅饼的一侧来调整hjust
和vjust
,而且我们还必须使
绘图面板比高宽,以便为侧面的标签腾出空间,而不会在上方和下方产生过多的空间。在极坐标方法中解决这些问题并不好玩,但在笛卡尔坐标中却很简单:
# generate hjust and vjust settings depending on the quadrant into which each
# label falls
dat_pies <- mutate(dat_pies,
hjust = ifelse(mid_angle>pi, 1, 0),
vjust = ifelse(mid_angle<pi/2 | mid_angle>3*pi/2, 0, 1))
rlabel = 1.05 * rpie # now we place labels outside of the pies
ggplot(dat_pies) +
geom_arc_bar(aes(x0 = 0, y0 = 0, r0 = 0, r = rpie,
start = start_angle, end = end_angle, fill = Volume)) +
geom_text(aes(x = rlabel*sin(mid_angle), y = rlabel*cos(mid_angle), label = Cnt,
hjust = hjust, vjust = vjust)) +
coord_fixed() +
scale_x_continuous(limits = c(-1.5, 1.4), name = "", breaks = NULL, labels = NULL) +
scale_y_continuous(limits = c(-1, 1), name = "", breaks = NULL, labels = NULL) +
facet_grid(Channel~.)
【讨论】:
【参考方案2】:新答案:随着 ggplot2 v2.2.0 的引入,position_stack()
可用于定位标签,而无需先计算位置变量。以下代码将为您提供与旧答案相同的结果:
ggplot(data = dat, aes(x = "", y = Cnt, fill = Volume)) +
geom_bar(stat = "identity") +
geom_text(aes(label = Cnt), position = position_stack(vjust = 0.5)) +
coord_polar(theta = "y") +
facet_grid(Channel ~ ., scales = "free")
要删除“空心”中心,请将代码修改为:
ggplot(data = dat, aes(x = 0, y = Cnt, fill = Volume)) +
geom_bar(stat = "identity") +
geom_text(aes(label = Cnt), position = position_stack(vjust = 0.5)) +
scale_x_continuous(expand = c(0,0)) +
coord_polar(theta = "y") +
facet_grid(Channel ~ ., scales = "free")
老答案: 这个问题的解决方案是创建一个位置变量,这可以很容易地使用基 R 或 data.table、plyr 或 dplyr 包完成:
第 1 步:为每个频道创建位置变量
# with base R
dat$pos <- with(dat, ave(Cnt, Channel, FUN = function(x) cumsum(x) - 0.5*x))
# with the data.table package
library(data.table)
setDT(dat)
dat <- dat[, pos:=cumsum(Cnt)-0.5*Cnt, by="Channel"]
# with the plyr package
library(plyr)
dat <- ddply(dat, .(Channel), transform, pos=cumsum(Cnt)-0.5*Cnt)
# with the dplyr package
library(dplyr)
dat <- dat %>% group_by(Channel) %>% mutate(pos=cumsum(Cnt)-0.5*Cnt)
第 2 步:创建多面图
library(ggplot2)
ggplot(data = dat) +
geom_bar(aes(x = "", y = Cnt, fill = Volume), stat = "identity") +
geom_text(aes(x = "", y = pos, label = Cnt)) +
coord_polar(theta = "y") +
facet_grid(Channel ~ ., scales = "free")
结果:
【讨论】:
感谢您的最新帮助。有没有办法摆脱“空心”中心? (中间的小白圈) @jesusgarciab 可能有点晚了,但我已经更新了答案【参考方案3】:以下答案是片面的、笨拙的,我不会接受。 希望它会寻求更好的解决方案。
text_KIOSK = dat$Cnt
text_AGENT = dat$Cnt
text_KIOSK[dat$Channel=='AGENT'] = 0
text_AGENT[dat$Channel=='KIOSK'] = 0
text_KIOSK = text_KIOSK/1.7 + c(0,cumsum(text_KIOSK)[-length(dat$Cnt)])
text_AGENT = text_AGENT/1.7 + c(0,cumsum(text_AGENT)[-length(dat$Cnt)])
text_KIOSK[dat$Channel=='AGENT'] = 0
text_AGENT[dat$Channel=='KIOSK'] = 0
pie_text = text_KIOSK + text_AGENT
vis = ggplot(data=dat, aes(x=factor(1), y=Cnt, fill=Volume)) +
geom_bar(stat="identity", position=position_fill(width=1)) +
coord_polar(theta="y") +
facet_grid(Channel~.) +
geom_text(aes(y=pie_text, label=format(Cnt,format="d",big.mark=','), ymax=Inf), position=position_fill(width=1))
它产生以下图表:
正如您所注意到的,我无法移动绿色(低)的标签。
【讨论】:
你看到我的回答了吗?我认为它提供了您所要求的解决方案。 好的,谢谢。我会尽可能坚持“无饼图”规则,但对于例外情况,您的答案就是答案:-) 尽可能避免使用饼图是绝对正确的,但有些人似乎真的很喜欢它们。大多数情况下,结构良好的条形图会更清晰。【参考方案4】:要调整标签文本相对于坐标的位置,您可以使用geom_text
的vjust
和hjust
参数。这将同时确定所有标签的位置,因此这可能不是您需要的。
或者,您可以调整标签的坐标。定义一个新的 data.frame
,在其中平均 Cnt
坐标 (label_x[i] = Cnt[i+1] + Cnt[i]
) 以将标签放置在该特定饼图的中心。只需将这个新的data.frame
传递给geom_text
以替换原来的data.frame
。
此外,饼图有一些视觉解释缺陷。一般来说,我不会使用它们,尤其是在存在好的替代品的情况下,例如一个点图:
ggplot(dat, aes(x = Cnt, y = Volume)) +
geom_point() +
facet_wrap(~ Channel, ncol = 1)
例如,从该图中可以明显看出,Kiosk 的 Cnt
高于代理,此信息在饼图中丢失。
【讨论】:
感谢您的回答 - 我会试一试。但我不同意提议的情节是饼图的替代品。在我看来,Cnt 较高的事实主要由于未对齐的文本标签而丢失。 饼图无法显示 AGENT 和 KIOSK 之间的大小差异,因为它只显示相对大小。在我看来,文本更难解释。当比较 5 个类别时,这种效果会变得更大。像amazon.com/Elements-Graphing-Data-William-Cleveland/dp/… 这样的书籍提倡使用点图等图表来支持饼图。只需谷歌pie chart evil
。
不幸的是,这个答案并没有解决刻面的问题,因为长度需要跨刻面变量对齐。
我不太确定你的意思。如果您需要构面之间的相对大小,只需在对facet_wrap
的调用中使用scale = free_x
。
我不使用 fact_wrap - 我使用 facet_grid (在这种情况下是无关的,因为 scales 适用于两者)。 scales 如何帮助对齐文本标签?我的意思是 pie_text 数字应该用相应的方面(变量 Channel)计算。以上是关于ggplot, facet, piechart:将文本放在饼图切片的中间的主要内容,如果未能解决你的问题,请参考以下文章
R语言ggplot2可视化将图例移动到分面图(facet)中的空白分面区域实战