使用dplyr

时间:2015-08-19 19:21:55

标签: r

我搜索过SO试图找到一个无济于事的解决方案。所以这就是。我有一个包含许多列的数据框,其中一些是数字的,应该是非负的。我想清理数据,因为这些数值列中的某些值是负数。我现在能做的是用正则表达式提取这些列的列名。但我不确定如何基于这些列实现行的过滤。

举个例子,让我们说:

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

我希望从此过滤中获得以下内容:

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中至少有一列包含负数。

7 个答案:

答案 0 :(得分:6)

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

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来查找是否存在任何匹配条件(&gt; 0 - 匹配,0-不匹配)。然后,我们使用!函数将其转换为逻辑向量:&gt; 0变为TRUE,而0变为FALSE。最后,我们根据该向量进行子集化。

答案 1 :(得分:4)

这是对dplyr的一种非常尴尬的用法,但对于精神

可能是正确的
> 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类似,则可以大大简化这一过程。

> 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:哎呀,误读图表,我是最快的,好的,不再是这个))

df %>% select(ends_with("_num")) %>% rowMins %>% {df[. >= 0,]}
编辑:出于好奇,对某些解决方案进行了一些微观标记(EDIT2:添加了更多解决方案)

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

以下是我使用的定义

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

答案 2 :(得分:4)

我希望看到使用dplyr filter_的标准评估是可行的。事实证明,可以在example code on this page之后,在 lazyeval interp的帮助下完成。实质上,您必须创建一个interp条件列表,然后将其传递给.dots的{​​{1}}参数。

filter_

更新

dplyr_0.7 开始,可以直接使用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 filter_at(不需要 lazyeval )来完成此操作。

all_vars

答案 3 :(得分:1)

使用基数R来获得结果

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

  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

编辑:这假设你有多个列&#34; _num&#34;。如果你只有一个_num列

,它就不会工作

答案 4 :(得分:1)

首先,我们创建所有数字列的索引。然后我们将所有列大于或等于零。因此,无需检查列名称,列ID将始终为正。

nums <- sapply(df, is.numeric)
df[apply(df[, nums], MARGIN = 1, function(x) all(x >= 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

答案 5 :(得分:1)

这是我丑陋的解决方案。建议/批评欢迎

df %>% 
  # Select the columns we want
  select(matches("_num$")) %>%
  # Convert every column to logical if >= 0
  lapply(">=", 0) %>%
  # Reduce all the sublist with AND 
  Reduce(f = "&", .) %>%
  # Convert the one vector of logical into numeric
  # index since slice can't deal with logical. 
  # Can simply write `{df[.,]}` here instead,
  # which is probably faster than which + slice
  # Edit: This is not true. which + slice is faster than `[` in this case
  which %>%
  slice(.data = df)

  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

答案 6 :(得分:0)

这将为您提供小于0的行的向量:

desired_rows <- sapply(target_columns, function(x) which(df[,x]<0), simplify=TRUE)
desired_rows <- as.vector(unique(unlist(desired_rows)))

然后获得所需行的df:

setdiff(df, df[desired_rows,])
  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