首页 > 解决方案 > R - 从数据框中删除仅在数字列中包含零的行、基本 R 和管道友好方法?

问题描述

我想删除所有总和为 0 的行,但我在前 2 列中有因子列。我想出了一个 dplyr 解决方案,创建一个中间行和列,过滤掉总和为 0 的行,然后删除该行和列。

我想找到一种方法可以在不创建不必要的 rowsum 列的情况下使用基本 R 和 dplyr/tidyverse 管道友好方法。肯定有一个简单的单行代码可以实现这一点吗?

library(tidyverse)

df <- data.frame(person = rep(c("Ed", "Sue"), 6),
                id = paste0("plot",1:12),
                a = c(2, 0, 0, 0, 0, 1, 0, 0, 4, 0, 0, 0),
                b = c(0, 0, 6, 4, 0, 8, 1, 0, 0, 0, 1, 1),
                c = c(4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 8),
                d = c(0, 0, 0, 3, 0, 1, 0, 0, 9, 0, 1, 5),
                e = c(7, 0, 5, 0, 0, 1, 0, 0, 0, 0, 7, 0))


##create intermediate 'row.sum' column, filter rows that have all 0's, then remove row.sum column
df1 <- df %>% 
  dplyr::mutate(row.sum = a+b+c+d+e) %>% 
  dplyr::filter(row.sum != 0) %>% 
  dplyr::select(-row.sum)


#end result:
#  person     id a b c d e
#1     Ed  plot1 2 0 4 0 7
#2     Ed  plot3 0 6 0 0 5
#3    Sue  plot4 0 4 0 3 0
#4    Sue  plot6 1 8 0 1 1
#5     Ed  plot7 0 1 0 0 0
#6     Ed  plot9 4 0 0 9 0
#7     Ed plot11 0 1 3 1 7
#8    Sue plot12 0 1 8 5 0

标签: rdataframedplyr

解决方案


一个 dplyr 方法

您可以使用 dplyrsfilter()和使用across()helper仅将 rowSums 应用于数字列where(is.numeric)

library(dplyr)

df%>%filter(rowSums(across(where(is.numeric)))!=0)

  person     id a b c d e
1     Ed  plot1 2 0 4 0 7
2     Ed  plot3 0 6 0 0 5
3    Sue  plot4 0 4 0 3 0
4    Sue  plot6 1 8 0 1 1
5     Ed  plot7 0 1 0 0 0
6     Ed  plot9 4 0 0 9 0
7     Ed plot11 0 1 3 1 7
8    Sue plot12 0 1 8 5 0

rowSums()如果您的数字列也具有负值,则此方法(以及一些依赖于 的方法)可能会失败。在这种情况下,我们必须确保只保留至少包含any()非零值的行。这可以通过修改rowSums()以包含其中的条件来.x!=0完成across()

df%>%filter(rowSums(across(where(is.numeric), ~.x!=0))>0)

或使用逻辑运算符 and Reduce()/reduce(),使用以下代码:

library(dplyr)
library(purrr)

df%>%filter(pmap_lgl(select(., where(is.numeric)), ~any(c(...)!=0)))

#or with purrr:reduce()#

df%>%filter(across(where(is.numeric), ~.x!=0)%>%reduce(`|`))
#or simply
df%>%filter(reduce(across(where(is.numeric), ~.x!=0), `|`))

基础 R 方法

您可以使用 base subsetting with [, withsapply(f, is.numeric)创建一个逻辑索引以仅选择数字列以提供给不等式运算符!=,然后获取rowSums()最终创建的逻辑矩阵的 并仅选择 rowSums > 0 的行:

df[rowSums(df[,sapply(df, is.numeric)]!=0)>0,]

编辑

我们可以从在数字向量上调用逻辑函数所带来的强制中受益。as.logical()会将零评估为 FALSE,将任何非零数字评估为 TRUE。x|x嵌套!(!)的爆炸符号也会这样做。这与将元素与零进行比较的其他解决方案一致,因此比rowSums解决方案更一致。

一个例子:

vector<-c(0,1,2,-1)
identical(as.logical(vector), vector|vector, vector!=0, !(!vector))

[1] TRUE

考虑到这一点,有一些巧妙的方法可以解决这个问题:

df%>%filter(reduce(across(where(is.numeric), as.logical), `|`))
#or simply
df%>%filter(reduce(across(where(is.numeric)), `|`))
#and with base R:
df[Reduce(`|`, df[sapply(df, is.numeric)]),]

迄今为止最干净的,新的if_any()

df%>%filter(if_any(where(is.numeric)))

推荐阅读