8

目前,我正在尝试networkD3按照 Chris Grandrud ( https://christophergandrud.github.io/networkD3/ ) 的说明使用包创建交互式 Sankey。
我不明白的是表格格式,因为他只使用两列来可视化更多的转换。更具体地说,我有一个数据集,其中包含代表 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)

为了让您了解我的数据,这里有一个屏幕截图:

样本数据截图

我会给你更多的“编码”信息,但由于我对 RI 的主题很陌生,希望你能在这个问题上遵循我的思路。如果没有,请不要犹豫质疑它。

谢谢 :)

4

2 回答 2

12

您需要两个数据框:一个列出所有节点(包含名称),一个列出链接。后者包含三列,源节点、目标节点和一些值,表示链接的强度或宽度。在链接数据框中,您通过节点数据框中的(从零开始的)位置来引用节点。

假设您的数据如下所示:

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)

可能有更优雅的解决方案,但这可能是您的问题的起点。如果您不喜欢节点名称中的“年份...”,您可以在设置数据框后将其删除。

于 2017-05-26T20:19:57.537 回答
9

这个问题出现了很多......如何转换一个数据集,该数据集在多列的每一行上定义了多个链接/边缘。以下是我如何将其转换为sankeyNetwork(以及许多其他处理边缘/链接/网络数据的包)使用的数据集类型......每行一个边缘/链接的数据集。

从示例数据集开始...

df <- read.table(header = TRUE, stringsAsFactors = FALSE, 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. 使用tidyrpivot_longer()函数将数据集转换为长格式
  3. 将列名变量转换为原始数据集中列的索引/编号
  4. 按行分组(原始数据集中的每个观察值),通过将其设置为下一列中跟随它的节点,为每个源节点的“目标”创建一个变量
  5. 过滤掉任何具有NA“目标”的行(原始数据集最后一列中的节点将没有“目标”,因此这些行没有指定链接)

library(dplyr)
library(tidyr)

links <-
  df %>%
  mutate(row = row_number()) %>%  # add a row id
  pivot_longer(-row, names_to = "column", values_to = "source") %>%  # gather all columns
  mutate(column = match(column, names(df))) %>%  # convert col names to col ids
  group_by(row) %>%
  mutate(target = lead(source, order_by = column)) %>%  # get target from following node in row
  ungroup() %>% 
  filter(!is.na(target))  # remove links from last column in original data

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

现在数据已经是由“源”和“目标”列定义的每行一个链接的典型网络数据格式,并且可以与sankeyNetwork(). 但是,您可能希望在您的情节中多次出现引用同一事物的节点......如果有人在第 1 年访问了希尔顿酒店,然后在第 3 年再次访问了希尔顿酒店,您可能需要 2 个单独的节点,都命名为希尔顿,但出现在情节的不同部分。为此,您必须将“源”和“目标”列中的每个节点标识为访问它们的年份。这就是保留“行”和“列”变量会派上用场的地方。

将列索引附加到“源”名称,并将列索引 + 1 附加到“目标”名称,现在您将能够区分,例如,在第 1 年访问的希尔顿节点和第 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 Hilton_2       Sheraton_3    
#  3 Sheraton_3     Westin_4      
#  4 Westin_4       Hyatt_5       
#  5 John_1         Four Seasons_2
#  6 Four Seasons_2 Ritz-Carlton_3
#  7 Ritz-Carlton_3 Westin_4      
#  8 Westin_4       Sheraton_5    
#  9 Tom_1          Ritz-Carlton_2
# 10 Ritz-Carlton_2 Westin_3      
# # … with 14 more rows

现在,您可以按照相当标准的过程使用链接的源-目标列表来为sankeyNetwork().

创建一个nodes包含在“源”和“目标”向量中找到的所有唯一节点的数据框。您还可以label在不包含年份/列 ID 后缀的节点数据框中创建一个向量。

nodes <- data.frame(name = unique(c(links$source, links$target)))
nodes$label <- sub('_[0-9]*$', '', nodes$name) # remove column id from node label

# # A tibble: 23 x 2
#    name           label       
#    <chr>          <chr>       
#  1 Bob_1          Bob         
#  2 Hilton_2       Hilton      
#  3 Sheraton_3     Sheraton    
#  4 Westin_4       Westin      
#  5 John_1         John        
#  6 Four Seasons_2 Four Seasons
#  7 Ritz-Carlton_3 Ritz-Carlton
#  8 Tom_1          Tom         
#  9 Ritz-Carlton_2 Ritz-Carlton
# 10 Westin_3       Westin      
# # … with 13 more rows

将数据框中的“源”和“目标”向量转换为links数据框中节点的从 0 开始的索引nodesvalue为数据框中的每个链接添加一个任意值,links因为sankeyNetwork(). 然后用sankeyNetwork()!

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

library(networkD3)

sankeyNetwork(Links = links, Nodes = nodes, Source = 'source_id',
              Target = 'target_id', Value = 'value', NodeID = 'label')

在此处输入图像描述

于 2018-09-08T16:21:48.090 回答