在R语言中更高效地进行条件累加的方法

3
作为我第一次在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)
}

1
我认为在这里使用while或者break循环可能确实会有帮助,因为你想要找到事件的第一次出现(特别是在大向量和早期出现的情况下)。此外,你也可以避免反复计算j,而是在循环中递增它。 - alexis_laz
我的解决方案以下使用了 @alexis_laz 的跳出循环的方法,基准测试确实显示它可以帮助处理大向量和早期出现的情况。由于在 R 中进行循环效率低下,我使用了 Rcpp 进行计算。 - josliber
5个回答

4
到目前为止,所有发布的解决方案都计算了整个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”。 - alexis_laz
安装并加载 Rcpp 包后,当我尝试声明您的函数时出现以下错误:Error in sourceCpp(code = code, env = env, rebuild = rebuild, showOutput = showOutput, : Error 1 occurred building shared library.警告:构建 R 包需要 Rtools,但目前尚未安装。请在继续之前下载并安装适当版本的 Rtools:我在使用 Rcpp 时是否错过了什么步骤?我只想在实例中声明和使用函数,而不是构建一个包。 - user3351605
看起来你需要重新启动才能使更改生效:https://dev59.com/52Mm5IYBdhLWcg3wbujb - josliber
@josilber,您提供的链接指向了答案:我没有意识到使用Rcpp编写的函数需要Rtools才能编译。 - user3351605

1

例如,尝试使用 cumsum 和向量化逻辑子集:

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

 get_min_cum(3,17) 
 5

1

这里是使用 data.table 的代码(因为语法简单):

library(data.table)
dt = data.table(df)

dt[X >= 3, X[cumsum(Y) >= 17][1]]
#[1] 5

1

好的,这里有一种方法:

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

这个操作使用df$X作为行的范围,从inrow(df),并根据基于cumsum(df$Y) > j的索引,也从行i开始。它返回所有满足cumsum > jdf$X。然后min(...)返回最小值。

1
with(df, which( cumsum( (x>=3)*y) >= 17)[1] )

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接