在dplyr的case_when()中实现条件值替换的问题与解决
问题背景
有一组亲子对应关系数据集:
key <- data.frame(parent = c("ID1", "ID2", "ID3"), infant = c("ID4", "ID5", "ID6"))
另有一个行为数据集,其中用通用代码inf指代当前亲代对应的子代,亲代ID存储在id列:
df <- data.frame(time_point = 1:6, id = rep("ID2", 6), social1 = c(NA, NA, "inf", NA, NA, NA), social2 = c(NA, NA, NA, NA, NA, "inf"), social3 = c("inf", NA, NA, NA, NA, NA))
需求是将df中所有inf替换为key里对应亲代的子代ID。
初始尝试与报错
尝试用mutate+case_when实现替换时出现报错:
df %>% mutate(social1 = case_when( grepl("inf", social1) ~ key[key$parent == id,]$infant, TRUE ~ social1 ))
报错信息:
Error in
mutate():
ℹ In argument:social1 = case_when(...).
Caused by error incase_when():
! Can't recycle..1 (left)(size 6) to match..1 (right)(size 2).
问题核心是在case_when的替换逻辑中引用其他列时,出现了向量长度不匹配的回收问题。
复杂场景更新
实际场景更复杂,inf可能与其他ID出现在同一字符串中:
更新后的数据集:
key <- data.frame(parent = c("ID1", "ID2", "ID3"), infant = c("ID4", "ID5", "ID6"))
df <- data.frame(time_point = 1:6, id = rep("ID2", 6), social1 = c(NA, NA, "inf ID7", NA, NA, NA), social2 = c(NA, NA, NA, NA, NA, "inf"), social3 = c("ID8 inf", NA, NA, NA, NA, NA))
尝试通过left_join关联亲子表后用gsub替换,出现警告:
df %>% left_join(., key, by = c("id" = "parent")) %>% mutate(social1 = case_when( grepl("inf", social1) ~ gsub("inf",infant, social1), TRUE ~ social1 ))
警告信息:
Warning message:
There was 1 warning inmutate().
ℹ In argument:social1 = case_when(...).
Caused by warning ingsub():
! argument 'replacement' has length > 1 and only the first element will be used
最终解决方案
在mutate前调用rowwise(),按行处理即可解决该问题,完整代码如下:
df %>% left_join(., key, by = c("id" = "parent")) %>% rowwise() %>% mutate( social1 = case_when( grepl("inf", social1) ~ gsub("inf", infant, social1), TRUE ~ social1 ), social2 = case_when( grepl("inf", social2) ~ gsub("inf", infant, social2), TRUE ~ social2 ), social3 = case_when( grepl("inf", social3) ~ gsub("inf", infant, social3), TRUE ~ social3 ) ) %>% ungroup() # 处理完取消按行分组,避免后续操作受影响
该方法虽因逐行处理速度较慢,但能有效解决向量长度不匹配的问题,完成字符串中inf的替换。
内容的提问来源于stack exchange,提问作者gavago

