R语言dplyr计算均值时保留NA与0值的实现问题
问题描述
原始数据框
EBOV (group_11) OmicronSpike (group_11) OmicronRBD (group_11) ID Well Tube_patient dpfd lab_id label 2103.0 547652.0 2693.0 PBS H1 PBS 0 PBS well_1 0.0 0.0 0.0 PBS H1 PBS 0 PBS well_1 1924.5 599256.0 2601.0 PBS H2 PBS 0 PBS well_1 1793.0 567137.0 2535.0 PBS H2 PBS 0 PBS well_1 1936.0 NA 2484.0 PBS H3 PBS 0 PBS well_1
需求
按label列分组,仅对列名包含group_的列计算均值,要求:
- 计算均值时排除原数据的NA和值为0.0的记录
- 保留原数据中的NA和0.0,不被计算出的均值替换
尝试过的代码
均值计算代码
pbs_in_v1 <- pbs_in %>% group_by(label) %>% mutate(across(contains("group_"), ~mean(., na.rm = TRUE), .names = "{col}_mean"))
转0为NA的代码
pbs_in <- pbs_in %>% group_by(label) %>% mutate(across(contains("group_"), ~ifelse(. == 0, NA, .)))
错误输出
EBOV (group_11)_mean OmicronSpike (group_11)_mean OmicronRBD (group_11)_mean 1849.273 542322.1 2537.167 1849.273 542322.1 2537.167 1849.273 542322.1 2537.167 1849.273 542322.1 2537.167 1849.273 542322.1 2537.167
期望输出
1849.273 542322.1 2537.167 0.0 0.0 0.0 1849.273 542322.1 2537.167 1849.273 542322.1 2537.167 1849.273 NA 2537.167
疑问
- 如何达成目标?
- 我哪里出错了?
- 是否需要用ifelse排除NA和0.0?
- 若是,该如何修改代码?
解答
1. 实现目标的方法
核心逻辑是先计算分组内排除0和NA后的均值,再根据原数据的NA/0状态选择性填充均值:
- 先算出每个分组中,排除0和NA后的列均值
- 生成新的均值列时,仅在原数据既不是0也不是NA的位置填充均值,原数据的0和NA位置保持原值不变
2. 之前的错误点
- 直接用
mean(., na.rm=TRUE)会把整个分组的均值填充到每一行,完全覆盖了原数据的0和NA,违背了保留原始值的需求 - 把0转成NA后再计算均值,同样还是会将分组均值批量填充到所有行,没有区分原始值的状态,导致所有行都变成均值
3. 需要用条件判断区分状态
是的,必须通过ifelse或case_when做条件判断,明确区分原数据的状态:只有当原数据既不是0也不是NA时,才填充分组均值;原数据是0或NA的位置,保留原值。
4. 修改后的代码
方法一:直接在mutate内嵌套条件判断
pbs_in_v1 <- pbs_in %>% group_by(label) %>% mutate(across(contains("group_"), ~case_when( .x == 0 ~ 0, # 原数据为0时保留0 is.na(.x) ~ NA_real_, # 原数据为NA时保留NA TRUE ~ mean(.x[.x != 0], na.rm = TRUE) # 其他情况填充排除0和NA后的分组均值 ), .names = "{col}_mean"))
解释:
case_when按顺序判断逻辑,精准控制每个位置的填充值mean(.x[.x != 0], na.rm = TRUE)确保计算均值时同时排除0和NA
方法二:先预计算分组均值再匹配填充
如果分组逻辑复杂,可先单独计算均值再合并填充:
# 第一步:预计算每个分组内各group_列的均值(排除0和NA) group_means <- pbs_in %>% group_by(label) %>% summarise(across(contains("group_"), ~mean(.x[.x != 0], na.rm = TRUE), .names = "{col}_mean")) # 第二步:合并原数据和分组均值,再按原数据状态选择性填充 pbs_in_v1 <- pbs_in %>% left_join(group_means, by = "label") %>% mutate(across(ends_with("_mean"), ~case_when( # 根据原列的值判断是否保留均值 get(str_remove(cur_column(), "_mean")) == 0 ~ 0, is.na(get(str_remove(cur_column(), "_mean"))) ~ NA_real_, TRUE ~ .x )))
解释:
- 先单独计算分组均值,避免在
mutate中重复计算,提升效率 - 通过
left_join合并均值数据,再用case_when根据原列值控制最终填充结果
内容的提问来源于stack exchange,提问作者Someone_1313
相关产品推荐
相关产品推荐

