【问题标题】:Remove rows with all or some NAs (missing values) in data.frame删除 data.frame 中具有全部或部分 NA(缺失值)的行
【发布时间】:2016-04-20 09:12:31
【问题描述】:

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

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

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

【问题讨论】:

    标签: r dataframe filter missing-data r-faq


    【解决方案1】:

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

    【讨论】:

      【解决方案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 更适合删除所有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 会更清晰、更快。

      【讨论】:

      • final[complete.cases(final),]后面的逗号是什么意思?
      • complete.cases(final) 返回一个布尔值的行,其中没有NA,如(TRUE, FALSE, TRUE)。尾随逗号表示所有列。因此,在逗号之前过滤行,但在逗号之后,您不进行过滤并要求所有内容
      • 感谢您提供此解决方案,我不知道我们可以使用 complete.cases 语句指定列。
      【解决方案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 的解决方案似乎更优雅。

      【讨论】:

      • 这非常慢。比例如慢得多前面提到的 complete.cases() 解决方案。至少,就我而言,在 xts 数据上。
      • rowSum(!is.na(final)) 似乎比 apply() 更适合
      • 不应该是final[rowSum(is.na(final)),]吗?
      【解决方案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 行,因为它是唯一包含 rnorcfam 的 NA 的行。然后可以更改布尔逻辑以适应特定要求。

      【讨论】:

      • 但是如果你想检查很多列而不输入每一列,你可以使用范围 final[,4:100] 吗?
      【解决方案5】:

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

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

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

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

      【讨论】:

      • 这太完美了,我实际上在寻找一种类似的解决方案,它可以让我们只保留那些具有一个或多个非 NA 值的行(与列无关)。但是,此代码不适用于我的情况。它保留所有行,甚至那些具有所有 NA 的行。
      【解决方案6】:

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

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

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

      【讨论】:

        【解决方案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
        

        【讨论】:

        • 这是最可靠的删除行的方法,当您需要至少多个 NA 才能删除该行时。帮了我很多!
        【解决方案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)
        }
        

        【讨论】:

          【解决方案9】:

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

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

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

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

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

          【讨论】:

          • final[as.logical((rowSums(is.na(final))-ncol(final))),] 通用答案
          【解决方案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
          

          【讨论】:

          • drop_na() 比 na.omit() 有什么优势?更快?
          • 当我尝试此命令时 df %>% drop_na(rnor, cfam) 出现类似这样的错误 错误:无法对不存在的列进行子集化。 x 列 rnor 不存在。为什么?
          • rnor 应该是表中的列名
          • 非常感谢!
          【解决方案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
          

          【讨论】:

            【解决方案12】:

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

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

            【讨论】:

            • 这比drop_na()慢了大约10.000倍
            • @Zimano 也许是真的,但对于多个变量drop_na 使用“任何”逻辑,filter 使用“所有”逻辑。所以如果你需要更灵活的表达方式,filter有更多的可能。
            • @jiggunjer 这绝对是真的!这真的取决于你想要达到的目标:)
            【解决方案13】:

            如果性能优先,请使用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))
            )
            

            【讨论】:

              【解决方案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)
              

              Above 函数从数据框中删除任何列中包含“NA”的所有行并返回结果数据。如果要检查多个值,例如 NA?,请将函数参数中的 dart=c('NA') 更改为 dart=c('NA', '?')

              【讨论】:

                【解决方案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
                

                【讨论】:

                • 这将保留带有NA 的行。我认为 OP 想要的是:df %&gt;% filter_all(all_vars(!is.na(.)))
                【解决方案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 包中也有变体函数(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(.))
                

                【讨论】:

                • 查看here 以获取另一个使用across 的示例
                【解决方案17】:

                dplyr 1.0.4 为filter 引入了两个伴随函数:它们是if_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,并且只保留那些不是这种情况的行。

                【讨论】:

                  猜你喜欢
                  • 2016-08-20
                  • 2021-02-24
                  • 2022-06-13
                  • 2020-05-09
                  • 1970-01-01
                  • 2021-10-06
                  • 2019-08-08
                  相关资源
                  最近更新 更多