我在看似简单的任务时遇到了一些问题:使用dplyr删除所有变量为NA
的所有行。我知道可以使用基数R(Remove rows in R matrix where all data is NA和Removing 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)
但这看起来并不太好,尽管它完成了工作。其他想法?
答案 0 :(得分:15)
由于dplyr 0.7.0新的,存在范围过滤动词。使用filter_any,您可以轻松过滤至少包含一个非缺失列的行:
dat %>% filter_all(any_vars(!is.na(.)))
使用@hejseb基准测试算法,似乎此解决方案与f4一样高效。
答案 1 :(得分:9)
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))
使用Reduce
和lapply
似乎是最快的:
> 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
答案 2 :(得分:5)
从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
但是问题是要求逆集:用 all NA删除行。
setdiff
,或者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
函数将从描述“聚合逻辑”的参数中受益。它可以默认为“全部”并保留行为,或者允许“任意”,这样我们就不需要编写类似anyVar
的辅助函数。
答案 3 :(得分:4)
我建议在这里使用精美的看门人软件包。看门人非常人性化:
janitor::remove_empty(dat, which = "rows")
答案 4 :(得分:3)
dplyr 1.0.4 引入了 if_any()
和 if_all()
函数:
dat %>% filter(if_any(everything(), ~!is.na(.)))
或者,更详细:
dat %>% filter(if_any(everything(), purrr::negate(is.na)))
“获取数据并保留任何条目为非 NA 的所有行”
答案 5 :(得分:2)
以下是使用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 %>% map %>% unnest
模式如何工作以及用户可以逐行运行方面做得相当不错。找出正在发生的事情。
答案 6 :(得分:0)
使用dplyr 1.0的解决方案很简单,不需要辅助函数,只需要在正确的位置添加一个否定项即可。
dat %>% filter(!across(everything(), is.na))
答案 7 :(得分:0)
我在 dplyr 1.0.1 中的一个巧妙解决方案是使用 rowwise()
dat %>%
rowwise() %>%
filter(!all(is.na(across(everything())))) %>%
ungroup()
与@Callum Savage 对顶帖的评论非常相似,但我在第一遍时错过了它,而且没有 sum()