如何在R中忽略只有两列包含NA的行?

18

我希望排除那些两个指定列都出现了NA的行。

我熟悉na.omitis.nacomplete.cases,但不知道如何使用它们来实现我的要求。例如,我有以下数据框:

(df <- structure(list(x = c(1L, 2L, NA, 3L, NA),
                     y = c(4L, 5L, NA, 6L, 7L),
                     z = c(8L, 9L, 10L, 11L, NA)),
                .Names = c("x", "y", "z"),
                class = "data.frame",
                row.names = c(NA, -5L)))
x   y   z
1   4   8
2   5   9
NA  NA  10
3   6   11
NA  7   NA

我想要删除仅当x列和y列中都出现NA时的行(不包括任何在z列中的东西),得到

x   y   z
1   4   8
2   5   9
3   6   11
NA  7   NA

有没有简单的方法可以做到这一点?使用na.omitis.nacomplete.cases都不起作用。

5个回答

27
df[!with(df,is.na(x)& is.na(y)),]
#      x y  z
#1  1 4  8
#2  2 5  9
#4  3 6 11
#5 NA 7 NA

我在一个稍大的数据集上进行了基准测试。以下是结果:

set.seed(237)
df <- data.frame(x=sample(c(NA,1:20), 1e6, replace=T), y= sample(c(NA, 1:10), 1e6, replace=T), z= sample(c(NA, 5:15), 1e6,replace=T)) 

f1 <- function() df[!with(df,is.na(x)& is.na(y)),]
f2 <- function() df[rowSums(is.na(df[c("x", "y")])) != 2, ]
f3 <- function()  df[ apply( df, 1, function(x) sum(is.na(x))>1 ), ] 

library(microbenchmark)

microbenchmark(f1(), f2(), f3(), unit="relative")
Unit: relative
#expr       min        lq    median        uq       max neval
# f1()  1.000000  1.000000  1.000000  1.000000  1.000000   100
# f2()  1.044812  1.068189  1.138323  1.129611  0.856396   100
# f3() 26.205272 25.848441 24.357665 21.799930 22.881378   100

谢谢akrun - 我使用了你的答案,因为它最短,但选择了另一个,因为它是先发布的。再次感谢。 :) - Thomas
1
@Thomas,请不要根据那些参数更改已接受的答案。如果akrun的答案符合您的要求(这似乎是),他们应该得到信用。我的FGITW参考只是对您选择哪个答案的方法的戳。我答案的主要优点是它易于应用于超过少数列。 - A5C1D2H2I1M1N2O1R2T1
好的,改回akrun的答案! :) - Thomas
@Thomas,根据您所描述的参数,这应该转到Amanda Mahto。很抱歉在讨论中迟到了。 - akrun
@Thomas,如果你感兴趣的话,我已经更新了我的答案,展示了我所提出的rowSums+is.na方法的应用简易性。 - A5C1D2H2I1M1N2O1R2T1

16

dplyr 解决方案

require("dplyr")
df %>% filter_at(.vars = vars(x, y), .vars_predicate = any_vars(!is.na(.)))

可以使用 .vars 参数修改以接受任意数量的列。


更新:dplyr 1.0.4

df %>%
  filter(!if_all(c(x, y), is.na))

查看类似的回答:https://dev59.com/31IG5IYBdhLWcg3w8Wiu#66136167


8

可以使用rowSumsis.na来处理,像这样:

> df[rowSums(is.na(df[c("x", "y")])) != 2, ]
   x y  z
1  1 4  8
2  2 5  9
4  3 6 11
5 NA 7 NA

跟随基准测试的潮流,并展示我提到的这个相当容易泛化的解决方案,考虑以下内容:

## Sample data with 10 columns and 1 million rows
set.seed(123)
df <- data.frame(replicate(10, sample(c(NA, 1:20), 
                                      1e6, replace = TRUE)))

首先,如果您只对两列感兴趣,下面是它们的显示效果。这两种解决方案都相当易读和简短。速度非常接近。

f1 <- function() {
  df[!with(df, is.na(X1) & is.na(X2)), ]
} 
f2 <- function() {
  df[rowSums(is.na(df[1:2])) != 2, ]
} 

library(microbenchmark)
microbenchmark(f1(), f2(), times = 20)
# Unit: milliseconds
#  expr      min       lq   median       uq      max neval
#  f1() 745.8378 1100.764 1128.047 1199.607 1310.236    20
#  f2() 784.2132 1101.695 1125.380 1163.675 1303.161    20

接下来,让我们看一下相同的问题,但这一次,我们考虑前5列中的NA值。此时,rowSums方法略快,并且语法并没有发生太大变化。
f1_5 <- function() {
  df[!with(df, is.na(X1) & is.na(X2) & is.na(X3) &
             is.na(X4) & is.na(X5)), ]
} 
f2_5 <- function() {
  df[rowSums(is.na(df[1:5])) != 5, ]
} 

microbenchmark(f1_5(), f2_5(), times = 20)
# Unit: seconds
#    expr      min       lq   median       uq      max neval
#  f1_5() 1.275032 1.294777 1.325957 1.368315 1.572772    20
#  f2_5() 1.088564 1.169976 1.193282 1.225772 1.275915    20

嗨,Ananda Mahto,我不确定你的意思,但akrun的答案是先发布的。 - Thomas
谢谢Ananda,我选择了你的答案,因为它是第一个发布的。 - Thomas

8
您可以申请将行切割为片段:
sel <- apply( df, 1, function(x) sum(is.na(x))>1 )

那么你可以用它来选择:
df[ sel, ]

忽略z列,只需从应用程序中省略它即可:
sel <- apply( df[,c("x","y")], 1, function(x) sum(is.na(x))>1 )

如果所有的条件都必须为TRUE,只需要稍微修改一下函数即可:
sel <- apply( df[,c("x","y")], 1, function(x) all(is.na(x)) )

这里其他的解决方案更专注于这个具体的问题,但是学习使用apply很值得,因为它可以解决许多其他问题。代价是速度(适用于小数据集和速度测试的常规警告):
> microbenchmark( df[!with(df,is.na(x)& is.na(y)),], df[rowSums(is.na(df[c("x", "y")])) != 2, ], df[ apply( df, 1, function(x) sum(is.na(x))>1 ), ] )
Unit: microseconds
                                              expr     min       lq   median       uq      max neval
              df[!with(df, is.na(x) & is.na(y)), ]  67.148  71.5150  76.0340  86.0155 1049.576   100
        df[rowSums(is.na(df[c("x", "y")])) != 2, ] 132.064 139.8760 145.5605 166.6945  498.934   100
 df[apply(df, 1, function(x) sum(is.na(x)) > 1), ] 175.372 184.4305 201.6360 218.7150  321.583   100

非常感谢您的回答,我真的很感激您的时间和帮助。 - Thomas
没问题。你得到了一些很好的答案来回答一个相对简单的问题,这表明你提问的能力不错 :-) - Ari B. Friedman

1
这也是基本的dplyr解决方案:
library(dplyr)

df %>%
  filter(!(is.na(x) & is.na(y)))

   x y  z
1  1 4  8
2  2 5  9
3  3 6 11
4 NA 7 NA

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接