【问题标题】:R: fast (conditional) subsetting where feasibleR:在可行的情况下快速(条件)子集
【发布时间】:2019-12-17 05:11:44
【问题描述】:

我想对我的数据行进行子集化

library(data.table); set.seed(333); n <- 100
dat <- data.table(id=1:n, x=runif(n,100,120), y=runif(n,200,220), z=runif(n,300,320))

> head(dat)
   id        x        y        z
1:  1 109.3400 208.6732 308.7595
2:  2 101.6920 201.0989 310.1080
3:  3 119.4697 217.8550 313.9384
4:  4 111.4261 205.2945 317.3651
5:  5 100.4024 212.2826 305.1375
6:  6 114.4711 203.6988 319.4913

分几个阶段。我知道我可以按顺序申请subset(.) 来实现这一点。

> s <- subset(dat, x>119)
> s <- subset(s, y>219)
> subset(s, z>315)
   id        x        y        z
1: 55 119.2634 219.0044 315.6556

我的问题是我需要自动执行此操作,并且子集可能为空。在这种情况下,我想跳过导致空集的步骤。例如,如果我的数据是

dat2 <- dat[1:50]
> s <-subset(dat2,x>119)
> s
   id        x        y        z
1:  3 119.4697 217.8550 313.9384
2: 50 119.2519 214.2517 318.8567

第二步subset(s, y&gt;219) 会空出来,但我仍然想应用第三步subset(s,z&gt;315)。有没有办法仅在导致非空集时才应用子集命令?我想像subset(s, y&gt;219, nonzero=TRUE) 这样的东西。我想避免像

这样的结构
s <- dat
if(nrow(subset(s, x>119))>0){s <- subset(s, x>119)}
if(nrow(subset(s, y>219))>0){s <- subset(s, y>219)}
if(nrow(subset(s, z>318))>0){s <- subset(s, z>319)}

因为我担心 if-then 丛林会相当缓慢,特别是因为我需要使用 lapply(.) 将所有这些应用到列表中的不同 data.tables。这就是为什么我希望找到一个针对速度优化的解决方案。

PS。为了清楚起见,我只选择了subset(.),解决方案例如如果不是更多的话,data.table 也会受到欢迎。

【问题讨论】:

  • 如果初始数据集是data.table,为什么不使用data.table方法`dat[x > 119 & y > 219]`
  • 这会忽略会导致空集的条件元素吗?

标签: r data.table subset


【解决方案1】:

我同意 Konrad 的回答,即这应该发出警告或至少报告以某种方式发生的情况。这是一种利用索引的 data.table 方式(有关详细信息,请参阅包小插曲):

f = function(x, ..., verbose=FALSE){
  L   = substitute(list(...))[-1]
  mon = data.table(cond = as.character(L))[, skip := FALSE]

  for (i in seq_along(L)){
    d = eval( substitute(x[cond, verbose=v], list(cond = L[[i]], v = verbose)) )
    if (nrow(d)){
      x = d
    } else {
      mon[i, skip := TRUE]
    }    
  }
  print(mon)
  return(x)
}

用法

> f(dat, x > 119, y > 219, y > 1e6)
        cond  skip
1:   x > 119 FALSE
2:   y > 219 FALSE
3: y > 1e+06  TRUE
   id        x        y        z
1: 55 119.2634 219.0044 315.6556

verbose 选项将打印 data.table 包提供的额外信息,因此您可以查看何时使用索引。比如f(dat, x == 119, verbose=TRUE),我看到了。

因为我担心 if-then 丛林会相当缓慢,特别是因为我需要使用 lapply(.) 将所有这些应用到列表中的不同 data.tables。

如果它用于非交互式使用,最好让函数返回list(mon = mon, x = x),以便更轻松地跟踪查询内容和发生的情况。此外,可以捕获并返回详细的控制台输出。

【讨论】:

  • 如果速度很重要,这绝对是一种方法。使用options("datatable.verbose"=TRUE) 可以轻松检查索引的使用情况。
  • 这种方式似乎比 tidyverse 快一些,谢谢!
  • 一个后续问题:有没有办法将f(.) 应用于data.table 中的不同组?在我的实际问题中,dat 还包含一个分组变量,我想将f(.) 应用于dat,但在单独的data.table c 中为每个组使用截止值。有什么想法,还是我应该作为单独的问题发布?样本数据:library(data.table); set.seed(333); n &lt;- 100; dat &lt;- data.table(id=1:n, group=rep(1:2,each=n/2), x=runif(n,100,120), y=runif(n,200,220), z=runif(n,300,320)); c &lt;- data.table(group=1:2, x=c(110,119), y=c(210,219), z=c(310,319))
  • @bumblebee 我认为也许一个新问题会有意义。我假设交互式使用(键入y &gt; 10 等)写了我的答案,但在您的扩展下,我希望一种非常不同的方法是有意义的(以编程方式进行),其他人可能对此有更好的想法。
  • 5min 是乐观的,但现在在这里:stackoverflow.com/questions/57465187/…
【解决方案2】:

使用dplyr 中提供的修改后的filter 函数可以开发一种有趣的方法。如果条件不满足,non_empty_filter 过滤函数将返回原始数据集。

注意事项

  • 恕我直言,这是相当不标准的行为,应通过warning 报告。当然,这个可以去掉,对函数结果没有影响。

功能

library(tidyverse)
library(rlang) # enquo
non_empty_filter <- function(df, expr) {
    expr <- enquo(expr)

    res <- df %>% filter(!!expr)

    if (nrow(res) > 0) {
        return(res)
    } else {
        # Indicate that filter is not applied
        warning("No rows meeting conditon")
        return(df)
    }
}

条件满足

行为:返回满足条件的一行。

dat %>%
    non_empty_filter(x > 119 & y > 219)

结果

# id        x        y        z
# 1 55 119.2634 219.0044 315.6556

条件不满足

行为:由于y &gt; 1e6,不满足整个条件,返回完整数据集。

dat %>%
    non_empty_filter(x > 119 & y > 219 & y > 1e6)

结果

# id        x        y        z
# 1:   1 109.3400 208.6732 308.7595
# 2:   2 101.6920 201.0989 310.1080
# 3:   3 119.4697 217.8550 313.9384
# 4:   4 111.4261 205.2945 317.3651
# 5:   5 100.4024 212.2826 305.1375
# 6:   6 114.4711 203.6988 319.4913
# 7:   7 112.1879 209.5716 319.6732
# 8:   8 106.1344 202.2453 312.9427
# 9:   9 101.2702 210.5923 309.2864
# 10:  10 106.1071 211.8266 301.0645

一一满足/不满足条件

行为:跳过将返回空数据集的过滤器。

dat %>%
    non_empty_filter(y > 1e6) %>% 
    non_empty_filter(x > 119) %>% 
    non_empty_filter(y > 219)

结果

# id        x        y        z
# 1 55 119.2634 219.0044 315.6556

【讨论】:

    猜你喜欢
    • 2018-02-11
    • 2013-07-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-13
    • 2016-05-05
    • 1970-01-01
    • 2015-11-08
    相关资源
    最近更新 更多