【问题标题】:Remove columns from dataframe where ALL values are NA从数据框中删除所有值为 NA 的列
【发布时间】:2011-02-08 07:01:37
【问题描述】:

我在使用数据框时遇到了问题,我自己无法真正解决该问题:
数据框具有任意列属性每一行代表一个数据集

问题是:
如何删除 ALL 行的值为 NA 的列?

【问题讨论】:

    标签: r apply dataframe


    【解决方案1】:

    试试这个:

    df <- df[,colSums(is.na(df))<nrow(df)]
    

    【讨论】:

    • 这会创建一个与旧对象大小相同的对象,这是大型对象的内存问题。最好使用一个函数来减小大小。下面使用 Filter 或使用 data.table 的答案将有助于您的内存使用。
    • 这似乎不适用于非数字列。
    • 如果列名重复则更改列名
    • 要对非数字列执行此操作,@mnel 使用 Filter() 的解决方案是一个很好的解决方案。可以找到多种方法的基准in this post
    【解决方案2】:

    到目前为止,提供的两种方法在处理大型数据集时都失败了,因为(以及其他内存问题)它们创建了is.na(df),这将是一个与df 大小相同的对象。

    这里有两种更节省内存和时间的方法

    一种使用Filter的方法

    Filter(function(x)!all(is.na(x)), df)
    

    以及使用 data.table 的方法(用于一般时间和内存效率)

    library(data.table)
    DT <- as.data.table(df)
    DT[,which(unlist(lapply(DT, function(x)!all(is.na(x))))),with=F]
    

    使用大数据的示例(30 列,1e6 行)

    big_data <- replicate(10, data.frame(rep(NA, 1e6), sample(c(1:8,NA),1e6,T), sample(250,1e6,T)),simplify=F)
    bd <- do.call(data.frame,big_data)
    names(bd) <- paste0('X',seq_len(30))
    DT <- as.data.table(bd)
    
    system.time({df1 <- bd[,colSums(is.na(bd) < nrow(bd))]})
    # error -- can't allocate vector of size ...
    system.time({df2 <- bd[, !apply(is.na(bd), 2, all)]})
    # error -- can't allocate vector of size ...
    system.time({df3 <- Filter(function(x)!all(is.na(x)), bd)})
    ## user  system elapsed 
    ## 0.26    0.03    0.29 
    system.time({DT1 <- DT[,which(unlist(lapply(DT, function(x)!all(is.na(x))))),with=F]})
    ## user  system elapsed 
    ## 0.14    0.03    0.18 
    

    【讨论】:

    • 非常好。不过,你可以对data.frame 做同样的事情。这里没有什么真正需要data.table。关键是lapply,它避免了is.na(df)对整个对象的复制。 +10 指出这一点。
    • 你会如何使用 data.frame? @matt-dowle
    • @s_a, bd1 &lt;- bd[, unlist(lapply(bd, function(x), !all(is.na(x))))]
    • @mnel 我认为您需要在function(x) 之后删除, - 感谢您提供的示例
    • 你能用 := 或 set() 更快吗?
    【解决方案3】:

    更新

    您现在可以将selectwhere 选择助手一起使用。 select_if 已被取代,但从 dplyr 1.0.2 开始仍然有效。 (感谢@mcstrother 提请注意)。

    library(dplyr)
    temp <- data.frame(x = 1:5, y = c(1,2,NA,4, 5), z = rep(NA, 5))
    not_all_na <- function(x) any(!is.na(x))
    not_any_na <- function(x) all(!is.na(x))
    
    > temp
      x  y  z
    1 1  1 NA
    2 2  2 NA
    3 3 NA NA
    4 4  4 NA
    5 5  5 NA
    
    > temp %>% select(where(not_all_na))
      x  y
    1 1  1
    2 2  2
    3 3 NA
    4 4  4
    5 5  5
    
    > temp %>% select(where(not_any_na))
      x
    1 1
    2 2
    3 3
    4 4
    5 5
    

    旧答案

    dplyr 现在有一个 select_if 动词,可能在这里有用:

    > temp
      x  y  z
    1 1  1 NA
    2 2  2 NA
    3 3 NA NA
    4 4  4 NA
    5 5  5 NA
    
    > temp %>% select_if(not_all_na)
      x  y
    1 1  1
    2 2  2
    3 3 NA
    4 4  4
    5 5  5
    
    > temp %>% select_if(not_any_na)
      x
    1 1
    2 2
    3 3
    4 4
    5 5
    

    【讨论】:

    • 来到这里寻找dplyr 解决方案。没有失望。谢谢!
    • 我发现这有一个问题,它还会删除大多数但不是所有值都缺失的变量
    • select_if 现在在 dplyr 中被取代,所以最后两行在最新的语法中将是 temp %&gt;% select(where(not_all_na)) - 尽管 select_if 现在在 dplyr 1.0.2 中仍然有效。如果您不想在单独的行上定义函数,temp %&gt;% select(where(~!all(is.na(.x)))) 也可以使用。
    • @mcstrother 谢谢 - 这是对我的回答非常有用的更新。如果您想自己回答,我很乐意回滚编辑。
    • not_any_na 不适合我。这是从哪里来的?我有dplyr 加载.....
    【解决方案4】:

    另一种方法是使用apply() 函数。

    如果你有data.frame

    df <- data.frame (var1 = c(1:7,NA),
                      var2 = c(1,2,1,3,4,NA,NA,9),
                      var3 = c(NA)
                      )
    

    然后您可以使用 apply() 查看哪些列满足您的条件,因此您可以简单地执行与 Musa 的答案相同的子集,仅使用 apply 方法。

    > !apply (is.na(df), 2, all)
     var1  var2  var3 
     TRUE  TRUE FALSE 
    
    > df[, !apply(is.na(df), 2, all)]
      var1 var2
    1    1    1
    2    2    2
    3    3    1
    4    4    3
    5    5    4
    6    6   NA
    7    7   NA
    8   NA    9
    

    【讨论】:

    • 我预计这会更快,因为 colSum() 解决方案似乎做了更多的工作。但是在我的测试集上(之前的 1614 个变量中的 213 个,之后的 1377 个变量),它需要的时间正好是 3 倍。 (但是 +1 是一种有趣的方法。)
    【解决方案5】:

    游戏晚了,但您也可以使用janitor 包。此函数将删除所有为 NA 的列,并且可以更改为删除所有为 NA 的行。

    df &lt;- janitor::remove_empty(df, which = "cols")

    【讨论】:

      【解决方案6】:
      df[sapply(df, function(x) all(is.na(x)))] <- NULL
      

      【讨论】:

        【解决方案7】:

        purrr 包的另一个选项:

        library(dplyr)
        
        df <- data.frame(a = NA,
                         b = seq(1:5), 
                         c = c(rep(1, 4), NA))
        
        df %>% purrr::discard(~all(is.na(.)))
        df %>% purrr::keep(~!all(is.na(.)))
        

        【讨论】:

          【解决方案8】:

          你可以使用 Janitor 包remove_empty

          library(janitor)
          
          df %>%
            remove_empty(c("rows", "cols")) #select either row or cols or both
          

          另外,另一种 dplyr 方法

           library(dplyr) 
           df %>% select_if(~all(!is.na(.)))
          

          df %>% select_if(colSums(!is.na(.)) == nrow(df))
          

          如果您只想排除/保留具有一定数量缺失值的列,这也很有用,例如

           df %>% select_if(colSums(!is.na(.))>500)
          

          【讨论】:

            【解决方案9】:

            一个老问题,但我认为我们可以用更简单的 data.table 解决方案更新@mnel 的好答案:

            DT[, .SD, .SDcols = \(x) !all(is.na(x))]

            (我正在使用 R>=4.1 中可用的新 \(x) lambda 函数语法,但真正关键的是通过 .SDcols 传递逻辑子集。

            速度相当。

            microbenchmark::microbenchmark(
              which_unlist = DT[, which(unlist(lapply(DT, \(x) !all(is.na(x))))), with=FALSE],
              sdcols       = DT[, .SD, .SDcols = \(x) !all(is.na(x))],
              times = 2
            )
            #> Unit: milliseconds
            #>          expr      min       lq     mean   median       uq      max neval cld
            #>  which_unlist 51.32227 51.32227 56.78501 56.78501 62.24776 62.24776     2   a
            #>        sdcols 43.14361 43.14361 49.33491 49.33491 55.52621 55.52621     2   a
            

            【讨论】:

              【解决方案10】:

              我希望这也能有所帮助。它可以做成一个命令,但我发现把它分成两个命令更容易阅读。我按照以下说明制作了一个函数,并且工作得很快。

              naColsRemoval = function (DataTable) { na.cols = DataTable [ , .( which ( apply ( is.na ( .SD ) , 2 , all ) ) )] DataTable [ , unlist (na.cols) := NULL , with = F] }

              .SD 将允许将验证限制在表格的一部分,如果您愿意,但它会将整个表格作为

              【讨论】:

                【解决方案11】:

                一个方便的base R 选项可以是colMeans()

                df[, colMeans(is.na(df)) != 1]
                

                【讨论】:

                  【解决方案12】:

                  根据我在应用以前的答案时遇到问题的经验,我发现我需要修改他们的方法才能实现这里的问题:

                  如何删除所有行的值为 NA 的列?

                  首先请注意,我的解决方案仅在您没有重复列时才有效(该问题已处理 here (on stack overflow)

                  其次,它使用dplyr

                  代替

                  df <- df %>% select_if(~all(!is.na(.)))
                  

                  我发现有效的是

                  df <- df %>% select_if(~!all(is.na(.)))
                  

                  关键是“非”符号“!”需要在全称量词之外。 IE。 select_if 运算符作用于列。在这种情况下,它只选择那些满足标准的

                  每个元素都等于“NA”

                  【讨论】:

                    【解决方案13】:

                    janitor::remove_constant() 做得很好。

                    【讨论】:

                    • janitor::remove_empty() 在这里更合适。 ?remove_empty = "从 data.frame 或矩阵中删除空行和/或列"
                    猜你喜欢
                    • 2017-06-13
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2022-09-22
                    • 1970-01-01
                    • 1970-01-01
                    • 2021-05-07
                    相关资源
                    最近更新 更多