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

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

a)在所有列中包含#0。下面是我的示例数据框。

             gene hsap mmul mmus rnor cfam1 ENSG00000208234    0   NA   NA   NA   NA2 ENSG00000199674    0   2    2    2    23 ENSG00000221622    0   NA   NA   NA   NA4 ENSG00000207604    0   NA   NA   1    25 ENSG00000207431    0   NA   NA   NA   NA6 ENSG00000221312    0   1    2    3    2

基本上,我想得到一个数据帧,如下所示。

             gene hsap mmul mmus rnor cfam2 ENSG00000199674    0   2    2    2    26 ENSG00000221312    0   1    2    3    2

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

             gene hsap mmul mmus rnor cfam2 ENSG00000199674    0   2    2    2    24 ENSG00000207604    0   NA   NA   1    26 ENSG00000221312    0   1    2    3    2
2209438 次浏览

至于第二个问题,试着把它作为另一个问题发布(为了清楚起见)。

检查#0

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

na.omit更适合删除所有NAcomplete.cases允许通过仅包含数据框的某些列来进行部分选择:

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

你的解决方案行不通。如果你坚持使用is.na,那么你必须这样做:

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

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

我更喜欢以下方式来检查行是否包含任何NAs:

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

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

sum(row.has.na)

并最终抛弃他们

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

对于过滤具有特定部分NAs的行,它变得有点棘手(例如,您可以将'Final[,5:6]'提要为'应用')。一般来说,Joris Meys的解决方案似乎更优雅。

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

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

使用上面的,这:

             gene hsap mmul mmus rnor cfam1 ENSG00000208234    0   NA   NA   NA   22 ENSG00000199674    0   2    2    2    23 ENSG00000221622    0   NA   NA   2   NA4 ENSG00000207604    0   NA   NA   1    25 ENSG00000207431    0   NA   NA   NA   NA6 ENSG00000221312    0   1    2    3    2

变成:

             gene hsap mmul mmus rnor cfam1 ENSG00000208234    0   NA   NA   NA   22 ENSG00000199674    0   2    2    2    23 ENSG00000221622    0   NA   NA   2   NA4 ENSG00000207604    0   NA   NA   1    26 ENSG00000221312    0   1    2    3    2

…其中仅删除了第5行,因为它是唯一包含rnorcfam的NAs的行。然后可以更改布尔逻辑以适应特定要求。

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

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

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

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

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

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

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

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

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

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

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

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

delete.na(final, 2)gene hsap mmul mmus rnor cfam2 ENSG00000199674    0    2    2    2    24 ENSG00000207604    0   NA   NA    1    26 ENSG00000221312    0    1    2    3    2

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

#' 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#' @exportez.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/#commentsdf.temp = df[,col,drop=FALSE]} else {df.temp = df}
if (length(n)==1){if (n==0) {# simply call complete.cases which might be fasterresult = df[complete.cases(df.temp),]} else {# credit: http://stackoverflow.com/a/30461945/2292993log <- 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)}

对于你的第一个问题,我有一个我很满意的代码来摆脱所有NAs。感谢@Gregor让它更简单。

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

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

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

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

tidyr有一个新函数#1

library(tidyr)df %>% drop_na()#              gene hsap mmul mmus rnor cfam# 2 ENSG00000199674    0    2    2    2    2# 6 ENSG00000221312    0    1    2    3    2df %>% 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

假设dat作为您的数据框,可以使用

1.rowSums

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

2.lapply

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

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

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

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

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

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

在vanilladata.frame上,#1#2#3快。请注意,na.omit.data.frame不支持cols=

基准测试结果

这是一个比较的基础(蓝色),dplyr(粉红色),和data.table(黄色)方法丢弃所有或选择缺失的观察,在名义数据集上的100万20个数字变量的观察,具有独立的5%的缺失可能性,以及第2部分的4个变量的子集。

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

注意y轴上的对数刻度。

在此处输入图片描述

基准脚本

#-------  Adjust these assumptions for your own use case  ------------row_size   <- 1e6Lcol_size   <- 20    # not including ID columnp_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-mingw32library(data.table); packageVersion('data.table') # 1.10.4.3library(dplyr);      packageVersion('dplyr')      # 0.7.4library(tidyr);      packageVersion('tidyr')      # 0.8.0library(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] <- NAreturn(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.framedf %>% 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)))
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', '?')

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

  m <- matrix(1:25, ncol = 5)m[c(1, 6, 13, 25)] <- NAdf <- 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

一种既通用又能产生相当可读代码的方法是使用{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 NAdf %>%filter(across(everything(),~ !is.na(.)))
# Filter only numeric columnsdf %>%filter(across(where(is.numeric),~ !is.na(.)))

类似地,dplyr包中也有变体函数(filter_allfilter_atfilter_if),它们完成了同样的事情:

library(dplyr)
vars_to_check <- c("rnor", "cfam")
# Filter a specific list of columns to keep only non-missing entriesdf %>%filter_at(.vars = vars(one_of(vars_to_check)),~ !is.na(.))
# Filter all the columns to exclude NAdf %>%filter_all(~ !is.na(.))
# Filter only numeric columnsdf %>%filter_if(is.numeric,~ !is.na(.))

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

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

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

这一行将只保留那些没有列具有NAs的行。

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

cols_to_check = c("rnor", "cfam")
df %>%filter(if_all(cols_to_check, ~ !is.na(.x)))

此行将检查是否有任何指定列(cols_to_check)具有NAs,并且仅保留那些不是这种情况的行。

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

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