使用R中的NetworkD3包创建Sankey图

时间:2017-05-23 10:36:50

标签: r plot sankey-diagram htmlwidgets networkd3

目前,我正在尝试按照Chris Grandrud(https://christophergandrud.github.io/networkD3/)的说明使用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)

为了向您概述我的数据,请点击此处截图:

SampleDataScreenshot

我会给你更多“编码”信息,但由于我对R的主题很新,我希望你能在这个问题上跟随我的思路。如果没有,请不要犹豫不决。

谢谢:)

2 个答案:

答案 0 :(得分:9)

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

假设您的数据如下:

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)

可能有更优雅的解决方案,但这可能是您问题的起点。如果您不喜欢节点名称中的“Year ...”,请在设置数据帧后删除它们。

答案 1 :(得分:1)

这个问题很多。。。如何转换一个数据集,该数据集在每行的多列中定义了多个链接/边。这就是我将其转换为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. 使用tidyr的{​​{1}}函数将数据集转换为长格式
  3. 将列名变量转换为原始数据集中的列的索引/编号
  4. 按行分组(原始数据集中的每个观察值),按其所在的列对每个节点进行排序,并通过将其设置为位于其后的列中的节点,为其“目标”创建一个变量
  5. 过滤掉所有带有gather()作为“目标”的行(原始数据集最后一列中的节点将没有“目标”,因此这些行未指定链接)

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 一起使用。但是,您可能希望引用同一事物的节点在您的情节中出现多次……如果某人在第一年访问了希尔顿,然后在第三年再次访问了希尔顿,则可能需要两个单独的节点,两个都命名为希尔顿,但出现在情节的不同部分。为此,您必须在“源”和“目标”列中标识出每个节点的访问年份。在那里保留“ row”和“ column”变量将很方便。

将列索引添加到“源”名称,并将列索引+ 1添加到“目标”名称,现在您将能够区分例如一年中访问过的希尔顿酒店节点1和希尔顿在3年访问的节点

sankeyNetwork()

现在,您可以按照相当标准的过程使用链接的源目标列表来为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数据帧中节点的从0开始的索引。 nodes要求为links数据框中的每个链接添加一个任意值。现在,您可以从sankeyNetwork()数据框中的节点名称中删除附加的列索引,因为它们将仅用于标记结果图中的节点(因此,如果它们是唯一的,则不再重要)。然后用nodes绘制它!

sankeyNetwork()

enter image description here