如何使用 dplyr 管道删除所有列为零的行

How to remove rows where all columns are zero using dplyr pipe

我有以下数据框:

dat <- structure(list(`A-XXX` = c(1.51653275922944, 0.077037240321129, 
0), `fBM-XXX` = c(2.22875185527511, 0, 0), `P-XXX` = c(1.73356698481106, 
0, 0), `vBM-XXX` = c(3.00397859609183, 0, 0)), .Names = c("A-XXX", 
"fBM-XXX", "P-XXX", "vBM-XXX"), row.names = c("BATF::JUN_AHR", 
"BATF::JUN_CCR9", "BATF::JUN_IL10"), class = "data.frame")

dat 
#>                     A-XXX  fBM-XXX    P-XXX  vBM-XXX
#> BATF::JUN_AHR  1.51653276 2.228752 1.733567 3.003979
#> BATF::JUN_CCR9 0.07703724 0.000000 0.000000 0.000000
#> BATF::JUN_IL10 0.00000000 0.000000 0.000000 0.000000

我可以使用以下命令删除所有列为零的行:

> dat <- dat[ rowSums(dat)!=0, ]
> dat
                    A-XXX  fBM-XXX    P-XXX  vBM-XXX
BATF::JUN_AHR  1.51653276 2.228752 1.733567 3.003979
BATF::JUN_CCR9 0.07703724 0.000000 0.000000 0.000000

但是我如何使用 dplyr 的管道样式来实现呢?

我们可以使用 purrr 中的 reduce 来获取行的总和和 filter 基于逻辑向量的数据集

library(tidyverse)
dat %>%
    reduce(`+`) %>%
    {. != 0} %>% 
   filter(dat, .)
#       A-XXX  fBM-XXX    P-XXX  vBM-XXX
#1 1.51653276 2.228752 1.733567 3.003979
#2 0.07703724 0.000000 0.000000 0.000000

注意:在 %>% 中,row.names 被剥离。最好创建一个新列或稍后分配 row.names


如果我们也需要行名,那么尽早创建一个行名列,然后在最后使用它来更改行名

dat %>%
  rownames_to_column('rn') %>%
  filter(rowSums(.[-1]) != 0) %>% 
  `row.names<-`(., .[['rn']]) %>% select(-rn)
#                   A-XXX  fBM-XXX    P-XXX  vBM-XXX
#BATF::JUN_AHR  1.51653276 2.228752 1.733567 3.003979
#BATF::JUN_CCR9 0.07703724 0.000000 0.000000 0.000000

这是一个 dplyr 选项:

library(dplyr)
filter_all(dat, any_vars(. != 0))

#       A-XXX  fBM-XXX    P-XXX  vBM-XXX
#1 1.51653276 2.228752 1.733567 3.003979
#2 0.07703724 0.000000 0.000000 0.000000

这里我们利用了如果任何变量不等于零,我们将保留它的逻辑。这与删除所有变量都为零的行相同。

关于row.names:

library(tidyverse)
dat %>% rownames_to_column() %>% filter_at(vars(-rowname), any_vars(. != 0))
#         rowname      A-XXX  fBM-XXX    P-XXX  vBM-XXX
#1  BATF::JUN_AHR 1.51653276 2.228752 1.733567 3.003979
#2 BATF::JUN_CCR9 0.07703724 0.000000 0.000000 0.000000

这是第三个选项,它使用 purrr::pmap 生成所有行是否为零的索引。绝对不如 filter_at 紧凑,但使用 pmap!

打开了有趣和复杂条件的选项
dat <- structure(list(`A-XXX` = c(1.51653275922944, 0.077037240321129, 
                                  0), `fBM-XXX` = c(2.22875185527511, 0, 0), `P-XXX` = c(1.73356698481106, 
                                                                                         0, 0), `vBM-XXX` = c(3.00397859609183, 0, 0)), .Names = c("A-XXX", 
                                                                                                                                                   "fBM-XXX", "P-XXX", "vBM-XXX"), row.names = c("BATF::JUN_AHR", 
                                                                                                                                                                                                 "BATF::JUN_CCR9", "BATF::JUN_IL10"), class = "data.frame")

library(tidyverse)
dat %>%
  rownames_to_column() %>%
  bind_cols(all_zero = pmap_lgl(., function(rowname, ...) all(list(...) == 0))) %>%
  filter(all_zero == FALSE) %>%
  `rownames<-`(.$rowname) %>%
  select(-rowname, -all_zero)
#>                     A-XXX  fBM-XXX    P-XXX  vBM-XXX
#> BATF::JUN_AHR  1.51653276 2.228752 1.733567 3.003979
#> BATF::JUN_CCR9 0.07703724 0.000000 0.000000 0.000000

reprex package (v0.2.0) 创建于 2018-03-14。

这是使用 dplyr 的 row-wise 操作的另一个选项(col1,col2,col3 定义了三个示例列,计算了按行求和):

library(tidyverse)

df <- df %>% 
    rowwise() %>% 
    filter(sum(c(col1,col2,col3)) != 0)

或者,如果您有大量变量(列)到 select,您还可以通过以下方式使用 tidyverse selection 语法:

df <- df %>% 
    rowwise() %>% 
    filter(sum(c_across(col1:col3)) != 0)

详情见:https://dplyr.tidyverse.org/articles/rowwise.html

添加@mgrund 的答案, dplyr 1.0.0 的较短替代方案是:

# Option A:
data %>% filter(across(everything(.)) != 0))

# Option B:
data %>% filter(across(everything(.), ~. == 0))

解释:
across() 检查每个 tidy_select 变量,即代表每一列的 everything()。在选项 A 中,如果不为零,则检查每一列,这在每一列中加起来是一整行零。在选项 B 中,在每一列上,应用公式 (~) 检查当前列是否为零。

编辑:
由于 filter 已经按行检查,因此您不需要 rowwise()。这对于 selectmutate 是不同的。

重要提示:
选项A中,写across(everything(.)) != 0
很关键 并不是 across(everything(.) != 0))!

原因:
across 需要一个 tidyselect 变量(此处为 everything()),而不是布尔值(即 everything(.) != 0)

您可以使用新的if_any()。我定制了一个在 if_any()

的文档中找到的示例
library(dplyr)
library(tibble)
dat <- structure(list(`A-XXX` = c(1.51653275922944, 0.077037240321129, 
                                  0), `fBM-XXX` = c(2.22875185527511, 0, 0), `P-XXX` = c(1.73356698481106, 
                                                                                         0, 0), `vBM-XXX` = c(3.00397859609183, 0, 0)), .Names = c("A-XXX", 
                                                                                                                                                   "fBM-XXX", "P-XXX", "vBM-XXX"), row.names = c("BATF::JUN_AHR", 
                                                                                                                                                                                                 "BATF::JUN_CCR9", "BATF::JUN_IL10"), class = "data.frame")
dat
#>                     A-XXX  fBM-XXX    P-XXX  vBM-XXX
#> BATF::JUN_AHR  1.51653276 2.228752 1.733567 3.003979
#> BATF::JUN_CCR9 0.07703724 0.000000 0.000000 0.000000
#> BATF::JUN_IL10 0.00000000 0.000000 0.000000 0.000000

dat %>% 
  rownames_to_column("ID") %>% 
  filter(if_any(!matches("ID"), ~ . != 0)) %>% 
  column_to_rownames("ID")
#>                     A-XXX  fBM-XXX    P-XXX  vBM-XXX
#> BATF::JUN_AHR  1.51653276 2.228752 1.733567 3.003979
#> BATF::JUN_CCR9 0.07703724 0.000000 0.000000 0.000000

reprex package (v1.0.0)

于 2021 年 4 月 12 日创建