使用dplyr::summarise结合across出现异常行为的原因排查
dplyr中summarise内across影响后续计算的原因分析
这不是异常行为,而是dplyr的设计规则导致的:summarise中的表达式严格按书写顺序执行,若新创建的列名与原始列名重复,会直接覆盖原始列,后续表达式引用该列时,使用的是已经计算出的汇总后的值(单值),而非分组前的原始分组内数据(向量)。
具体原因拆解
across前置时的覆盖问题
当你把across(matches("amount"), sum)放在summarise的开头,它会生成amount、another_amount等汇总后的列(每个组对应一个单值),直接覆盖了原始的amount列(每个组内是长度为10的向量):sum_if_yes_1 = sum(amount[ yes_no == "Yes"]):此时amount是单值,而yes_no还是原始分组内的长度10的向量,索引操作因长度不匹配返回NA,求和后仍是NA。sum_if_yes_2 = sum(ifelse(yes_no == "Yes", amount, 0)):单值amount会被自动循环匹配长度10的yes_no,相当于把汇总值重复10次,再对其中5个"Yes"对应的位置求和,结果就是汇总值 * 5(比如第一组5.47*5≈27.4)。sum_if_yes_3正常:因为amount_if_yes的汇总值和原始列的分组总和完全一致,对单值求和自然得到正确结果。
调整顺序后恢复正常
把自定义的sum计算放在across前面时,这些表达式引用的都是原始的分组内列数据(还没被覆盖),计算完成后across再生成汇总列,不会影响前面的结果。与data.table的行为差异
data.table在分组计算的j表达式中,所有列引用默认指向原始的分组内数据,即使先创建了同名列,后续引用仍会使用原始数据(除非显式指定新列),因此顺序不会影响结果。
正确解决方法
有两种可靠的方式避免这个问题:
方法1:使用.data代词明确引用原始列
.data代词会强制引用原始的输入数据列,不受后续列覆盖的影响:
test_data |> mutate( amount_if_yes = ifelse(yes_no == "Yes", amount, 0) ) |> group_by(group) |> summarise( across(matches("amount"), sum), sum_all = sum(.data$amount), sum_if_yes_1 = sum(.data$amount[.data$yes_no == "Yes"]), sum_if_yes_2 = sum(ifelse(.data$yes_no == "Yes", .data$amount, 0)), sum_if_yes_3 = sum(.data$amount_if_yes) )
方法2:修改across生成的列名,避免覆盖
通过.names参数给汇总列加前缀/后缀,和原始列名区分开:
test_data |> mutate( amount_if_yes = ifelse(yes_no == "Yes", amount, 0) ) |> group_by(group) |> summarise( across(matches("amount"), sum, .names = "sum_{.col}"), sum_all = sum(amount), sum_if_yes_1 = sum(amount[yes_no == "Yes"]), sum_if_yes_2 = sum(ifelse(yes_no == "Yes", amount, 0)), sum_if_yes_3 = sum(amount_if_yes) )
内容的提问来源于stack exchange,提问作者rosscova
相关产品推荐
相关产品推荐

