如何在R中对分组行执行逻辑测试并逐行生成输出?
问题:如何在R中对一组行执行逻辑测试,但将输出应用于单独的行?
场景
我有两个物品,每个物品按一年的4个季度拆分。如果某物品全年有过至少一次发货,我需要对对应季度进行后续计算;如果全年无发货,则需显示NA以排除在后续计算外。
df <- data.frame(item = c(rep("A", 4), rep("B", 4)), value = c(0, NA, 5, 1, 0, NA, NA, NA)) > df item value 1 A 0 2 A NA 3 A 5 4 A 1 5 B 0 6 B NA 7 B NA 8 B NA
期望输出
最终结果应为:
item value new_value <chr> <dbl> <dbl> 1 A 0 0 2 A NA 0 3 A 5 5 4 A 1 1 5 B 0 NA 6 B NA NA 7 B NA NA 8 B NA NA
已尝试方法
我通常使用library(tidyverse),但如果有基础R解决方案也可以接受。我的首次尝试报错:
library(tidyverse) df %>% group_by(item) %>% mutate(new_value = if_else(sum(value, na.rm = TRUE)>0, coalesce(value, 0), NA_real_)) Error in `mutate()`: ! Problem while computing `new_value = if_else(sum(value, na.rm = TRUE) > 0, coalesce(value, 0), NA_real_)`. ℹ The error occurred in group 1: item = "A". Caused by error in `if_else()`: ! `true` must be length 1 (length of `condition`), not 4.
我使用group_by()是为了让sum(x, na.rm = TRUE)作用于分组,但希望coalesce()作用于每一行。我知道dplyr的if_else()与基础ifelse()不同,因此尝试了后者:
df %>% group_by(item) %>% mutate(new_value = ifelse(sum(value, na.rm = TRUE)>0, coalesce(value, 0), NA_real_)) # A tibble: 8 × 3 # Groups: item [2] item value new_value <chr> <dbl> <dbl> 1 A 0 0 2 A NA 0 3 A 5 0 # should be '5' 4 A 1 0 # should be '1' 5 B 0 NA 6 B NA NA 7 B NA NA 8 B NA NA
结果接近期望输出,但似乎只使用了分组中的第一行,我希望操作作用于每一行,如上述期望输出所示。
解决方案与误区解释
误区分析
你遇到的问题核心在于对if_else(dplyr)和ifelse(基础R)的向量长度匹配逻辑理解有误:
- dplyr的
if_else要求条件、返回的true值、返回的false值三者长度完全一致。你代码中sum(value, na.rm=T)>0是长度为1的逻辑值,而coalesce(value,0)是长度为4的向量,因此触发长度不匹配的报错。 - 基础R的
ifelse会自动循环短向量来匹配长向量的长度,但这里它只取coalesce(value,0)的第一个元素(即0),重复填充整个分组,导致后面的行无法得到对应行的计算结果。
Tidyverse 解决方案
使用case_when可以更优雅地处理这种分组内的条件判断,它会自动将长度为1的条件广播到整个分组:
library(tidyverse) df %>% group_by(item) %>% mutate(new_value = case_when( sum(value, na.rm = TRUE) > 0 ~ coalesce(value, 0), TRUE ~ NA_real_ )) %>% ungroup() # 可选,取消分组
或者保持用if_else,通过rep()将条件向量扩展为与分组行数一致的长度:
df %>% group_by(item) %>% mutate(new_value = if_else( rep(sum(value, na.rm = TRUE) > 0, n()), coalesce(value, 0), NA_real_ )) %>% ungroup()
基础R 解决方案
使用ave()函数对每个分组应用自定义逻辑:
# 用基础R实现coalesce的功能 df$new_value <- ave(df$value, df$item, FUN = function(x) { if (sum(x, na.rm = TRUE) > 0) { ifelse(is.na(x), 0, x) } else { rep(NA_real_, length(x)) } })
以上两种方案都能得到你期望的输出结果。
内容的提问来源于stack exchange,提问作者ScottyJ
相关产品推荐
相关产品推荐

