在group_by()中进行数据子集化与汇总时结果异常的技术问询
分组汇总时的子集筛选问题
我原本对dplyr和base R的使用很有信心,但遇到了一个搞不懂的问题:想按Species分组后,只汇总Sepal.Width小于3.5的对应Sepal.Length之和,但得到的结果一直不对,这让我很慌,因为我经常用这种操作模式。
以下是我尝试的代码:
library(tidyverse) ## 尝试按Species分组,汇总Sepal.Width小于3.5的Sepal.Length之和 ### 此代码会在新列中生成NA iris %>% group_by(Species) %>% summarize(new_col1=sum(Sepal.Length[iris$Sepal.Width<3.5])) #### 此代码产生结果,但'versicolor'的结果为166.3 iris %>% group_by(Species) %>% summarize(new_col1=sum(Sepal.Length[iris$Sepal.Width<3.5],na.rm = TRUE)) #### 但手动验证时得到的结果是296.6 iris %>% filter(Species=="versicolor", Sepal.Width<3.5) %>% pull(Sepal.Length) %>% sum
错误原因
核心问题出在你用了iris$Sepal.Width——在group_by()之后的summarize()里,直接引用整个数据集的列(iris$xxx)会绕过分组逻辑,用的是原始未分组的全量数据,而不是当前分组的子集。
比如第一个代码里,每个分组的Sepal.Length会被整个数据集的Sepal.Width<3.5筛选,长度不匹配就会生成NA;加了na.rm=TRUE后,实际是把全数据集中所有符合Sepal.Width<3.5的Sepal.Length求和,再把这个结果重复给每个分组,自然和手动验证的分组结果对不上。
正确解法
当然可以在group_by()+summarize()里做子集筛选,只要不要用iris$引用全量列,直接用当前分组的列即可,这里有几种靠谱的写法:
- 直接在sum中筛选当前分组数据:
iris %>% group_by(Species) %>% summarize(new_col1 = sum(Sepal.Length[Sepal.Width < 3.5], na.rm = TRUE))
这里的Sepal.Width < 3.5会自动对应当前分组的子集,筛选出该分组内符合条件的Sepal.Length再求和,na.rm=TRUE是为了避免某个分组里没有符合条件的数据时返回NA(如果不需要可以去掉)。
- 先筛选再分组汇总(更直观):
如果逻辑不复杂,先把全局符合条件的数据筛出来,再分组求和,可读性更高:
iris %>% filter(Sepal.Width < 3.5) %>% group_by(Species) %>% summarize(new_col1 = sum(Sepal.Length))
- 用ifelse转换后求和:
把不符合条件的Sepal.Length转为0,再求和,效果和第一种一致:
iris %>% group_by(Species) %>% summarize(new_col1 = sum(ifelse(Sepal.Width < 3.5, Sepal.Length, 0)))
结果验证
用以上任意一种方法,versicolor对应的结果都会是296.6,和手动验证的结果一致。
内容的提问来源于stack exchange,提问作者alejandro_hagan
相关产品推荐
相关产品推荐

