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

Remove rows with all or some NAs (missing values) in data.frame

提问人:Benoit B. 提问时间:2/1/2011 最后编辑:JaapBenoit B. 更新时间:7/24/2023 访问量:2304229

问:

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

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
筛选器 缺失数据 R-FAQ

评论


答:

316赞 Roman Luštrik 2/1/2011 #1

尝试。至于第二个问题,请尝试将其作为另一个问题发布(为清楚起见)。na.omit(your.data.frame)

1287赞 Joris Meys 2/1/2011 #2

还要检查 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只删除所有 ' 更好。 允许通过仅包含 DataFrame 的某些列进行部分选择:NAcomplete.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赞 hertzsprung 10/1/2012
尾随逗号的意义是什么?final[complete.cases(final),]
2赞 Kay 3/18/2021
返回一个布尔值的行,其中没有 like。尾随逗号表示所有列。因此,在逗号之前,您可以过滤行,但在逗号之后,您不进行过滤并要求所有内容complete.cases(final)NA(TRUE, FALSE, TRUE)
0赞 Sandy 6/7/2021
感谢您提供此解决方案,我不知道我们可以用语句指定列。complete.cases
0赞 robertspierre 4/21/2023
请注意,这将删除包含至少一个 NA 的行(即,一列为 NA 就足以删除该行)。与以下列相同:如果您传递的一列(或多列)为 NA,则返回 FALSEna.omitcomplete.cases
104赞 donshikin 2/3/2011 #3

我更喜欢以下方法来检查行是否包含任何 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赞 Dave 1/17/2019
这是非常缓慢的。比前面提到的 complete.cases() 解决方案慢得多。至少,就我而言,在 xts 数据上。
3赞 s_baldur 2/9/2019
rowSum(!is.na(final))似乎比apply()
0赞 Hsiao Yi 11/11/2021
不应该吗?final[rowSum(is.na(final)),]
53赞 getting-there 11/5/2013 #4

如果要更好地控制行被视为无效的方式,另一个选项是

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 行,因为它是唯一包含 AND 的 NA 的行。然后可以更改布尔逻辑以满足特定要求。rnorcfam

评论

6赞 Herman Toothrot 10/20/2016
但是,如果您想检查许多列,而不输入每一列,您如何使用它,您可以使用范围 final[,4:100]?
19赞 Leo 9/19/2014 #5

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

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

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

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

评论

0赞 Sandy 6/8/2021
这是完美的,我实际上正在寻找一个类似的解决方案,该解决方案可以允许我们仅保留具有一个或多个非 NA 值的行(无论列如何)。但是,此代码在我的情况下不起作用。它保留所有行,甚至那些具有所有 NA 的行。
14赞 Ramya Ural 11/12/2014 #6

我们也可以为此使用子集函数。

finalData<-subset(data,!(is.na(data["mmul"]) | is.na(data["rnor"])))

这将仅给出那些在 mmul 和 rnor 中都没有 NA 的行

67赞 Pierre L 5/26/2015 #7

如果要控制每行有效的 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

评论

1赞 Gabriel G. 3/29/2021
这是删除行的最可靠方法,当您需要至少多个 NA 来删除该行时。帮了我很多!
0赞 jay.sf 7/24/2023
请参阅下面的稍快版本。借此机会,非常感谢您分享这个出色的解决方案,从那时起我一直在使用它。
9赞 Jerry T 2/4/2016 #8

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

#' 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)
}
19赞 LegitMe 2/10/2016 #9

对于你的第一个问题,我有一个代码,我可以放心地摆脱所有 NA。感谢您@Gregor让它变得更简单。

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

对于第二个问题,代码只是与上一个解决方案的替代。

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

请注意,-5 是数据中的列数。这将消除包含所有 NA 的行,因为 rowSums 加起来为 5,减法后它们变为零。这一次,as.logical 是必要的。

评论

0赞 Ferroao 2/22/2017
final[as.logical((rowSums(is.na(final)))-ncol(final))),] 用于通用答案
226赞 lukeA 8/16/2016 #10

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赞 wordsforthewise 10/12/2020
drop_na() 与 na.omit() 相比有什么优势?更快?
2赞 robertspierre 4/21/2023
请注意,如果一列或多列为 NA(即,不必所有列都是 NA),这将删除行
10赞 Prradep 3/16/2017 #11

假设作为您的数据帧,可以使用以下命令实现预期的输出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
23赞 Raminsu 4/12/2017 #12

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

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

评论

4赞 Zimano 2/21/2020
这比drop_na()
3赞 jiggunjer 7/26/2020
@Zimano 也许是真的,但对于多个变量,使用“任何”逻辑并使用“所有”逻辑。因此,如果你在表达上需要更多的灵活性,过滤器就有了更多的可能性。drop_nafilter
1赞 Zimano 7/30/2020
@jiggunjer 这绝对是真的!这真的取决于你想实现什么:)
57赞 C8H10N4O2 2/16/2018 #13

如果性能是重中之重,请使用 and with optional param 。data.tablena.omit()cols=

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

如果您不想使用 ,请使用 。data.tablecomplete.cases()

在原版上,complete.casesna.omit() 或 dplyr::d rop_na() 快。请注意,不支持 .data.framena.omit.data.framecols=

基准测试结果

以下是在包含 20 个数字变量的 100 万个观测值(具有独立的 5% 缺失概率)的名义数据集上,以及第 2 部分的 4 个变量的子集,用于删除所有或选择缺失观测值的基本(蓝色)、(粉红色)和(黄色)方法的比较。dplyrdata.table

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

注意 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))
)
4赞 sapy 2/23/2018 #14
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”的数据框中删除所有行,并返回结果数据。如果要检查多个值,例如将函数 param 更改为NA?dart=c('NA')dart=c('NA', '?')

4赞 Joni Hoppen 5/9/2018 #15

我的猜测是,这可以通过这种方式更优雅地解决:

  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

评论

11赞 asifzuba 6/26/2018
这将保留带有 . 的行。我认为 OP 想要的是:NAdf %>% filter_all(all_vars(!is.na(.)))
20赞 bschneidr 4/24/2019 #16

一种既通用又可生成可读性相当高的代码的方法是使用 {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 包 (, , ) 中也有 variant 函数可以完成相同的操作: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(.))

评论

0赞 jiggunjer 7/26/2020
有关另一个示例,请参阅此处across
13赞 kasia_b 6/29/2021 #17

DPLYR 1.0.4 引入了两个配套函数:它们是 和 。在这种情况下,伴随函数将特别有用:filterif_any()if_all()if_all()

a) 删除所有列中包含 NA 的行

df %>% 
  filter(if_all(everything(), ~ !is.na(.x)))

此行将仅保留所有列都没有 NA 的行。

b) 删除仅包含某些列中 NA 的行

cols_to_check = c("rnor", "cfam")

df %>% 
  filter(if_all(cols_to_check, ~ !is.na(.x)))

此行将检查是否有任何指定的列 (cols_to_check) 具有 NA,并且仅保留那些没有 NA 的行。

评论

0赞 medium-dimensional 11/16/2022
如果需要删除至少具有一个 NA 值的行,可以尝试 或 。df %>% filter(if_any(everything(), ~!is.na(.x)))na.omit(df)
0赞 GitZine 11/28/2022
哪种解决方案花费的时间更少,您建议的解决方案还是以下解决方案:df %>% filter(!if_all(everything(), ~ is.na(.)))
3赞 GitZine 11/28/2022 #18

如果您只想删除所有列中具有 NA 的行,以下是解决方案:

df %>%
    filter(!if_all(everything(), ~  is.na(.)))
0赞 Maël 5/8/2023 #19

另一种选择是以下功能:na_omitcollapse

na_omit(df)
#              gene hsap mmul mmus rnor cfam
# 1 ENSG00000199674    0    2    2    2    2
# 2 ENSG00000221312    0    1    2    3    2

或者,对于选定的列:

na_omit(df, cols = c("rnor", "cfam")) #Alternatively, works with a function, index or logical vector

#              gene hsap mmul mmus rnor cfam
# 1 ENSG00000199674    0    2    2    2    2
# 2 ENSG00000207604    0   NA   NA    1    2
# 3 ENSG00000221312    0    1    2    3    2

collapse::na_omit比此处提供的任何其他解决方案都快,包括:data.table::na.omittidyr::drop_na

#Using data from https://stackoverflow.com/a/48830183/13460602
mb <- 
  microbenchmark(
  collapse = na_omit(dt),
  dt = na.omit(dt), 
  base = na.omit(df),
  complete.cases = df[complete.cases(df), ],
  tidyr = drop_na(df)
)

# Unit: milliseconds
#            expr      min       lq     mean   median        uq       max neval
#        collapse  70.6927 130.5431 191.9058 156.6320  217.1957  915.6074   100
#              dt  76.5151 130.7049 195.9737 172.6981  215.2754  735.1839   100
#            base 406.3456 778.5028 900.1791 860.7407 1005.6444 2009.2036   100
#  complete.cases 295.6927 497.8572 649.3397 575.9408  715.9452 2137.3366   100
#           tidyr  83.4586 145.3758 207.3149 180.9264  242.0750  838.3654   100
0赞 jay.sf 7/24/2023 #20

对于删除缺少一些的行,即事先不知道列的行,我们@PierreL使用 .rowSums()

我想快速指出一种使用 matrixStats 包的更快的方法。对于 200,000x1,000 的矩阵,这几乎节省了一秒钟。rowSums2

该函数以相同的方式应用。(注意,结果已经是类,否则会抱怨。is.na()"matrix"rowSums2

M[matrixStats::rowSums2(is.na(M)) < dim(M)[2]*1e-2, ]  ## allow 1% missings per row

基准

Unit: seconds
     expr      min       lq     mean   median       uq      max neval
  rowSums 3.083927 3.104315 3.135399 3.131235 3.162080 3.209985   100
 rowSums2 2.129368 2.241577 2.347562 2.287717 2.494494 2.683859   100

enter image description here

法典:

m=2e5; n=1e3
set.seed(42)
M <- matrix(rpois(n*m, 2), m, n)
M[sample(seq_along(M), m*n*.01)] <- NA_integer_

microbenchmark::microbenchmark(
  rowSums=M[rowSums(is.na(M)) < dim(M)[2]*1e-2, ],
  rowSums2=M[matrixStats::rowSums2(is.na(M)) < dim(M)[2]*1e-2, ],
  check='identical'
  ) |> print()

Rscript --vanilla ~/is_na_issue.R