41

我在一个看似简单的任务中遇到了一些问题:删除所有变量都NA使用 dplyr 的所有行。我知道可以使用基本 R 来完成(删除所有数据为 NA 的 R 矩阵中的行并删除 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()and do()

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

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

4

9 回答 9

66

由于 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(.)))
于 2018-05-02T09:36:51.723 回答
18

我建议在这里使用精彩的看门人包。Janitor 非常人性化:

janitor::remove_empty(dat, which = "rows")
于 2020-10-25T10:41:58.883 回答
14

基准测试

@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))

使用Reduceandlapply似乎是最快的:

> 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
于 2017-01-12T11:16:49.887 回答
8

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

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

但问题要求反向集:删除所有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

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

于 2020-07-10T08:06:02.910 回答
5

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

dat %>% filter(!across(everything(), is.na))
于 2020-10-25T10:11:17.390 回答
5

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 的所有行”

于 2021-03-09T14:09:53.557 回答
2

这是另一个使用purrr::map_lgl()and的解决方案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 %>% map %>% unnest模式的工作原理,并且用户可以逐行运行它以弄清楚发生了什么。

于 2018-03-04T00:30:18.783 回答
0

(tidyverse 1.3.1)

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

data%>%rowwise()%>%
filter(!all(is.na(c_across(starts_with("***")))))
于 2021-12-07T10:39:52.560 回答
0

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

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

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

于 2021-03-31T07:00:19.153 回答