ggplot, фасет, piechart: размещение текста в середине срезов круговой диаграммы

Я пытаюсь создать фасеточную круговую диаграмму с 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))

выход: enter image description here

какие параметры geom_text следует настроить, чтобы разместить числовые метки в середине ломтиков piechart?

вопрос пирог сюжет получает свой текст друг на друга но он не обрабатывает случай с фацетом.

обновление: после Павла Совет и подход 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))

как я и ожидал, настройка текста coordiantes является абсолютной, но она должна быть в пределах данных фасета: enter image description here

4 ответов


ОТВЕТ: введение 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 или с данные.таблица, 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") 

результат:

enter image description here


Я хотел бы высказаться против обычного способа приготовления пирогов в ggplot2, который заключается в том, чтобы нарисовать сложенный барплот в полярных координатах. Хотя я ценю математическую элегантность этого подхода, он вызывает всевозможные головные боли, когда сюжет выглядит не совсем так, как он должен. В частности, точно регулировать размер пирога может быть сложно. (Если вы не знаете, что я имею в виду, попробуйте сделать круговую диаграмму, которая простирается вплоть до края участка панель.)

Я предпочитаю рисовать пироги в нормальной декартовой системе координат, используя geom_arc_bar() от ggforce. Это требует немного дополнительной работы на переднем конце, потому что мы должны вычислить углы, но это легко и уровень контроля, который мы получаем в результате более чем стоит. Я использовал этот подход в предыдущих ответах здесь и здесь.

данные (из вопроса):

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~.)

enter image description here

чтобы показать, почему я думаю, что этот подход намного мощнее обычного (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~.)

enter image description here


чтобы настроить положение текста метки относительно координаты, вы можете использовать vjust и hjust доводы geom_text. Это определит положение всех меток одновременно, поэтому это может быть не то, что вам нужно.

кроме того, вы можете настроить координату метки. Определите новое data.frame где вы усредняете Cnt координат (label_x[i] = Cnt[i+1] + Cnt[i]), чтобы расположить метку в центре этого конкретного пирога. Просто передайте это новое data.frame до geom_text in замена оригинала data.frame.

кроме того, piecharts имеют некоторые недостатки визуальной интерпретации. В общем, я бы не использовал их, особенно там, где существуют хорошие альтернативы, например, dotplot:

ggplot(dat, aes(x = Cnt, y = Volume)) + 
  geom_point() + 
  facet_wrap(~ Channel, ncol = 1)

например, из этого сюжета очевидно, что Cnt выше для киоска чем для агента, эта информация потеряна в piechart.

enter image description here


следующий ответ частичный, неуклюжий, и я не буду его принимать. Есть надежда, что она приведет к лучшему решению.

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))

он создает следующую диаграмму: enter image description here

Как вы заметили, я не могу перемещать метки для зеленого (низкий).