Многократная диаграмма солнечных лучей / пончиков на зависимом уровне с использованием ggplot2 - PullRequest
0 голосов
/ 24 апреля 2018

Я пытаюсь создать двухуровневую диаграмму солнечных лучей / пончика (для печати), где второй уровень представляет собой детальный вид первого. Я прочитал и понял этот урок , но я новичок в R и ggplot2, и у меня возникают проблемы при создании второго уровня. В вышеупомянутой статье уровень корня имеет только один элемент (который немного избыточен), тогда как мой корень имеет много элементов; из которых вторичный уровень имеет не менее 1 и не более 10 элементов.

Допустим, мои данные имеют три столбца: name, type и value; где name и type определяют элементы корневого и второго уровня соответственно. Каждый name имеет ровно один type из all, который является суммой value s через type s (из которых есть по крайней мере один и через name s наборы type может пересекаться или быть взаимоисключающим). Например:

name  type    value
----- ------- ------
foo   all     444
foo   type1   123
foo   type2   321
bar   all     111
bar   type3   111
baz   all     999
baz   type1   456
baz   type3   543

Я могу создать стек корневого уровня (до преобразования в полярные координаты), используя:

data.all <- data[data$type == "all",]
ggplot(data.all, aes(x=1, y=data.all$value, fill=data.all$name)) + geom_bar(stat="identity")

Что мне нужно для стека второго уровня, так это чтобы значения type были выровнены в пределах значений name, пропорциональных их значению:

 +-----+  +-------+
 |     |  | type3 |
 | baz |  +-------+
 |     |  | type1 |
 +-----+  +-------+
 |     |  |       |
 | bar |  | type3 |
 |     |  |       |
 +-----+  +-------+
 |     |  | type2 |
 | foo |  +-------+
 |     |  | type1 |
-+-----+--+-------+-

(нет, это явно не в масштабе!)

Мне также необходимо, чтобы значения type были последовательно окрашены (например, цвет блока type1 должен быть одинаковым для foo и baz и т. Д.)

Я думал, что смогу сделать это, объединив столбцы name и type в новый столбец, а затем раскрасив следующим образом:

data.other <- data[data$type != "other",]
data.other$comb <- paste(data.other$name, data.other$type, sep=":")
ggplot(data.other, aes(x=2, y=data.other$value, fill=data.other$comb)) + geom_bar(stat="identity")

Однако это нарушает согласованность окраски - очевидно, задним числом - и, как ни странно, я абсолютно не верю, что выравнивание будет правильным.

Мой род R / ggplot2, вероятно, довольно очевиден (извините!); как мне достичь того, что я ищу?


РЕДАКТИРОВАТЬ Я также сталкивался с этим вопросом и ответом , однако мои данные выглядят иначе, чем их. Если мои данные можно объединить в одну и ту же форму - что я не знаю, как сделать, - тогда мой вопрос станет их частным случаем.

Ответы [ 4 ]

0 голосов
/ 18 января 2019

Я искал способ сделать этот тип графика, используя ggplot. Ответ @camille был действительно полезным! Я также использовал этот ответ здесь , чтобы создать слегка измененный ответ на этот вопрос.

Прошел почти год, но, возможно, кто-то еще ищет этот тип ответа! Возможно, другие пакеты, упомянутые в других ответах, более полезны, но для тех из нас, кто хочет остаться в ggplot, надеюсь, это поможет.

Думаю, я мог бы сделать то, о чем просил ОП (последовательно окрашивая второй уровень), хотя я не уверен, что это оптимальный путь.

Вместо использования geom_col я использовал geom_rect. Это дает нам больше гибкости, а также больше контроля над тем, где рисуется каждый прямоугольник (у сгруппированных баров всегда есть проблема с блоками порядка). Также, как ни странно, в полярных координатах geom_col заканчивается рисованием всех пирогов от 0 до x. Поэтому @camille пришлось поиграться с прозрачными пленками, чтобы получить желаемый результат. В geom_rect мы можем установить xmin и xmax, чтобы получить нужную форму.

Но нам нужно кое-что обработать, чтобы привести данные в форму.

Кроме того, на сюжете, который я пытаюсь сделать, некоторые вторые уровни пусты. Поэтому я немного изменил набор данных, чтобы включить один дополнительный класс первого уровня без класса второго уровня.

Вот мое решение:

library(tidyverse)
library(ggplot2)
library(RColorBrewer)

df <- "name  type    value
foo   all     444
foo   type1   123
foo   type2   321
bar   all     111
bar   type3   111
baz   all     999
baz   type1   456
baz   type3   543
boz   -       222" %>% read_table2() %>% filter(type != 'all') %>% 
mutate(type=ifelse(type=='-', NA, type)) %>% arrange(name, value)

# here I create the columns xmin, xmax, ymin, ymax using cumsum function
# (be VERY careful with ordering of rows!)

# I also created a column 'colour' which I map to the asthetic 'colour' (colour of line of each rectangle)
# it is a boolean saying if a line should or should not be drawn.
# for empty second levels i want to draw an empty space (no fill and no line)

# define a padding space between the levels of the pie chart 
padding <- 0.05

# create df for level 0
lvl0 <- tibble(name = "Parent", value = 0, level = 0, fill = NA) %>%
  mutate(xmin=0, xmax=1, ymin=0, ymax=value) %>%
  mutate(x.avg=0, y.avg=0, colour=FALSE)

print(lvl0)

# create df for level 1
lvl1 <- df %>%
  group_by(name) %>%
  summarise(value = sum(value)) %>%
  ungroup() %>%
  mutate(level = 1) %>%
  mutate(fill = name) %>%
  mutate(xmin=1+padding, xmax=2, ymin=0, ymax=cumsum(value)) %>%
  mutate(ymin=lag(ymax, default=0),
         x.avg=(xmin+xmax)/2,
         y.avg=(ymin+ymax)/2,
         colour=TRUE)

print(lvl1)

# create df for level 2
lvl2 <- df %>%
  select(name = type, value, fill = name) %>%
  mutate(level = 2) %>%
  mutate(fill=paste0(fill, '_', name)) %>%
  mutate(xmin=2+padding, xmax=3, ymin=0, ymax=cumsum(value)) %>%
  mutate(ymin=lag(ymax, default=0),
         x.avg=(xmin+xmax)/2,
         y.avg=(ymin+ymax)/2,
         colour=ifelse(grepl('_NA', fill), FALSE, TRUE))

print(lvl2)

# this is my dirty workaround for defining the colours of levels 1 one 2 independently. Probably not the best way and 
# maybe it will not scale very well... But for this small data set it seemed to work...

# number of classes in each level (don't include NA)
n.classes.1 <- 4
n.classes.2 <- 3
n.classes.total <- n.classes.1 + n.classes.2

# get colour pallete for level 1
col.lvl1 <- brewer.pal(n.classes.total,"Dark2")[1:n.classes.1]
names(col.lvl1) <- as.character(unique(lvl1$name))

# get colour pallete for level 2 (don't include NA)
col.lvl2 <- brewer.pal(n.classes.total,"Dark2")[(n.classes.1+1):n.classes.total]
names(col.lvl2) <- as.character(unique(lvl2$name)[!is.na(unique(lvl2$name))])

# compile complete color pallete
fill.pallete <- c(col.lvl1)

for (l1 in as.character(unique(lvl1$name))) {
  for (l2 in as.character(unique(lvl2$name))) {
    if (!is.na(l2)) {
        name.type <- paste0(l1, '_', l2)
        aux <- col.lvl2[l2]
        names(aux) <- name.type
        fill.pallete <- c(fill.pallete, aux)        
    } else {
        # if level2 is NA, then assign transparent colour
        name.type <- paste0(l1, '_NA')
        aux <- NA
        names(aux) <- name.type
        fill.pallete <- c(fill.pallete, aux)        
    }
  }
}
print(fill.pallete)


# put all data frames together for ggplot

df.total <- bind_rows(lvl0, lvl1, lvl2) %>%
  mutate(name = as.factor(name) %>% fct_reorder2(fill, value)) %>%
  arrange(fill, name) %>%
  mutate(level = as.factor(level))

print(df.total)

# create plot (it helped me to look at the rectangular coordinates first before changing to polar!)

g <- ggplot(data=df.total, aes(fill = fill)) +
  geom_rect(aes(ymax=ymax, ymin=ymin, xmax=xmax, xmin=xmin, colour=colour), size = 0.1) +
  scale_fill_manual(values = fill.pallete, , guide = F, na.translate = FALSE) +
  scale_color_manual(values = c('TRUE'='gray20', 'FALSE'='#FFFFFF00'), 
                     guide = F, na.translate = FALSE) +
  geom_text(aes(x = x.avg, y = y.avg, label = name), size = rel(2.5)) +
  scale_x_discrete(breaks = NULL) +
  scale_y_continuous(breaks = NULL) +
  labs(x = NULL, y = NULL) +
  theme_minimal() +
  theme(panel.grid=element_blank()) + 
  coord_polar(theta = "y", start = 0, direction = -1)

print(g)

Это результирующий сюжет .

0 голосов
/ 24 апреля 2018

Это может быть только частично, и это может плохо масштабироваться до гораздо более сложного набора данных. Мне стало очень любопытно, как это сделать, и у меня был такой же большой набор данных, который я пытаюсь визуализировать для работы, так что это фактически помогает мне справляться с моей работой:)

По сути, я разбил набор данных на фреймы данных для трех уровней: родительский уровень, который в основном является фиктивными данными, уровень 1 df с суммами всех типов под каждым именем (полагаю, я мог бы просто отфильтровать ваши данные для type == "all" - у меня не было аналогичного столбца для моих рабочих данных), а уровень 2 - это все внешние узлы. Свяжите их все вместе, составьте столбчатую диаграмму с накоплением, дайте ей полярные координаты.

У того, что я сделал для работы, было намного больше ярлыков, и они были довольно длинными, поэтому вместо них я использовал ggrepel::geom_text_repel. Они быстро стали громоздкими и безобразными.

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

library(tidyverse)

df <- "name  type    value
foo   all     444
foo   type1   123
foo   type2   321
bar   all     111
bar   type3   111
baz   all     999
baz   type1   456
baz   type3   543" %>% read_table2() %>%
    filter(type != "all") %>%
    mutate(name = as.factor(name) %>% fct_reorder(value, sum)) %>%
    arrange(name, value) %>%
    mutate(type = as.factor(type) %>% fct_reorder2(name, value))

lvl0 <- tibble(name = "Parent", value = 0, level = 0, fill = NA)

lvl1 <- df %>%
    group_by(name) %>%
    summarise(value = sum(value)) %>%
    ungroup() %>%
    mutate(level = 1) %>%
    mutate(fill = name)

lvl2 <- df %>%
    select(name = type, value, fill = name) %>%
    mutate(level = 2)


bind_rows(lvl0, lvl1, lvl2) %>%
    mutate(name = as.factor(name) %>% fct_reorder2(fill, value)) %>%
    arrange(fill, name) %>%
    mutate(level = as.factor(level)) %>%
    ggplot(aes(x = level, y = value, fill = fill, alpha = level)) +
        geom_col(width = 1, color = "gray90", size = 0.25, position = position_stack()) +
        geom_text(aes(label = name), size = 2.5, position = position_stack(vjust = 0.5)) +
        coord_polar(theta = "y") +
        scale_alpha_manual(values = c("0" = 0, "1" = 1, "2" = 0.7), guide = F) +
        scale_x_discrete(breaks = NULL) +
        scale_y_continuous(breaks = NULL) +
        scale_fill_brewer(palette = "Dark2", na.translate = F) +
        labs(x = NULL, y = NULL) +
        theme_minimal()

Создано в 2018-04-24 пакетом представ. (v0.2.0).

0 голосов
/ 05 мая 2018

Это можно сделать с помощью ggsunburst (как предложено Камиллой). ggsunburst читает файлы newick и csv (или любые разделенные разделителями). Вам нужно будет установить последнюю версию 0.0.9, чтобы этот пример работал

# first row with header is mandatory
# remove lines with type "all" from your data
# add colour as additional column
df <- read.table(header=T, text =
"parent node  size  colour
foo   type1   123 type1
foo   type2   321 type2
bar   type3   111 type3
baz   type1   456 type1
baz   type3   543 type3")

# write data.frame into csv file
write.table(df, file = 'df.csv', row.names = F, sep = ",")

# install ggsunburst 0.0.9
if (!require("ggplot2")) install.packages("ggplot2")
if (!require("rPython")) install.packages("rPython")
install.packages("http://genome.crg.es/~didac/ggsunburst/ggsunburst_0.0.9.tar.gz", repos=NULL, type="source")


library(ggsunburst)

sb <- sunburst_data('df.csv', type = "node_parent", sep = ',', node_attributes = 'colour')
sunburst(sb, rects.fill.aes = "colour", node_labels = T, node_labels.min = 25)

смотрите здесь ваши солнечные лучи

0 голосов
/ 24 апреля 2018

На основании рекомендованной вами веб-страницы попробуйте следующее:

library(ggplot2) 
library(dplyr) 
library(scales) 

toRead <- "name  type    value
foo   all     444
foo   type1   123
foo   type2   321
bar   all     111
bar   type3   111
baz   all     999
baz   type1   456
baz   type3   543"

data <- read.table(textConnection(toRead), header = TRUE)
closeAllConnections()



sum_total_value = sum(data$value)

firstLevel = data %>% summarize(total_value=sum(value))

sunburst_0 = ggplot(firstLevel) # Just a foundation
sunburst_1 = 
  sunburst_0 + 
  geom_bar(data=firstLevel, aes(x=1, y=total_value), fill='darkgrey', stat='identity') +
  geom_text(aes(x=1, y=sum_total_value/2, label=paste('Sum of all VALUE had', comma(total_value))), color='white')

sunburst_1
sunburst_1 + coord_polar('y')


sum_val = data %>% group_by(type) %>%
  summarize(total_value=sum(value)) %>%
  arrange(desc(total_value))


sunburst_2 <- sunburst_1 +
  geom_bar(data=sum_val,
           aes(x=2, y=total_value, fill=total_value),
           color='white', position='stack', stat='identity', size=0.6) + 
  geom_text(data=sum_val, aes(label=paste(type, total_value), x=2, y=total_value), position='stack')

sunburst_2

Это дает следующий сюжет: enter image description here

Если вы хотите это для полярных координат, вы можете добавить следующее:

sunburst_2 + coord_polar('y')

Что дает вам:

enter image description here

...