删除data.frame中具有全部或部分NA(缺失值)的行


851

我想删除此数据框中的行:

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

b)仅在某些列中包含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

Answers:


1062

还要检查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更加清晰,快捷。


8
尾随逗号的含义是什么final[complete.cases(final),]
2012年

6
@hertzsprung您需要选择行,而不是列。你还会怎么做?
乔里斯·梅斯

4
是否有一个简单的否定complete.cases?如果我想用NA保留行而不是丢弃?final[ ! complete.cases(final),]不合作...
tumultous_rooster 2015年

2
final数据框是可变的吗?
莫尔斯

1
确实是@Prateek。
Joris Meys


116

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

3
管道和之间没有真正的联系drop_na。例如df %>% drop_na()df %>% na.omit()并且drop_na(df)都基本相当。
伊斯塔

4
@Ista我不同意。na.omit添加其他信息,例如省略案例的索引,并且-更重要的是-不允许您选择列-这是一个drop_na亮点。
lukeA

3
当然,我的意思是,这与管道无关。您可以使用na.omit或不使用管道,就像可以使用drop_na或不使用管道一样。
Ista

1
是的,与管道完全无关。drop_na()就像其他函数一样,可以直接调用或使用管道调用。不幸的是,与其他提到的方法不同,drop_na()不能用于zoo或xts对象类型。对于某些人来说这可能是个问题。
戴夫

是的,所以我编辑了答案,使它不提及管道。
亚瑟·叶

91

我更喜欢以下方法来检查行是否包含任何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的解决方案似乎更优雅。


2
这非常慢。比例如上述complete.cases()解决方案要慢得多。就我而言,至少在xts数据上。
戴夫

3
rowSum(!is.na(final))似乎比apply()
indri_baldur

45

如果您想更好地控制如何将行视为无效的另一种选择是

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

...仅删除第5行,因为这是唯一包含两个rnorAND的NA的行cfam。然后可以更改布尔逻辑以适合特定要求。


5
但是,如果要检查很多列而又不键入每一列,该如何使用呢?可以使用范围final [,4:100]吗?
Herman Toothrot,

40

如果要控制每行有效的NA数量,请尝试使用此功能。对于许多调查数据集,太多的空白问题答案可能会破坏结果。因此,它们会在某个阈值之后被删除。此功能将允许您选择删除行之前可以容纳多少个NA:

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

39

如果性能是重中之重,请使用,data.table并将其na.omit()与可选参数一起使用cols=

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

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

在香草上data.framecomplete.casesna.omit()或快dplyr::drop_na()。注意na.omit.data.frame不支持cols=

基准结果

这是基准(蓝色),dplyr(粉红色)和data.table在全部20个数值变量的100万个观测值的名义数据集上删除所有或选择缺失观测值(黄色)方法的比较,以及第2部分的4个变量的子集。

您的结果可能会因特定数据集的长度,宽度和稀疏性而异。

注意y轴上的对数刻度。

在此处输入图片说明

基准脚本

#-------  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))
)

18

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

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

1
它的执行速度比drop_na()
Zimano

17

这将返回至少具有一个非NA值的行。

final[rowSums(is.na(final))<length(final),]

这将返回至少具有两个非NA值的行。

final[rowSums(is.na(final))<(length(final)-1),]

16

对于您的第一个问题,我有一个适合摆脱所有NA的代码。感谢@Gregor使它更简单。

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

对于第二个问题,代码只是先前解决方案的替代。

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

注意-5是数据中的列数。这将消除具有所有NA的行,因为rowSums总计为5,并且相减后为零。这一次,逻辑上是必要的。


final [as.ologic((rowSums(is.na(final))-ncol(final))),],以寻求普遍答案
Ferroao


9

我是合成器:)。在这里,我将答案合并为一个函数:

#' keep rows that have a certain number (range) of NAs anywhere/somewhere and delete others
#' @param df a data frame
#' @param col restrict to the columns where you would like to search for NA; eg, 3, c(3), 2:5, "place", c("place","age")
#' \cr default is NULL, search for all columns
#' @param n integer or vector, 0, c(3,5), number/range of NAs allowed.
#' \cr If a number, the exact number of NAs kept
#' \cr Range includes both ends 3<=n<=5
#' \cr Range could be -Inf, Inf
#' @return returns a new df with rows that have NA(s) removed
#' @export
ez.na.keep = function(df, col=NULL, n=0){
    if (!is.null(col)) {
        # R converts a single row/col to a vector if the parameter col has only one col
        # see https://radfordneal.wordpress.com/2008/08/20/design-flaws-in-r-2-%E2%80%94-dropped-dimensions/#comments
        df.temp = df[,col,drop=FALSE]
    } else {
        df.temp = df
    }

    if (length(n)==1){
        if (n==0) {
            # simply call complete.cases which might be faster
            result = df[complete.cases(df.temp),]
        } else {
            # credit: http://stackoverflow.com/a/30461945/2292993
            log <- apply(df.temp, 2, is.na)
            logindex <- apply(log, 1, function(x) sum(x) == n)
            result = df[logindex, ]
        }
    }

    if (length(n)==2){
        min = n[1]; max = n[2]
        log <- apply(df.temp, 2, is.na)
        logindex <- apply(log, 1, function(x) {sum(x) >= min && sum(x) <= max})
        result = df[logindex, ]
    }

    return(result)
}

8

假设 dat您的数据帧可以使用以下方法实现预期的输出

1。rowSums

> dat[!rowSums((is.na(dat))),]
             gene hsap mmul mmus rnor cfam
2 ENSG00000199674    0   2    2    2    2
6 ENSG00000221312    0   1    2    3    2

2。lapply

> dat[!Reduce('|',lapply(dat,is.na)),]
             gene hsap mmul mmus rnor cfam
2 ENSG00000199674    0   2    2    2    2
6 ENSG00000221312    0   1    2    3    2

7

一种方法中这是一般的和产生相当可读代码是使用filter功能及其在dplyr包变体(filter_allfilter_atfilter_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(.))

4
delete.dirt <- function(DF, dart=c('NA')) {
  dirty_rows <- apply(DF, 1, function(r) !any(r %in% dart))
  DF <- DF[dirty_rows, ]
}

mydata <- delete.dirt(mydata)

上面的函数删除数据框中所有列中具有“ NA”的所有行,并返回结果数据。如果要检查多个值,例如,NA然后?dart=c('NA')函数参数更改为dart=c('NA', '?')


3

我的猜测是,可以通过以下方式更优雅地解决此问题:

  m <- matrix(1:25, ncol = 5)
  m[c(1, 6, 13, 25)] <- NA
  df <- data.frame(m)
  library(dplyr) 
  df %>%
  filter_all(any_vars(is.na(.)))
  #>   X1 X2 X3 X4 X5
  #> 1 NA NA 11 16 21
  #> 2  3  8 NA 18 23
  #> 3  5 10 15 20 NA

6
这将保留带有的行NA。我认为OP希望的是:df %>% filter_all(all_vars(!is.na(.)))
asifzuba
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.