R中能否用dplyr管道替代lapply处理列表实现分组相异度计算?
问题:tidyverse框架下如何实现分组计算物种相异指数
我想了解是否可以使用tidyverse完成此前在R中依赖列表实现的分析任务。我有一份记录各样方物种丰度的矩阵,需要借助vegan包的vegdist函数计算相异指数,之后将结果转为长格式、去除自比较记录等。在简单示例中使用dplyr实现非常顺畅,代码如下:
library(tidyverse) library(vegan) df <- data.frame(spec1=sample.int(50,10,replace=T), spec2=sample.int(75,10,replace=T), spec3=sample.int(10,10,replace=T), spec4=sample.int(40,10,replace=T), spec5=sample.int(50,10,replace=T), spec6=sample.int(5,10,replace=T)) df%>% vegdist() %>% as.matrix() %>% as_tibble(rownames= "rownames") %>% pivot_longer(-rownames) %>% filter(rownames < name)
现在需要实现分组计算:数据中的物种分属不同类别,需要为每个类别单独生成距离矩阵,之后再合并为单个长格式data.frame或tibble。初始处理代码如下:
cat <- data.frame(spec=c("spec1","spec2","spec3","spec4","spec5","spec6"), group=c("a","b","c","b","a","c")) df%>% pivot_longer(cols = everything(),values_to="abundance",names_to="spec")%>% left_join(cat, by="spec")
前期步骤逻辑很清晰,但到了以往习惯按group列将数据拆分为列表的环节时,始终找不到合适的tidyverse实现方案:尝试过group_by + pivot_wider + vegdist的组合,也试过group_split,但都没能得到可正常运行的结果。求可行的实现方案,或是确认这类场景是否应该继续沿用列表处理的方案。
解决方案
完全可以在tidyverse框架下实现,不需要退回纯列表操作,核心是用group_nest()+purrr::map()做分组迭代,逻辑和原有列表处理思路一致,但全程保持tibble结构化输出,不用手动拆分、合并列表。
可直接运行的完整代码如下:
library(tidyverse) library(vegan) # 沿用示例的分组表、丰度表,提前为样方添加唯一ID避免结果错位 cat <- data.frame(spec=c("spec1","spec2","spec3","spec4","spec5","spec6"), group=c("a","b","c","b","a","c")) df <- data.frame(spec1=sample.int(50,10,replace=T), spec2=sample.int(75,10,replace=T), spec3=sample.int(10,10,replace=T), spec4=sample.int(40,10,replace=T), spec5=sample.int(50,10,replace=T), spec6=sample.int(5,10,replace=T), row.names = paste0("plot",1:10)) result <- df %>% # 丰度表转长表,关联物种分组信息 pivot_longer(cols = everything(), values_to = "abundance", names_to = "spec") %>% mutate(plot_id = rep(paste0("plot",1:10), ncol(df))) %>% left_join(cat, by = "spec") %>% # 按物种分组嵌套,每个分组对应一个独立子数据集 group_nest(group) %>% # 迭代处理每个分组的子数据集,计算相异指数并整理为长表 mutate(dist_res = map(data, function(x){ x %>% # 转回样方-物种宽格式丰度矩阵 pivot_wider(id_cols = plot_id, names_from = spec, values_from = abundance) %>% column_to_rownames("plot_id") %>% # 调用vegdist计算相异指数 vegdist() %>% as.matrix() %>% as_tibble(rownames = "plot1") %>% pivot_longer(-plot1, names_to = "plot2", values_to = "dissimilarity") %>% # 去除自比较、重复配对记录 filter(plot1 < plot2) })) %>% # 展开所有分组的计算结果,得到最终合并的长表 select(-data) %>% unnest(dist_res)
几个关键说明:
- 之前用
group_by+pivot_wider运行失败的核心原因是vegdist这类基础R函数不识别dplyr的分组上下文,无法自动对分组子集做独立计算,必须通过嵌套+map迭代的方式显式处理每个分组的子数据集,本质和手动拆分列表的逻辑一致,只是tidyverse帮你完成了结构化拆分、结果对齐和最终绑定,不需要手动做列表命名和行合并。 - 一定要提前为样方设置唯一ID,否则宽表转换、距离矩阵生成的过程中很容易丢失样方匹配关系,导致结果错位。
- 如果已经熟悉列表处理逻辑,完全可以继续沿用,
group_split()输出的列表和手动拆分的列表没有本质区别,后续配合map_dfr()添加分组标识后绑定结果即可,两种实现方式没有优劣,选择自己顺手的就行。
group_split版本的核心代码片段参考:
df %>% pivot_longer(cols = everything(), values_to = "abundance", names_to = "spec") %>% mutate(plot_id = rep(paste0("plot",1:10), ncol(df))) %>% left_join(cat, by = "spec") %>% group_split(group) %>% map_dfr(function(x){ x %>% pivot_wider(id_cols = plot_id, names_from = spec, values_from = abundance) %>% column_to_rownames("plot_id") %>% vegdist() %>% as.matrix() %>% as_tibble(rownames = "plot1") %>% pivot_longer(-plot1, names_to = "plot2", values_to = "dissimilarity") %>% filter(plot1 < plot2) %>% mutate(group = unique(x$group)) })
内容的提问来源于stack exchange,提问作者N R
相关产品推荐
相关产品推荐

