tidyverse中summarise可调用函数疑问及分组处理异常排查
问题:dplyr分组summarise中自定义函数获取全量数据而非分组数据的原因及解决方法
我有一个名为samples的数据框,共50行,对应5个样本,每个样本10行,样本编号由sample_no列标记。我还有一个函数f,接收样本数据并返回单行汇总数据框。执行以下代码时:
samples %>% group_by(sample_no) %>% dplyr::summarise( d = f(.) ) %>% unpack(cols = d)
发现f获取到了全部50行数据,而非每个分组对应的10行。但执行f(samples %>% filter(sample_no == 1))时,f能正确获取10行数据。请问是不是只有特定的「分组感知」函数才能在summarise中使用?还是存在其他问题?
最小可复现示例(MWE)
library(tidyverse) SAMPLE_SIZE <- 10 NSAMPLES <- 5 fresh_sample <- function() { data.frame(F = rnorm(SAMPLE_SIZE, 0, 1)) } # 我也想知道有没有更简洁的写法... samples <- do.call(rbind, lapply(1:NSAMPLES, function(k) { fresh_sample() %>% mutate(sample_no = k) })) # 我实际不需要计算维度,真正要对样本做的计算复杂得多 dims <- function(sample) { data.frame(nrow = nrow(sample), ncol = ncol(sample)) } # 返回1行数据框,nrow = 10,ncol = 2 dims(samples %>% filter(sample_no == 1)) # 返回5行数据框,每行的nrow = 50,ncol = 2 samples %>% group_by(sample_no) %>% dplyr::summarise(d = dims(.)) %>% unpack(cols = d)
问题原因
在dplyr::summarise()中直接传递.给自定义函数时,.代表的是带分组属性的整个分组tibble,而非当前分组的纯数据子集。当你在函数内部调用nrow()这类函数时,它会忽略分组属性,直接读取整个tibble的总行数,因此得到了全量的50行。
解决方法
方法1:用cur_data()提取当前分组的纯数据
cur_data()是dplyr提供的专用函数,用于在分组操作中获取当前分组的纯数据(不带分组属性)。修改summarise调用即可解决问题:
samples %>% group_by(sample_no) %>% dplyr::summarise(d = dims(cur_data())) %>% unpack(cols = d)
此时dims函数收到的是每个分组的10行数据,返回结果会正确显示每个分组的nrow=10。
方法2:将函数适配为分组感知型
如果你的函数逻辑允许,也可以将其改写为支持dplyr分组上下文的形式,但对于需要处理整个分组数据框的场景,cur_data()是最直接高效的方案。
额外优化:更简洁的样本数据生成方式
你提到想知道更简洁的样本生成写法,可以用purrr::map_dfr替代do.call(rbind, lapply(...)),代码更简洁易读:
# 写法1 samples <- map_dfr(1:NSAMPLES, function(k) { fresh_sample() %>% mutate(sample_no = k) }) # 更简洁的匿名函数写法 samples <- map_dfr(1:NSAMPLES, ~fresh_sample() %>% mutate(sample_no = .x))
内容的提问来源于stack exchange,提问作者Mohan
相关产品推荐
相关产品推荐

