首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >使用dplyr对多列进行多条件过滤

使用dplyr对多列进行多条件过滤
EN

Stack Overflow用户
提问于 2015-08-19 19:21:55
回答 7查看 16.3K关注 0票数 12

我一直在寻找解决办法,但没有结果。所以就是这样了。我有一个包含许多列的数据框架,其中一些列是数字的,应该是非负的。我想清理这些数据,因为这些数字列中的一些值是负值。我现在可以做的是用正则表达式提取这些列的列名。但是,我不知道如何实现基于这些列的行筛选。

举个例子,比方说:

代码语言:javascript
复制
library(dplyr)
df <- read.table(text = 
  "id   sth1    tg1_num   sth2    tg2_num    others   
  1     dave    2         ca      35         new
  2     tom     5         tn      -3         old
  3     jane    -3        al       0         new
  4     leroy   0         az      25         old
  5     jerry   4         mi      55        old", header=TRUE)
pattern <- "_num$"
ind <- grep(pattern, colnames(df))
target_columns <- colnames(df)[ind]
df <- df %>% filter(target_columns >= 0) # it's is wrong, but it's what I want to do

我想从这个过滤中得到以下内容:

代码语言:javascript
复制
id   sth1 tg1_num   sth2 tg2_num others
1    dave       2     ca      35    new
4   leroy       0     az      25    old
5   jerry       4     mi      55    old

其中第2行和第3行被过滤掉,因为这些行的tg1_num和tg2_num中至少有一列包含负数。

EN

回答 7

Stack Overflow用户

回答已采纳

发布于 2015-08-19 20:21:37

这是一个非常笨拙的使用dplyr,但可能是真正的精神

代码语言:javascript
复制
> df %>% mutate(m = do.call(pmin, select(df, ends_with("_num"))))
  id  sth1 tg1_num sth2 tg2_num others  m
1  1  dave       2   ca      35    new  2
2  2   tom       5   tn      -3    old -3
3  3  jane      -3   al       0    new -3
4  4 leroy       0   az      25    old  0
5  5 jerry       4   mi      55    old  4

在那里,您可以添加一个filter(m >= 0)来得到您想要的答案。如果有一个类似于rowMinsrowMeans,那么这将大大简化这一点。

代码语言:javascript
复制
> rowMins <- function(df) { do.call(pmin, df) }
> df %>% mutate(m = rowMins(select(df, ends_with("_num"))))
  id  sth1 tg1_num sth2 tg2_num others  m
1  1  dave       2   ca      35    new  2
2  2   tom       5   tn      -3    old -3
3  3  jane      -3   al       0    new -3
4  4 leroy       0   az      25    old  0
5  5 jerry       4   mi      55    old  4

不过,我不知道这有多有效。而筑巢的select看起来真的很丑陋。

EDIT3:使用其他解决方案/评论(h/t到@Vlo)中的想法,我可以加快我的速度(不幸的是,类似的优化会使@Vlo的解决方案更快(EDIT4:哎呀,误读图表,我是最快的,好的,不再在这个问题上了)

代码语言:javascript
复制
df %>% select(ends_with("_num")) %>% rowMins %>% {df[. >= 0,]}

编辑:出于好奇,在一些解决方案上做了一些微基准测试(EDIT2:添加了更多的解决方案)

代码语言:javascript
复制
microbenchmark(rowmins(df), rowmins2(df), reducer(df), sapplyer(df), grepapply(df), tchotchke(df), withrowsums(df), reducer2(df))

Unit: microseconds
            expr       min         lq      mean    median        uq       max
     rowmins(df)  1373.452  1431.9700  1732.188  1576.043  1729.410  5147.847
    rowmins2(df)   836.885   875.9900  1015.364   913.285  1038.729  2510.339
     reducer(df)   990.096  1058.6645  1217.264  1201.159  1297.997  3103.809
    sapplyer(df) 14119.236 14939.8755 16820.701 15952.057 16612.709 66023.721
   grepapply(df) 12907.657 13686.2325 14517.140 14485.520 15146.294 17291.779
   tchotchke(df)  2770.818  2939.6425  3114.233  3036.926  3172.325  4098.161
 withrowsums(df)  1526.227  1627.8185  1819.220  1722.430  1876.360  3025.095
    reducer2(df)   900.524   943.1265  1087.025  1003.820  1109.188  3869.993

以下是我所用的定义

代码语言:javascript
复制
rowmins <- function(df) {
  df %>%
    mutate(m = rowMins(select(df, ends_with("_num")))) %>%
    filter(m >= 0) %>%
    select(-m)
}

rowmins2 <- function(df) {
  df %>% select(ends_with("_num")) %>% rowMins %>% {df[. >= 0,]}
}

reducer <- function(df) {
  df %>%
    select(matches("_num$")) %>%
    lapply(">=", 0) %>%
    Reduce(f = "&", .) %>%
    which %>%
    slice(.data = df)
}

reducer2 <- function(df) {
  df %>%
    select(matches("_num$")) %>%
    lapply(">=", 0) %>%
    Reduce(f = "&", .) %>%
    {df[.,]}
}

sapplyer <- function(df) {
  nums <- sapply(df, is.numeric)
  df[apply(df[, nums], MARGIN=1, function(x) all(x >= 0)), ]
}

grepapply <- function(df) {
  cond <- df[, grepl("_num$", colnames(df))] >= 0
    df[apply(cond, 1, function(x) {prod(x) == 1}), ]
}

tchotchke <- function(df) {
  pattern <- "_num$"
  ind <- grep(pattern, colnames(df))
  target_columns <- colnames(df)[ind]
  desired_rows <- sapply(target_columns, function(x) which(df[,x]<0), simplify=TRUE)
  as.vector(unique(unlist(desired_rows)))
}

withrowsums <- function(df) {
  df %>% mutate(m=rowSums(select(df, ends_with("_num"))>0)) %>% filter(m==2) %>% select(-m)
}


df <- data.frame(id=1:10000, sth1=sample(LETTERS, 10000, replace=T), tg1_num=runif(10000,-1,1), tg2_num=runif(10000,-1, 1))
票数 4
EN

Stack Overflow用户

发布于 2015-08-19 20:55:54

这里有一个可能的矢量化解决方案

代码语言:javascript
复制
ind <- grep("_num$", colnames(df))
df[!rowSums(df[ind] < 0),]
#   id  sth1 tg1_num sth2 tg2_num others
# 1  1  dave       2   ca      35    new
# 4  4 leroy       0   az      25    old
# 5  5 jerry       4   mi      55    old

这里的思想是使用<函数创建一个逻辑矩阵(它是一个具有data.frame方法的泛型函数,这意味着它返回类似于结构的数据帧)。然后,我们使用rowSums来查找是否存在任何匹配条件(> 0匹配,0不匹配)。然后,我们使用!函数将其转换为逻辑向量:>0变为TRUE,而0变为FALSE。最后,我们将根据这个向量进行细分。

票数 6
EN

Stack Overflow用户

发布于 2015-08-19 21:22:33

我希望看到使用dplyr的filter_进行标准评估是可能的。结果表明,这是可以在interp的帮助下完成的,来自lazyeval,遵循此页上的示例代码。本质上,您必须创建一个interp条件列表,然后将其传递给filter_.dots参数。

代码语言:javascript
复制
library(lazyeval)

dots <- lapply(target_columns, function(cols){
    interp(~y >= 0, .values = list(y = as.name(cols)))
})

filter_(df, .dots = dots)   

  id  sth1 tg1_num sth2 tg2_num others
1  1  dave       2   ca      35    new
2  4 leroy       0   az      25    old
3  5 jerry       4   mi      55    old

更新

从dplyr_0.7开始,这可以直接用filter_atall_vars来完成(不需要懒虫)。

代码语言:javascript
复制
df %>%
     filter_at(vars(target_columns), all_vars(. >= 0) )

  id  sth1 tg1_num sth2 tg2_num others
1  1  dave       2   ca      35    new
2  4 leroy       0   az      25    old
3  5 jerry       4   mi      55    old
票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/32103943

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档