【问题标题】:More Efficient Way To Do A Conditional Running Total In R在 R 中进行条件运行总计的更有效方法
【发布时间】:2014-03-07 19:17:18
【问题描述】:

由于这是我第一次在 SO 上提出问题,对于任何格式不当,我提前道歉。

我对 R 非常陌生,我正在尝试创建一个函数,一旦另一列中的运行总计达到或超过给定值(运行总和开始的行),该函数将返回数据框列的行值也是一个论点)。

例如,给定下面的数据框,如果给定一个起始参数x=3,终止参数y=17,函数应该返回5(y之和>=17所在行的x值) .

X   Y
1   5
2   10
3   5
4   10
5   5
6   10
7   5
8   10

我目前编写的函数返回正确答案,但我必须相信有一种更“R-ish”的方式来完成此任务,而不是使用循环和递增临时变量,并且希望学习正确的方法,而不是养成以后必须改正的坏习惯。

一个非常简化的函数版本:

myFunction<-function(DataFrame,StartRow,Total){
    df<-DataFrame[DataFrame[[1]] >= StartRow,]
    i<-0
    j<-0

    while (j < Total) {
        i<-i+1
        j<-sum(df[[2]][1:i])
    }

    x<-df[[1]][i]
    return(x)
}

【问题讨论】:

  • 我可能会说,在这里使用whilebreak 循环可能确实很有帮助,因为您希望事件第一次发生(尤其是对于大向量和早期发生)。您还可以避免一次又一次地计算 j,而是在循环中递增它。
  • 我下面的解决方案使用@alexis_laz 的打破循环的解决方案,并且基准测试确实表明它有助于大向量和早期出现。由于 R 中的循环效率低下,因此我使用 Rcpp 进行此计算。

标签: r loops cumulative-sum


【解决方案1】:

到目前为止发布的所有解决方案都会计算整个 Y 变量的累积和,这在数据框非常大但索引接近开头的情况下可能效率低下。在这种情况下,使用 Rcpp 的解决方案可能更有效:

library(Rcpp)
get_min_cum2 = cppFunction("
int gmc2(NumericVector X, NumericVector Y, int start, int total) {
    double running = 0.0;
    for (int idx=0; idx < Y.size(); ++idx) {
        if (X[idx] >= start) {
            running += Y[idx];
            if (running >= total) {
                return X[idx];
            }
        }
    }
    return -1;  // Running total never exceeds limit
}")

与微基准比较:

get_min_cum <- 
 function(start,total) 
   with(dat[dat$X>=start,],X[min(which(cumsum(Y)>total))])
get_min_dt <- function(start, total)
   dt[X >= start, X[cumsum(Y) >= total][1]]

set.seed(144)
dat = data.frame(X=1:1000000, Y=abs(rnorm(1000000)))
dt = data.table(dat)
get_min_cum(3, 17)
# [1] 29
get_min_dt(3, 17)
# [1] 29
get_min_cum2(dat$X, dat$Y, 3, 17)
# [1] 29

library(microbenchmark)
microbenchmark(get_min_cum(3, 17), get_min_dt(3, 17),
               get_min_cum2(dat$X, dat$Y, 3, 17))
# Unit: milliseconds
#                               expr        min         lq    median         uq      max neval
#                 get_min_cum(3, 17) 125.324976 170.052885 180.72279 193.986953 418.9554   100
#                  get_min_dt(3, 17) 100.990098 149.593250 162.24523 176.661079 399.7531   100
#  get_min_cum2(dat$X, dat$Y, 3, 17)   1.157059   1.646184   2.30323   4.628371 256.2487   100

在这种情况下,使用 Rcpp 解决方案的速度比其他方法快大约 100 倍。

【讨论】:

  • +1!我想这应该是有效的,因为它同时“cumsum”和“which”
  • @josilber 安装并加载 Rcpp 包后,尝试声明函数时出现以下错误: sourceCpp 中的错误(code = code,env = env,rebuild =rebuild,showOutput = showOutput, : 构建共享库时发生错误 1。警告:构建 R 包需要 Rtools,但当前未安装。请在继续之前下载并安装适当版本的 Rtools:使用 Rcpp 时是否缺少步骤?我不想要要构建一个包,只需在实例中声明并使用该函数。
  • 您可能需要重新启动才能获得更改:stackoverflow.com/questions/17619185/…
  • @josilber 您提供的链接导致了答案:我没有意识到使用 Rcpp 编写的函数需要 Rtools 才能编译。
【解决方案2】:

试试这个例如,我使用cumsum 和矢量化逻辑子集:

 get_min_cum <- 
 function(start,total) 
   with(dat[dat$X>=start,],X[min(which(cumsum(Y)>total))])

 get_min_cum(3,17) 
 5

【讨论】:

    【解决方案3】:

    给你(使用data.table,因为语法简单):

    library(data.table)
    dt = data.table(df)
    
    dt[X >= 3, X[cumsum(Y) >= 17][1]]
    #[1] 5
    

    【讨论】:

      【解决方案4】:

      好吧,这是一种方法:

      i <- 3
      j <- 17
      min(df[i:nrow(df),]$X[cumsum(df$Y[i:nrow(df)])>j])
      # [1] 5
      

      这需要df$Xi:nrow(df) 和基于cumsum(df$Y) &gt; j 的索引,也从第i 行开始。这将返回所有df$X,其中cumsum &gt; jmin(...) 然后返回最小值。

      【讨论】:

        【解决方案5】:
        with(df, which( cumsum( (x>=3)*y) >= 17)[1] )
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-03-20
          • 2015-08-16
          • 2014-06-14
          • 2013-04-27
          • 2021-09-22
          • 1970-01-01
          相关资源
          最近更新 更多