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

Ben*_* B. 792 r filter missing-data dataframe r-faq

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

a)包含NA所有列的s.下面是我的示例数据框.

             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
Run Code Online (Sandbox Code Playgroud)

基本上,我想获得如下的数据框.

             gene hsap mmul mmus rnor cfam
2 ENSG00000199674    0   2    2    2    2
6 ENSG00000221312    0   1    2    3    2
Run Code Online (Sandbox Code Playgroud)

b)只在某些列中包含NAs,所以我也可以得到这个结果:

             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
Run Code Online (Sandbox Code Playgroud)

Jor*_*eys 995

还检查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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

您的解决方案无法运作.如果您坚持使用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
Run Code Online (Sandbox Code Playgroud)

但使用complete.cases更清晰,更快.

  • 在'final [complete.cases(final),]中尾随逗号的意义是什么? (8认同)
  • @hertzsprung您需要选择行而不是列.你还怎么做? (6认同)
  • 是否有一个简单的否定`complete.cases`?如果我想用NA保留行而不是丢弃?`最后[!complete.cases(final),]`不合作...... (4认同)
  • “complete.cases(final)”返回一个布尔值,其中不存在“NA”,如“(TRUE, FALSE, TRUE)”。后面的逗号表示所有列。因此,在逗号之前,您对行进行过滤,但在逗号之后,您将不进行过滤并询问所有内容 (3认同)
  • 最终是数据帧变量吗? (2认同)

Rom*_*rik 243

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

  • @Bear如果您不关心行号,只需执行`rownames(x)<-NULL`。 (2认同)

luk*_*keA 100

如果你喜欢管道(tidyr),drop_na新的tidyr是你的朋友:

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
Run Code Online (Sandbox Code Playgroud)

  • drop_na() 相对于 na.omit() 有哪些优点?快点? (6认同)
  • @Ista我不同意.`na.omit`添加了额外的信息,如省略案例的索引,更重要的是 - 不允许你选择列 - 这是`drop_na`闪耀的地方. (4认同)
  • 管道与`drop_na`之间没有真正的联系.例如,`df%>%drop_na()`,`df%>%na.omit()`和`drop_na(df)`基本相同. (3认同)
  • 当然,我的观点是,这与管道有任何关系.您可以使用带有或不带管道的`na.omit`,就像使用带有或不带管道的`drop_na`一样. (3认同)
  • 请注意,如果一列或多列为 NA,则这会删除行(即,*所有*列不必为 NA) (2认同)

don*_*kin 88

我更喜欢按照以下方式检查行是否包含任何NA:

row.has.na <- apply(final, 1, function(x){any(is.na(x))})
Run Code Online (Sandbox Code Playgroud)

这将返回逻辑向量,其值表示行中是否存在任何NA.您可以使用它来查看要删除的行数:

sum(row.has.na)
Run Code Online (Sandbox Code Playgroud)

并最终放弃他们

final.filtered <- final[!row.has.na,]
Run Code Online (Sandbox Code Playgroud)

为了过滤具有某些NA的行,它变得有点棘手(例如,你可以将'final [,5:6]'提供给'apply').一般来说,Joris Meys的解决方案似乎更优雅.

  • `rowSum(!is.na(final))` 似乎比 `apply()` 更适合 (3认同)
  • 这非常慢。比例如上述complete.cases()解决方案要慢得多。就我而言,至少在xts数据上。 (2认同)

get*_*ere 44

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

final <- final[!(is.na(final$rnor)) | !(is.na(rawdata$cfam)),]
Run Code Online (Sandbox Code Playgroud)

使用上面的,这个:

             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
Run Code Online (Sandbox Code Playgroud)

变为:

             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
Run Code Online (Sandbox Code Playgroud)

...仅删除第5行,因为它是唯一包含两个rnorAND的NAs的行cfam.然后可以更改布尔逻辑以满足特定要求.

  • 但如果你想检查很多列而不键入每一列,你怎么能使用它,你能使用范围最终[,4:100]吗? (5认同)

Pie*_*une 38

如果要控制每行有效的NA数,请尝试此功能.对于许多调查数据集,太多空白问题响应可能会破坏结果.所以在一定的阈值后删除它们.此功能允许您选择在删除行之前可以拥有多少个NAs:

delete.na <- function(DF, n=0) {
  DF[rowSums(is.na(DF)) <= n,]
}
Run Code Online (Sandbox Code Playgroud)

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

delete.na(final)
             gene hsap mmul mmus rnor cfam
2 ENSG00000199674    0    2    2    2    2
6 ENSG00000221312    0    1    2    3    2
Run Code Online (Sandbox Code Playgroud)

或者指定允许的最大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
Run Code Online (Sandbox Code Playgroud)


C8H*_*4O2 35

如果性能是优先级,请使用data.tablena.omit()使用可选参数cols=.

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

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

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

基准测试结果

下面是基础(蓝色),dplyr(粉红色)和data.table(黄色)方法的比较,用于丢弃全部或选择缺失的观察结果,在20个数值变量的100万个观测值的概念数据集中,具有独立的5%丢失可能性,以及第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))
)
Run Code Online (Sandbox Code Playgroud)


小智 19

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

dplyr::filter(df,  !is.na(columnname))
Run Code Online (Sandbox Code Playgroud)

  • 它的执行速度比 `drop_na()` 慢大约 10.000 倍 (3认同)
  • @Zimano也许是真的,但对于多个变量,`drop_na`使用“任何”逻辑,而“filter”使用“所有”逻辑。所以如果你需要更灵活的表达,filter有更多的可能性。 (3认同)

Leo*_*Leo 17

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

final[rowSums(is.na(final))<length(final),]
Run Code Online (Sandbox Code Playgroud)

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

final[rowSums(is.na(final))<(length(final)-1),]
Run Code Online (Sandbox Code Playgroud)


Leg*_*tMe 15

对于你的第一个问题,我有一个代码,我很乐意摆脱所有的NA.感谢@Gregor让它变得更简单.

final[!(rowSums(is.na(final))),]
Run Code Online (Sandbox Code Playgroud)

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

final[as.logical((rowSums(is.na(final))-5)),]
Run Code Online (Sandbox Code Playgroud)

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


小智 14

我们也可以使用子集函数.

finalData<-subset(data,!(is.na(data["mmul"]) | is.na(data["rnor"])))
Run Code Online (Sandbox Code Playgroud)

这将只给出mmul和rnor中没有NA的那些行


bsc*_*idr 14

一种既通用又产生相当可读代码的方法是使用{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(.)))
Run Code Online (Sandbox Code Playgroud)

同样,在 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(.))
Run Code Online (Sandbox Code Playgroud)


小智 13

dplyr 1.0.4 引入了两个伴随函数filter:它们是if_any()if_all()。在这种情况下,伴随函数if_all()将特别有用:

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

df %>% 
  filter(if_all(everything(), ~ !is.na(.x)))
Run Code Online (Sandbox Code Playgroud)

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

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

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

df %>% 
  filter(if_all(cols_to_check, ~ !is.na(.x)))
Run Code Online (Sandbox Code Playgroud)

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


Jer*_*y T 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)
}
Run Code Online (Sandbox Code Playgroud)


Prr*_*dep 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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)