R中桑基图(Sankey plot)节点宽度调整及多分类数据适配技术求助
嘿,你遇到的这个问题我之前帮别人解决过好几次!核心就是要先把那些用&连起来的多分类拆解开,再精准控制每个阶段的节点权重——比如Stage3要等大,其他阶段按特征数量来,这样才能让桑基图完全符合你的要求。我来一步步给你讲清楚怎么做~
首先咱们得先处理数据,原始数据里的A&B这种合并分类,桑基图工具是识别不了的,必须拆成单独的行,才能正确统计每个分类对应的特征数量。我用tidyverse工具包来做预处理,这是R里处理数据最顺手的一套工具了。
第一步:加载工具包并准备原始数据
先把需要的包和你的原始数据准备好:
library(tidyverse) library(networkD3) # 做交互式桑基图的常用包,也可以用ggalluvial做静态图
我根据你的描述补全了Stage3和Stage4的内容:
df <- data.frame(Features = c("Feature1", "Feature2", "Feature3", "Feature4", "Feature5"), Stage1 = c("A", "A&B", "B", "B&C", "C"), Stage2 = c("D", "D&E", "F", "F&G", "G"), Stage3 = c("X1", "X2", "X3", "X4", "X5"), # 每个特征对应唯一值,保证Stage3节点等大 Stage4 = c("f", "f", "f", "f", "g"))
第二步:拆分多分类字符串
把Stage1和Stage2里用&连接的分类拆成单独的行,这样每个分类就能对应到对应的Feature:
# 拆分Stage1的多分类 df_stage1_split <- df %>% separate_rows(Stage1, sep = "&") # 拆分Stage2的多分类 df_stage2_split <- df %>% separate_rows(Stage2, sep = "&")
拆分完之后,你就能看到Feature2对应的Stage1会拆成两行:A和B,这样就能正确统计Stage1里A有2个特征、B有3个特征、C有2个特征了,完全符合你的要求。
第三步:构建桑基图的链接与节点数据
桑基图的核心是**链接(links)和节点(nodes)**数据框,我们需要精准控制每个链接的权重(也就是流量大小),以此来控制节点的大小:
1. 构建各阶段的链接关系
# Stage1 → Stage2:统计每个分类组合的特征数量 links_s1s2 <- df %>% separate_rows(Stage1, sep = "&") %>% separate_rows(Stage2, sep = "&") %>% group_by(Stage1, Stage2) %>% summarise(value = n_distinct(Features), .groups = "drop") # Stage2 → Stage3:为了让Stage3节点等大,每个链接的权重设为1(每个Stage3节点只对应1个Feature) links_s2s3 <- df %>% separate_rows(Stage2, sep = "&") %>% group_by(Stage2, Stage3) %>% summarise(value = 1, .groups = "drop") # Stage3 → Stage4:统计每个Stage3到Stage4的特征数量 links_s3s4 <- df %>% group_by(Stage3, Stage4) %>% summarise(value = n(), .groups = "drop")
2. 合并所有链接并匹配节点ID
networkD3要求节点用数字ID来标识,所以我们要把所有唯一节点提取出来,分配ID:
# 合并所有链接 all_links <- bind_rows( links_s1s2 %>% rename(source = Stage1, target = Stage2), links_s2s3 %>% rename(source = Stage2, target = Stage3), links_s3s4 %>% rename(source = Stage3, target = Stage4) ) # 提取所有唯一节点并分配ID(networkD3的ID从0开始) nodes <- data.frame( name = unique(c(all_links$source, all_links$target)) ) %>% mutate(id = row_number() - 1) # 把链接里的分类名称替换成对应的节点ID all_links <- all_links %>% left_join(nodes, by = c("source" = "name")) %>% rename(source_id = id) %>% left_join(nodes, by = c("target" = "name")) %>% rename(target_id = id) %>% select(source_id, target_id, value)
第四步:绘制交互式桑基图
现在可以用networkD3生成桑基图了,还能调整节点宽度等样式:
sankeyNetwork( Links = all_links, Nodes = nodes, Source = "source_id", Target = "target_id", Value = "value", NodeID = "name", fontSize = 12, nodeWidth = 30, # 这里可以调整节点的宽度(像素值) iterations = 0 # 固定节点顺序,避免自动调整导致阶段混乱 )
运行这段代码后,你就能看到完全符合要求的桑基图:
- Stage1的节点大小:A对应2个特征,B对应3个,C对应2个
- Stage2的节点大小:D对应2个,E对应1个,F对应2个,G对应2个
- Stage3的每个节点大小完全一致(因为每个节点的入度都是1)
- Stage4的f对应4个特征,g对应1个特征
备选方案:用ggalluvial做静态桑基图
如果你需要更灵活的样式控制(比如自定义节点颜色、标签位置),可以用ggalluvial包(基于ggplot2),代码更简洁:
library(ggalluvial) # 整理成ggalluvial需要的格式 df_alluvial <- df %>% separate_rows(Stage1, sep = "&") %>% separate_rows(Stage2, sep = "&") # 绘制桑基图 ggplot(df_alluvial, aes(y = 1, axis1 = Stage1, axis2 = Stage2, axis3 = Stage3, axis4 = Stage4)) + geom_alluvium(aes(fill = Features), width = 1/12) + geom_stratum(width = 1/3) + # 调整节点宽度 geom_text(stat = "stratum", aes(label = after_stat(stratum))) + scale_x_discrete(limits = c("Stage1", "Stage2", "Stage3", "Stage4"), expand = c(0.05, 0.05)) + theme_minimal() + guides(fill = "none") # 去掉Feature的图例,按需调整
这里的y = 1就保证了Stage3的每个节点大小一致,因为每个节点对应的权重都是1,非常直观。
关键要点总结
- 拆分多分类是核心:必须把
A&B这种合并字符串拆成单独的行,否则桑基图会把它当成一个单独的节点,完全不符合你的统计需求 - 权重控制节点大小:节点的大小由流入/流出的总权重决定,所以我们通过设置
value参数来精准控制——比如Stage3的链接权重设为1,就能让所有节点等大 - 工具选择看需求:networkD3适合做交互式桑基图(可以拖拽节点、查看流量),ggalluvial适合做静态、高度定制化的桑基图
备注:内容来源于stack exchange,提问作者olu

