【问题标题】:Remove rows where all variables are NA using dplyr使用 dplyr 删除所有变量为 NA 的行
【发布时间】:2017-05-27 08:49:19
【问题描述】:

我在执行一项看似简单的任务时遇到了一些问题:使用 dplyr 删除 all 变量为 NA 的所有行。我知道可以使用 base R(Remove rows in R matrix where all data is NARemoving empty rows of a data file in R)来完成,但我很想知道是否有使用 dplyr 的简单方法。

例子:

library(tidyverse)
dat <- tibble(a = c(1, 2, NA), b = c(1, NA, NA), c = c(2, NA, NA))
filter(dat, !is.na(a) | !is.na(b) | !is.na(c))

上面的filter 调用可以满足我的需求,但在我面临的情况下它是不可行的(因为存在大量变量)。我想可以通过使用filter_ 并首先使用(长)逻辑语句创建一个字符串来做到这一点,但似乎应该有一种更简单的方法。

另一种方法是使用rowwise()do()

na <- dat %>% 
  rowwise() %>% 
  do(tibble(na = !all(is.na(.)))) %>% 
  .$na
filter(dat, na)

但这看起来不太好,尽管它完成了工作。其他想法?

【问题讨论】:

  • 可以dat %&gt;% filter(rowSums(is.na(.)) != ncol(.)) 或者dat %&gt;% filter(rowMeans(is.na(.)) &lt; 1)
  • dat %&gt;% filter(Reduce(`+`, lapply(., is.na)) != ncol(.)),它们基本上都是基本R结合filter的实现。我认为tidyverse 中没有内置任何东西可以非常有效地做到这一点
  • 不过,Hadley 可能会建议使用长格式,例如 dat %&gt;% mutate(indx = row_number()) %&gt;% gather(var, val, -indx) %&gt;% group_by(indx) %&gt;% filter(sum(is.na(val)) != n()) %&gt;% spread(var, val)
  • @DavidArenburg 谢谢,这些都是不错的建议!我感觉会有某种all 函数,比如filter(dat, !all_na()),但我猜不会。
  • 如果还没有,说不定哪天会有。

标签: r dplyr tidyverse


【解决方案1】:

(tidyverse 1.3.1)

data%>%rowwise()%>%
filter(!all(is.na(c_across(is.numeric))))

data%>%rowwise()%>%
filter(!all(is.na(c_across(starts_with("***")))))

【讨论】:

  • 正如目前所写,您的答案尚不清楚。请edit 添加其他详细信息,以帮助其他人了解这如何解决所提出的问题。你可以找到更多关于如何写好答案的信息in the help center
【解决方案2】:

dplyr 1.0.4 引入了if_any()if_all() 函数:

dat %>% filter(if_any(everything(), ~!is.na(.)))

或者,更详细:

dat %>% filter(if_any(everything(), purrr::negate(is.na)))

“获取 dat 并保留任何条目为非 NA 的所有行”

【讨论】:

    【解决方案3】:

    从 dplyr 0.7.0 开始,存在范围过滤动词。使用 filter_any,您可以轻松地过滤至少有一个非缺失列的行:

    # dplyr 0.7.0
    dat %>% filter_all(any_vars(!is.na(.)))
    

    使用@hejseb 基准测试算法,该解决方案似乎与 f4 一样高效。

    更新:

    自 dplyr 1.0.0 起,上述范围动词被取代。相反,引入了跨函数族,它允许在多个(或所有)列上执行一个函数。过滤至少一列不是 NA 的行现在看起来像这样:

    # dplyr 1.0.0
    dat %>% filter(if_any(everything(), ~ !is.na(.)))
    

    【讨论】:

    • 在我看来,这是删除所有行的最直观的解决方案。此外,值得一提的是,当您想要检测所有行时,您必须使用 all_vars() 而不是 any_vars(),如 dat %&gt;% filter_all(all_vars(is.na(.)))
    • 在 dplyr 1.0 中,filter_allany_vars 都已被取代,据我所知,any_vars 没有替代品。 colwise vignette 中建议的选项是定义您自己的助手,例如 rowAny &lt;- function(x) rowSums(x) &gt; 0,以便上述解决方案变为 dat %&gt;% filter(rowAny(across(everything(), ~ !is.na(.x))))
    • dplyr 1.0 中的另一种选择可能是 dat %&gt;% rowwise() %&gt;% filter(sum(is.na(c_across(everything()))) != ncol(.)) %&gt;% ungroup(),尽管可能有更优雅的方式来实现这一点。
    • colwise 小插图(现在?)提到了以下方法:dat %&gt;% filter(if_any(everything(), ~ !is.na(.x))),它“保留 至少一个 选定列的谓词为真的行”。 (请参阅下面的 shosaco 方式的答案)
    【解决方案4】:

    我在 dplyr 1.0.1 中工作的一个巧妙的解决方案是使用 rowwise()

    dat %>%
      rowwise() %>%
      filter(!all(is.na(across(everything())))) %>%
      ungroup()
    

    与@Callum Savage 对置顶帖子的评论非常相似,但我在第一遍时错过了它,并且没有 sum()

    【讨论】:

      【解决方案5】:

      使用 dplyr 1.0 的解决方案很简单,不需要辅助函数,你只需要在正确的地方添加一个否定。

      dat %>% filter(!across(everything(), is.na))
      

      【讨论】:

      • 更短:dat %>% filter(!across(everything(), is.na))
      • @mharinga 是的,我想明确一点,但我会根据你的建议编辑答案
      • 这不会回答所提出的问题,但会删除 any 列包含 NA 的所有行,而不仅仅是那些 all 列包含 NA 的行
      【解决方案6】:

      我建议在这里使用美妙的看门人包。看门人非常人性化:

      janitor::remove_empty(dat, which = "rows")
      

      【讨论】:

        【解决方案7】:

        从 dyplr 1.0 开始,colwise vignette 给出了一个类似的例子:

        filter(across(everything(), ~ !is.na(.x))) #Remove rows with *any* NA
        

        我们可以看到它使用与多个表达式相同的隐式“& 逻辑”filter。所以下面的小调整会选择所有 NA 行:

        filter(across(everything(), ~ is.na(.x))) #Remove rows with *any* non-NA
        

        但问题要求反向集:删除行 all NA。

        1. 我们可以使用前面的方法做一个简单的setdiff,或者
        2. 我们可以利用across 返回一个逻辑小标题而filter 有效地执行逐行all()(即&)这一事实。

        例如:

        rowAny = function(x) apply(x, 1, any)
        anyVar = function(fcn) rowAny(across(everything(), fcn)) #make it readable
        df %<>% filter(anyVar(~ !is.na(.x))) #Remove rows with *all* NA
        

        或者:

        filterout = function(df, ...) setdiff(df, filter(df, ...))
        df %<>% filterout(across(everything(), is.na)) #Remove rows with *all* NA
        

        或者甚至结合上面2个更直接的表达第一个例子:

        df %<>% filterout(anyVar(~ is.na(.x))) #Remove rows with *any* NA
        

        在我看来,tidyverse filter 函数将受益于描述“聚合逻辑”的参数。它可以默认为“all”并保留行为,或者允许“any”,这样我们就不需要编写类似anyVar 的辅助函数。

        【讨论】:

        • 谢谢,使用 setdiff 的过滤功能可以正常工作。只需要小心,因为它也会删除任何重复的行。为了避免这种情况,我们可以使用 dplyr 的 anti_join filterout = function(df, ...) anti_join(df, filter(df, ...))
        【解决方案8】:

        这是另一个使用purrr::map_lgl()tidyr::nest() 的解决方案:

        library(tidyverse)
        
        dat <- tibble(a = c(1, 2, NA), b = c(1, NA, NA), c = c(2, NA, NA))
        
        any_not_na <- function(x) {
          !all(map_lgl(x, is.na))
        }
        
        
        dat_cleaned <- dat %>%
          rownames_to_column("ID") %>%
          group_by(ID) %>%
          nest() %>%
          filter(map_lgl(data, any_not_na)) %>%
          unnest() %>%
          select(-ID)
        ## Warning: package 'bindrcpp' was built under R version 3.4.2
        
        dat_cleaned
        ## # A tibble: 2 x 3
        ##       a     b     c
        ##   <dbl> <dbl> <dbl>
        ## 1    1.    1.    2.
        ## 2    2.   NA    NA
        

        我怀疑这种方法能否与@hejseb 答案中的基准竞争,但我认为它很好地展示了nest %&gt;% map %&gt;% unnest 模式的工作原理,并且用户可以逐行运行它以弄清楚发生了什么。

        【讨论】:

          【解决方案9】:

          基准测试

          @DavidArenburg 提出了一些替代方案。这是对它们的简单基准测试。

          library(tidyverse)
          library(microbenchmark)
          
          n <- 100
          dat <- tibble(a = rep(c(1, 2, NA), n), b = rep(c(1, 1, NA), n))
          
          f1 <- function(dat) {
            na <- dat %>% 
              rowwise() %>% 
              do(tibble(na = !all(is.na(.)))) %>% 
              .$na
            filter(dat, na)
          }
          
          f2 <- function(dat) {
            dat %>% filter(rowSums(is.na(.)) != ncol(.))
          }
          
          f3 <- function(dat) {
            dat %>% filter(rowMeans(is.na(.)) < 1)
          }
          
          f4 <- function(dat) {
            dat %>% filter(Reduce(`+`, lapply(., is.na)) != ncol(.))
          }
          
          f5 <- function(dat) {
            dat %>% mutate(indx = row_number()) %>% gather(var, val, -indx) %>% group_by(indx) %>% filter(sum(is.na(val)) != n()) %>% spread(var, val) 
          }
          
          # f1 is too slow to be included!
          microbenchmark(f2 = f2(dat), f3 = f3(dat), f4 = f4(dat), f5 = f5(dat))
          

          使用Reducelapply 似乎是最快的:

          > microbenchmark(f2 = f2(dat), f3 = f3(dat), f4 = f4(dat), f5 = f5(dat))
          Unit: microseconds
           expr        min          lq       mean      median         uq        max neval
             f2    909.495    986.4680   2948.913   1154.4510   1434.725 131159.384   100
             f3    946.321   1036.2745   1908.857   1221.1615   1805.405   7604.069   100
             f4    706.647    809.2785   1318.694    960.0555   1089.099  13819.295   100
             f5 640392.269 664101.2895 692349.519 679580.6435 709054.821 901386.187   100
          

          使用更大的数据集107,880 x 40

          dat <- diamonds
          # Let every third row be NA
          dat[seq(1, nrow(diamonds), 3), ]  <- NA
          # Add some extra NA to first column so na.omit() wouldn't work
          dat[seq(2, nrow(diamonds), 3), 1] <- NA
          # Increase size
          dat <- dat %>% 
            bind_rows(., .) %>%
            bind_cols(., .) %>%
            bind_cols(., .)
          # Make names unique
          names(dat) <- 1:ncol(dat)
          microbenchmark(f2 = f2(dat), f3 = f3(dat), f4 = f4(dat))
          

          f5 太慢所以也被排除在外。 f4 似乎比以前做得更好。

          > microbenchmark(f2 = f2(dat), f3 = f3(dat), f4 = f4(dat))
          Unit: milliseconds
           expr      min       lq      mean    median       uq      max neval
             f2 34.60212 42.09918 114.65140 143.56056 148.8913 181.4218   100
             f3 35.50890 44.94387 119.73744 144.75561 148.8678 254.5315   100
             f4 27.68628 31.80557  73.63191  35.36144 137.2445 152.4686   100
          

          【讨论】:

          • 不知道在f4中使用purrr函数会不会影响速度? filter(reduce(map., is.na), `+`) != ncol(.)) 可以说是“更整洁”
          • 我的机器在原始 f4 和我上面建议的 purr-ified 版本之间的相同大钻石数据集上获得大致相同的性能。
          猜你喜欢
          • 1970-01-01
          • 2018-10-12
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-02-24
          • 2015-05-05
          • 2020-10-04
          • 2017-07-15
          相关资源
          最近更新 更多