在数据框中删除所有或部分缺失值(NA)的行

1076

我想要在这个数据框中删除以下这些行:

a) 所有列都包含NA的行。 以下是我的示例数据框。

             gene hsap mmul mmus rnor cfam
1 ENSG00000208234    0   NA   NA   NA   NA
2 ENSG00000199674    0   2    2    2    2
3 ENSG00000221622    0   NA   NA   NA   NA
4 ENSG00000207604    0   NA   NA   1    2
5 ENSG00000207431    0   NA   NA   NA   NA
6 ENSG00000221312    0   1    2    3    2

我希望获得诸如以下的数据框。

             gene hsap mmul mmus rnor cfam
2 ENSG00000199674    0   2    2    2    2
6 ENSG00000221312    0   1    2    3    2
仅在某些列中包含NA,因此我也可以得到以下结果:
             gene hsap mmul mmus rnor cfam
2 ENSG00000199674    0   2    2    2    2
4 ENSG00000207604    0   NA   NA   1    2
6 ENSG00000221312    0   1    2    3    2
20个回答

1287

另外还需要检查 complete.cases

> final[complete.cases(final), ]
             gene hsap mmul mmus rnor cfam
2 ENSG00000199674    0    2    2    2    2
6 ENSG00000221312    0    1    2    3    2

na.omit更适合只删除所有NA的情况。 complete.cases允许通过仅包括数据框中的特定列来进行部分选择:

> final[complete.cases(final[ , 5:6]),]
             gene hsap mmul mmus rnor cfam
2 ENSG00000199674    0    2    2    2    2
4 ENSG00000207604    0   NA   NA    1    2
6 ENSG00000221312    0    1    2    3    2

您的解决方案无法工作。如果您坚持使用 is.na,那么您需要做类似于以下操作:

> final[rowSums(is.na(final[ , 5:6])) == 0, ]
             gene hsap mmul mmus rnor cfam
2 ENSG00000199674    0    2    2    2    2
4 ENSG00000207604    0   NA   NA    1    2
6 ENSG00000221312    0    1    2    3    2

但是使用 complete.cases 更加清晰快速。


13
final[complete.cases(final),] 中,末尾逗号的意义是什么? - hertzsprung
2
complete.cases(final) 返回一个布尔值,其中包含没有 NA 的行,例如 (TRUE, FALSE, TRUE)。逗号后面表示所有列。因此,在逗号之前,您可以对行进行过滤,但在逗号之后,您不需要进行过滤,并要求返回所有内容。 - Kay
感谢您提供这个解决方案,我之前不知道我们可以使用complete.cases语句来指定列。 - Sandy
请注意,na.omit将删除包含至少一个NA的行(即,只要一列为NA,该行就会被删除)。对于complete.cases也是如此:如果您传递的一列或多列中有NA,则它将返回FALSE。 - robertspierre

316
尝试使用na.omit(your.data.frame)来处理缺失值。至于第二个问题,请将其发布为另一个问题(以便更清晰)。

226

tidyr 新增了一个函数drop_na

library(tidyr)
df %>% drop_na()
#              gene hsap mmul mmus rnor cfam
# 2 ENSG00000199674    0    2    2    2    2
# 6 ENSG00000221312    0    1    2    3    2
df %>% drop_na(rnor, cfam)
#              gene hsap mmul mmus rnor cfam
# 2 ENSG00000199674    0    2    2    2    2
# 4 ENSG00000207604    0   NA   NA    1    2
# 6 ENSG00000221312    0    1    2    3    2

6
drop_na()比na.omit()的优点是什么?更快吗?drop_na()和na.omit()都可以用于在数据分析中删除带有缺失值(NA)的行或列。但是,相对于na.omit()而言,drop_na()更快,并且在处理大型数据集时的效率更高。 - wordsforthewise
当我尝试运行这个命令 df %>% drop_na(rnor, cfam) 时,出现了以下错误:Error: Can't subset columns that don't exist. x Column rnor doesn't exist. 为什么会出现这种情况? - user90
"rnor" 应该是您表格中的列名。 - Calum You
2
请注意,如果一个或多个列为NA,则此操作将删除行(即,并非所有列都必须为NA)。 - robertspierre

104

我更喜欢以下方法来检查行是否包含任何NA值:

row.has.na <- apply(final, 1, function(x){any(is.na(x))})

这将返回一个逻辑向量,其中的值表示一行中是否有任何NA。您可以使用它来查看需要删除多少行:

sum(row.has.na)

最终将它们删除

final.filtered <- final[!row.has.na,]

如果要筛选具有特定数量的NA值的行,需要使用一些技巧(例如,可以将'final [,5:6]'输入到'apply'中)。总的来说,Joris Meys的解决方案似乎更加优雅。


4
这非常慢。比如前面提到的complete.cases()解决方案要慢得多。至少在我的情况下,针对xts数据是这样。 - Dave
3
“rowSum(!is.na(final))”似乎比“apply()”更合适。 - s_baldur
难道不应该是 final[rowSum(is.na(final)),] 吗? - Hsiao Yi

67

如果您想对每行的空白值数量有控制权,请尝试使用此函数。对于许多调查数据集,过多的空白问题响应可能会破坏结果。因此,在达到某个阈值后它们将被删除。该函数允许您选择在删除行之前可以有多少个空白值:

delete.na <- function(DF, n=0) {
  DF[rowSums(is.na(DF)) <= n,]
}

默认情况下,它将消除所有NA值:

delete.na(final)
             gene hsap mmul mmus rnor cfam
2 ENSG00000199674    0    2    2    2    2
6 ENSG00000221312    0    1    2    3    2

或者指定允许的最大NA数量:

delete.na(final, 2)
             gene hsap mmul mmus rnor cfam
2 ENSG00000199674    0    2    2    2    2
4 ENSG00000207604    0   NA   NA    1    2
6 ENSG00000221312    0    1    2    3    2

1
这是最可靠的方法,当您需要至少一定数量的NA才能删除该行时使用。对我帮助很大! - Gabriel G.
请查看稍快版本下方链接。在这个场合,非常感谢您分享这个很棒的解决方案,我从那时起就一直在使用它。 - jay.sf

57

如果性能是优先考虑的因素,请使用 data.tablena.omit(),并选择可选参数 cols=

na.omit.data.table 在我的基准测试中是最快的(请参见下文),无论是针对所有列还是选择列(OP问题第二部分)。

如果您不想使用 data.table,请使用 complete.cases()

在一个原始的 data.frame 上,complete.casesna.omit() 或者 dplyr::drop_na() 更快。注意,na.omit.data.frame 不支持 cols=

基准测试结果

这里对于删除所有或选择性缺失观测的基本(蓝色)、dplyr(粉色)和 data.table(黄色)方法进行了比较,使用一个包含 20 个数值变量、独立 5% 的缺失可能性和 4 个变量的子集的 100 万观测量级数据集。

您的结果可能会根据数据集的长度、宽度和稀疏程度而有所不同。

请注意 y 轴上的对数刻度。

enter image description here

基准测试脚本

#-------  Adjust these assumptions for your own use case  ------------
row_size   <- 1e6L 
col_size   <- 20    # not including ID column
p_missing  <- 0.05   # likelihood of missing observation (except ID col)
col_subset <- 18:21  # second part of question: filter on select columns

#-------  System info for benchmark  ----------------------------------
R.version # R version 3.4.3 (2017-11-30), platform = x86_64-w64-mingw32
library(data.table); packageVersion('data.table') # 1.10.4.3
library(dplyr);      packageVersion('dplyr')      # 0.7.4
library(tidyr);      packageVersion('tidyr')      # 0.8.0
library(microbenchmark)

#-------  Example dataset using above assumptions  --------------------
fakeData <- function(m, n, p){
  set.seed(123)
  m <-  matrix(runif(m*n), nrow=m, ncol=n)
  m[m<p] <- NA
  return(m)
}
df <- cbind( data.frame(id = paste0('ID',seq(row_size)), 
                        stringsAsFactors = FALSE),
             data.frame(fakeData(row_size, col_size, p_missing) )
             )
dt <- data.table(df)

par(las=3, mfcol=c(1,2), mar=c(22,4,1,1)+0.1)
boxplot(
  microbenchmark(
    df[complete.cases(df), ],
    na.omit(df),
    df %>% drop_na,
    dt[complete.cases(dt), ],
    na.omit(dt)
  ), xlab='', 
  main = 'Performance: Drop any NA observation',
  col=c(rep('lightblue',2),'salmon',rep('beige',2))
)
boxplot(
  microbenchmark(
    df[complete.cases(df[,col_subset]), ],
    #na.omit(df), # col subset not supported in na.omit.data.frame
    df %>% drop_na(col_subset),
    dt[complete.cases(dt[,col_subset,with=FALSE]), ],
    na.omit(dt, cols=col_subset) # see ?na.omit.data.table
  ), xlab='', 
  main = 'Performance: Drop NA obs. in select cols',
  col=c('lightblue','salmon',rep('beige',2))
)

53

如果您希望更好地控制哪些行被视为无效,另一个选择是

final <- final[!(is.na(final$rnor)) | !(is.na(rawdata$cfam)),]

使用上述方法,这个:

             gene hsap mmul mmus rnor cfam
1 ENSG00000208234    0   NA   NA   NA   2
2 ENSG00000199674    0   2    2    2    2
3 ENSG00000221622    0   NA   NA   2   NA
4 ENSG00000207604    0   NA   NA   1    2
5 ENSG00000207431    0   NA   NA   NA   NA
6 ENSG00000221312    0   1    2    3    2
             gene hsap mmul mmus rnor cfam
1 ENSG00000208234    0   NA   NA   NA   2
2 ENSG00000199674    0   2    2    2    2
3 ENSG00000221622    0   NA   NA   2   NA
4 ENSG00000207604    0   NA   NA   1    2
6 ENSG00000221312    0   1    2    3    2

...这里只删除第五行,因为它是唯一同时包含rnorcfam的NA值的行。然后可以更改布尔逻辑以符合特定要求。


6
如果你想检查多列而不是每一列都输入,你该怎么使用它呢?你可以使用一个范围的final[,4:100]吗? - Herman Toothrot

23
使用dplyr包,我们可以按如下方式过滤NA:

使用dplyr包,我们可以按照以下方式过滤NA:

dplyr::filter(df,  !is.na(columnname))

4
这比 drop_na() 慢大约 10,000 倍。 - Zimano
3
也许是正确的,但对于多个变量,drop_na 使用“任何”逻辑,而 filter 使用“全部”逻辑。因此,如果您需要更灵活的表达式,filter 具有更多的可能性。 - jiggunjer
1
@jiggunjer 这绝对是真的!这取决于你想要实现什么 :) - Zimano

20

有一种通用且生成的代码相当易读的方法是使用 {dplyr} 包中的 filter() 函数和 across() 辅助函数。

library(dplyr)

vars_to_check <- c("rnor", "cfam")

# Filter a specific list of columns to keep only non-missing entries

df %>% 
  filter(across(one_of(vars_to_check),
                ~ !is.na(.x)))

# Filter all the columns to exclude NA
df %>% 
  filter(across(everything(),
                ~ !is.na(.)))

# Filter only numeric columns
df %>%
  filter(across(where(is.numeric),
                ~ !is.na(.)))

同样地,在dplyr包中也有变种函数(filter_all, filter_at, filter_if),它们可以完成同样的事情:

library(dplyr)

vars_to_check <- c("rnor", "cfam")

# Filter a specific list of columns to keep only non-missing entries
df %>% 
  filter_at(.vars = vars(one_of(vars_to_check)),
            ~ !is.na(.))

# Filter all the columns to exclude NA
df %>% 
  filter_all(~ !is.na(.))

# Filter only numeric columns
df %>%
  filter_if(is.numeric,
            ~ !is.na(.))

请点击此处查看使用across的另一个示例。 - jiggunjer

19

对于您的第一个问题,我有一段代码可以轻松地去掉所有的NAs。感谢@Gregor让它变得更简单。

final[!(rowSums(is.na(final))),]

对于第二个问题,代码只是与先前的解决方案有所不同。

final[as.logical((rowSums(is.na(final))-5)),]

注意,-5是您数据中的列数。这将消除所有NAs的行,因为rowSums相加为5并且在减法后变成0。这次,需要使用as.logical。


final[as.logical((rowSums(is.na(final))-ncol(final))),] 对于一个通用答案 - Ferroao

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