Создание диаграммы Санки с использованием пакета NetworkD3 в R

В настоящее время я пытаюсь создать интерактивный Sankey с пакетом networkD3, следуя инструкциям Криса Грандруда (https://christophergandrud.github.io/networkD3/).
Чего я не понимаю, так это табличного формата, поскольку он просто использует два столбца для визуализации большего количества переходов. Чтобы быть более конкретным, у меня есть набор данных, содержащий четыре столбца, которые представляют 4 года. Внутри этих столбцов указаны разные названия отелей, а каждая строка представляет одного клиента, которого «отслеживают» в течение этих четырех лет.

    URL <- paste0(
        "https://cdn.rawgit.com/christophergandrud/networkD3/",
        "master/JSONdata/energy.json")
    Energy <- jsonlite::fromJSON(URL)

    sankeyNetwork(Links = Energy$links, Nodes = Energy$nodes, Source = "source",
         Target = "target", Value = "value", NodeID = "name",
         units = "TWh", fontSize = 12, nodeWidth = 30)

Чтобы дать вам обзор моих данных, вот скриншот:

SampleDataScreenshot

Я бы дал вам больше «закодированной» информации, но поскольку я новичок в теме R, я надеюсь, что вы сможете проследить мой ход мыслей по этой проблеме. Если нет, не стесняйтесь сомневаться в этом.

Спасибо :)


person Phipsy    schedule 23.05.2017    source источник
comment
сделайте минимальный воспроизводимый пример, чтобы вам было легче помочь   -  person CJ Yetman    schedule 23.05.2017


Ответы (2)


вам понадобятся два фрейма данных: в одном перечислены все узлы (содержащие имена), а в другом - ссылки. Последний содержит три столбца: исходный узел, целевой узел и некоторое значение, указывающее силу или ширину ссылки. В кадре данных ссылок вы ссылаетесь на узлы по позиции (отсчитываемой от нуля) в кадре данных узлов.

Предполагая, что ваши данные выглядят так:

df <- data.frame(Year1=sample(paste0("Hotel", 1:4), 1000, replace = TRUE),
                 Year2=sample(paste0("Hotel", 1:4), 1000, replace = TRUE),
                 Year3=sample(paste0("Hotel", 1:4), 1000, replace = TRUE),
                 Year4=sample(paste0("Hotel", 1:4), 1000, replace = TRUE),
                 stringsAsFactors = FALSE)

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

df$Year1 <- paste0("Year1_", df$Year1)
df$Year2 <- paste0("Year2_", df$Year2)
df$Year3 <- paste0("Year3_", df$Year3)
df$Year4 <- paste0("Year4_", df$Year4)

ссылки - это «переходы» между отелями из года в год:

library(dplyr)
trans1_2 <- df %>% group_by(Year1, Year2) %>% summarise(sum=n())
trans2_3 <- df %>% group_by(Year2, Year3) %>% summarise(sum=n())
trans3_4 <- df %>% group_by(Year3, Year4) %>% summarise(sum=n())

colnames(trans1_2)[1:2] <- colnames(trans2_3)[1:2] <- colnames(trans3_4)[1:2] <- c("source","target")

links <- rbind(as.data.frame(trans1_2), 
               as.data.frame(trans2_3), 
               as.data.frame(trans3_4))

наконец, фреймы данных должны быть связаны друг с другом:

nodes <- data.frame(name=unique(c(links$source, links$target)))
links$source <- match(links$source, nodes$name) - 1
links$target <- match(links$target, nodes$name) - 1

Тогда схему можно нарисовать:

library(networkD3)
sankeyNetwork(Links = links, Nodes = nodes, Source = "source",
              Target = "target", Value = "sum", NodeID = "name",
              fontSize = 12, nodeWidth = 30)

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

person scheddy    schedule 26.05.2017
comment
Спасибо за ответ, это было действительно полезно для меня, когда я пытался визуализировать пути пользователя. Я изо всех сил пытаюсь удалить Год (StepX в моем случае) после создания фреймов данных и как визуализировать пустые случаи (когда пользователи покидают веб-сайт). Есть ли у вас какие-либо предложения? - person maggieto; 19.09.2017
comment
вы можете просто изменить уровни nodes$name, например - person scheddy; 20.09.2017
comment
вы можете просто изменить уровни nodes$name, например пользователя nodes$name <- sub("Year[1-4]_", "", levels(nodes$name)). Для левых пользователей вы можете подумать о добавлении нового узла на каждом шаге, который растет от шага к шагу. Используя параметр LinkGroup, вы можете отличить ссылки на выходящий узел от других ссылок. - person scheddy; 20.09.2017
comment
Привет, если я хочу реализовать onClick при щелчке по этим строкам, вы можете помочь мне с этой проблемой здесь. - person Ashmin Kaul; 11.10.2017
comment
К сожалению, нет. Может быть, это стоит нового вопроса. - person scheddy; 12.10.2017

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

начиная с примера набора данных ...

df <- read.csv(header = TRUE, as.is = TRUE, text = '
name,year1,year2,year3,year4
Bob,Hilton,Sheraton,Westin,Hyatt
John,Four Seasons,Ritz-Carlton,Westin,Sheraton
Tom,Ritz-Carlton,Westin,Sheraton,Hyatt
Mary,Westin,Sheraton,Four Seasons,Ritz-Carlton
Sue,Hyatt,Ritz-Carlton,Hilton,Sheraton
Barb,Hilton,Sheraton,Ritz-Carlton,Four Seasons
')

#   name        year1        year2        year3        year4
# 1  Bob       Hilton     Sheraton       Westin        Hyatt
# 2 John Four Seasons Ritz-Carlton       Westin     Sheraton
# 3  Tom Ritz-Carlton       Westin     Sheraton        Hyatt
# 4 Mary       Westin     Sheraton Four Seasons Ritz-Carlton
# 5  Sue        Hyatt Ritz-Carlton       Hilton     Sheraton
# 6 Barb       Hilton     Sheraton Ritz-Carlton Four Seasons

  1. создайте номер строки, чтобы вы по-прежнему могли определять, из какой строки / наблюдения пришла каждая отдельная ссылка, когда вы конвертируете данные в длинный формат
  2. используйте функцию gather() tidyr для преобразования набора данных в длинный формат
  3. преобразовать переменную имени столбца в индекс / номер столбца в исходном наборе данных
  4. сгруппированные по строкам (каждое наблюдение в исходном наборе данных), упорядочить каждый узел по столбцу, в котором он находился, и создать переменную для его «цели», установив ее на узел из столбца после него
  5. отфильтровать любые строки, которые имеют NA для «цели» (узлы в последнем столбце исходного набора данных не будут иметь «цель», и поэтому в этих строках не указывается ссылка)

library(dplyr)
library(tidyr)

links <-
  df %>%
  mutate(row = row_number()) %>%
  gather('column', 'source', -row) %>%
  mutate(column = match(column, names(df))) %>%
  group_by(row) %>%
  arrange(column) %>%
  mutate(target = lead(source)) %>%
  ungroup() %>%
  filter(!is.na(target))

# # A tibble: 24 x 4
#      row column source       target
#    <int>  <int> <chr>        <chr>
#  1     1      1 Bob          Hilton
#  2     2      1 John         Four Seasons
#  3     3      1 Tom          Ritz-Carlton
#  4     4      1 Mary         Westin
#  5     5      1 Sue          Hyatt
#  6     6      1 Barb         Hilton
#  7     1      2 Hilton       Sheraton
#  8     2      2 Four Seasons Ritz-Carlton
#  9     3      2 Ritz-Carlton Westin
# 10     4      2 Westin       Sheraton
# # ... with 14 more rows

Теперь данные уже находятся в типичном формате сетевых данных - одна ссылка на строку, определенную столбцами «источник» и «цель», и их можно использовать с sankeyNetwork(). Однако вы, вероятно, захотите, чтобы узлы, относящиеся к одному и тому же объекту, появлялись несколько раз в вашем графике ... если кто-то посетил Hilton в год 1, а затем снова посетил Hilton в год 3, вам, вероятно, понадобятся 2 отдельных узла, оба названные Хилтон, но появляясь в разных частях сюжета. Для этого вам нужно будет указать каждый узел в столбцах «источник» и «цель» с указанием года, в котором они были посещены. Вот где пригодятся переменные "строка" и "столбец".

Добавьте индекс столбца к имени «источника» и добавьте индекс столбца + 1 к имени «целевого», и теперь вы сможете различать, например, узел для Hilton, который был посещен в год 1, и узел для Hilton, который был посещен в течение 3-го года

links <-
  links %>%
  mutate(source = paste0(source, '_', column)) %>%
  mutate(target = paste0(target, '_', column + 1)) %>%
  select(source, target)

# # A tibble: 24 x 2
#    source         target
#    <chr>          <chr>
#  1 Bob_1          Hilton_2
#  2 John_1         Four Seasons_2
#  3 Tom_1          Ritz-Carlton_2
#  4 Mary_1         Westin_2
#  5 Sue_1          Hyatt_2
#  6 Barb_1         Hilton_2
#  7 Hilton_2       Sheraton_3
#  8 Four Seasons_2 Ritz-Carlton_3
#  9 Ritz-Carlton_2 Westin_3
# 10 Westin_2       Sheraton_3
# # ... with 14 more rows

Теперь вы можете следовать довольно стандартной процедуре использования списка ссылок источник-цель для создания необходимых фреймов данных для sankeyNetwork(). Создайте nodes фрейм данных со всеми уникальными узлами, найденными в "исходном" и "целевом" векторах. Преобразуйте "исходный" и "целевой" векторы в кадре данных links в индекс узла в кадре данных nodes с отсчетом от 0. Добавьте произвольное значение для каждой ссылки во фрейме данных links, поскольку это требуется sankeyNetwork(). Теперь вы можете удалить добавленный индекс столбца из имен узлов в nodes фрейме данных, потому что они будут использоваться только для маркировки узлов в результирующем графике (поэтому больше не имеет значения, являются ли они уникальными). Затем начертите это с помощью sankeyNetwork()!

nodes <- data.frame(name = unique(c(links$source, links$target)))

links$source <- match(links$source, nodes$name) - 1
links$target <- match(links$target, nodes$name) - 1
links$value <- 1

nodes$name <- sub('_[0-9]+$', '', nodes$name)

library(networkD3)
library(htmlwidgets)

sankeyNetwork(Links = links, Nodes = nodes, Source = 'source',
              Target = 'target', Value = 'value', NodeID = 'name')

введите описание изображения здесь

person CJ Yetman    schedule 08.09.2018