如何用外部变量指定列过滤缺失值?两种方法有效性解析
filter(!is.na(the.letter))无效,而drop_na(all_of(the.letter))生效? 问题背景
我们有如下模拟数据,需要依次筛选出a、b、c列存在有效值的样本:
foo <- data.frame( a = c(letters[1:5], rep(NA, 10)), b = c(rep(NA, 5), letters[6:10], rep(NA, 5)), c = c(rep(NA, 10), letters[11:15]) )
生成的数据框:
> foo a b c 1 a <NA> <NA> 2 b <NA> <NA> 3 c <NA> <NA> 4 d <NA> <NA> 5 e <NA> <NA> 6 <NA> f <NA> 7 <NA> g <NA> 8 <NA> h <NA> 9 <NA> i <NA> 10 <NA> <NA> <NA> 11 <NA> <NA> k 12 <NA> <NA> l 13 <NA> <NA> m 14 <NA> <NA> n 15 <NA> <NA> o
尝试用循环实现逐列过滤时,直观写法filter(!is.na(the.letter))完全无效,但drop_na(all_of(the.letter))和硬编码列名的filter(!is.na(c))却能正常工作:
myletters <- c("a", "b", "c") for(the.letter in myletters) { foo2 <- foo %>% # 此代码无效 # filter(!is.na(the.letter)) # 此代码生效 drop_na(all_of(the.letter)) # 硬编码列名也生效,但无法遍历 # filter(!is.na(c)) print(foo2) }
直观方案失效的原因
filter(!is.na(the.letter))里的the.letter是一个字符串变量,is.na()在这里检查的是这个字符串本身是否为NA,而不是数据框中对应列的元素。
比如当循环到the.letter = "c"时,is.na("c")的结果永远是FALSE,所以!is.na(the.letter)会生成一个全为TRUE的向量,filter会保留所有行,等于没有过滤。
生效方案的原理
1. drop_na(all_of(the.letter))
all_of()是dplyr提供的选择工具,它的作用是将字符串形式的列名转换为dplyr能识别的列引用。当你传入all_of(the.letter)时,它会告诉drop_na:去检查数据框中名为the.letter对应字符串的那列,删除该列有NA的行,因此循环时能正确对应每一列。
2. 硬编码filter(!is.na(c))
这里的c直接引用了数据框中的c列,is.na(c)会逐行检查该列的每个元素是否为NA,生成的是和数据框行数一致的逻辑向量,filter会根据这个向量筛选行,所以能正常工作。但这种写法无法通过循环批量处理列名。
用filter实现批量过滤的正确写法
如果想保留filter的写法,也可以用dplyr的.data代词来引用列:
filter(!is.na(.data[[the.letter]]))
.data[[x]]会从当前数据框中提取名为x的列,这样就能正确检查对应列的缺失值,实现循环过滤。
内容的提问来源于stack exchange,提问作者Stuart

