【问题标题】:Most efficient way to loop through each observation in a data frame循环遍历数据框中每个观察值的最有效方法
【发布时间】:2014-10-14 20:58:24
【问题描述】:

我正在尝试找到最有效的方法来循环遍历数据框并按 5 组进行聚类观察。例如,如果我有:

group <- c(1,2,3,4,5,6,7,8,9,10)
people <- c(1,2,3,4,4,3,2,1,2,3)
avg_age <- c(5,10,15,20,25,30,35,40,45,50)
data <- data.frame(group,people,age)

这应该会生成

   group people avg_age
1      1      1   5
2      2      2  10
3      3      3  15
4      4      4  20
5      5      4  25
6      6      3  30
7      7      2  35
8      8      1  40
9      9      1  45
10    10      2  50

然后,我想创建另一个包含至少 5 人的群组“集群”,并为“集群”设置加权平均年龄。但我想以最有效的方式执行此操作,方法是遍历数据集并按顺序添加组,直到组成一个至少有 5 人的“集群”。我们的数据应该如下所示:

   group people age cluster tot_ppl avg_age
1      1      1   5       1       6   11.67
2      2      2  10       1       6   11.67
3      3      3  15       1       6   11.67
4      4      4  20       2       8    22.5
5      5      4  25       2       8    22.5
6      6      3  30       3       5      32
7      7      2  35       3       5      32
8      8      1  40       4       6   46.67
9      9      2  45       4       6   46.67
10    10      3  50       4       6   46.67

我想在一个大约有 10,000 个观察值而不是 10 个的数据集上做这样的事情。有没有人知道一种有效的方法来解决这个问题?


这是我目前所得到的,但是,对于我正在处理的一些数据样本,实际上有接近 200 万个观测值,因此运行可能需要相当长的时间......

data$cluster <- 0
count=0

while (min(data$cluster)==0)
#while (max(data$cluster)<=10)
{
count = count+1
data$cum <- ave(data$people, by=list(data$zipcode,data$cluster), FUN=cumsum) 
data$a <- floor(data$cum/10)
data$b <- data$cum-data$n1
data$c <- floor(data$b/10)
data$cluster[data$c==0] = data$cluster[data$c==0]+1
}

extravars <- c('cum','a','b','c')
for (inc.source in extravars){
  eval(parse(text = paste("data$",inc.source,"<-NULL",sep="")))         
}

data$tot_ppl <- ave(data$people, by=list(data$zipcode,data$cluster), FUN=sum) 
data$cluster[data$tot_ppl<10]=data$cluster[data$tot_ppl<10]+1
data$tot_ppl <- ave(data$people, by=list(data$zipcode,data$cluster), FUN=sum)

data2 <- data


for (i in 3:(ncol(data2)-3)){
  data2$x <- data2[ ,i]*data2$tot_ppl
  data2$x <- ave(data2$x, by=list(data2$zipcode,data2$cluster), FUN=sum)
  data2$x <- round(data2$x/data2$tot_ppl,digits=2)
  data2[ ,i] = data2$x
}

data2$x <- NULL

虽然这可行,但需要几个小时才能运行,所以如果有人知道如何提高效率或改进它,我将不胜感激。谢谢!

【问题讨论】:

  • 您是否编写过一种“低效”的方式?如果是这样,请显示您已经尝试过的代码。 10,000 次观察真的需要很长时间吗?

标签: r loops vectorization


【解决方案1】:

我真的想不出一个聪明的方法来向量化这个操作,所以你可以在 R 中使用 for 循环:

pureR <- function(x, lim) {
    cs <- cumsum(x)
    newGroup <- rep(FALSE, length(x))
    prevSum <- 0
    for (i in 1:length(newGroup)) {
        if (cs[i] - prevSum >= lim) {
            newGroup[i] <- TRUE
            prevSum <- cs[i]
        }
    }
    return(1+c(0, head(cumsum(newGroup), -1)))
}
pureR(dat$people, 5)
# [1] 1 1 1 2 2 3 3 4 4 4

您可以使用Rcpp 包来加速非向量化计算:

library(Rcpp)
rcpp <- cppFunction("
NumericVector rcpp(NumericVector x, const double limit) {
    NumericVector result(x.size());
    result[0] = 1;
    double acc = x[0];
    for (int i=1; i < x.size(); ++i) {
        if (acc >= limit) {
            result[i] = result[i-1] + 1;
            acc = x[i];
        } else {
            result[i] = result[i-1];
            acc += x[i];
        }
    }
    return result;
}
")
rcpp(dat$people, 5)
# [1] 1 1 1 2 2 3 3 4 4 4

最后,我们可以在包含 10,000 个观察值的数据集上进行基准测试:

set.seed(144)
dat2 <- dat[sample(1:nrow(dat), 10000, replace=TRUE),]
library(microbenchmark)
microbenchmark(pureR(dat2$people, 5), rcpp(dat2$people, 5))
# Unit: microseconds
#                   expr      min       lq     mean   median       uq       max neval
#  pureR(dat2$people, 5) 7073.571 7287.733 8665.394 7822.639 8749.232 31313.946   100
#   rcpp(dat2$people, 5)   90.309   98.241  129.120  118.351  136.210   324.866   100

虽然 Rcpp 代码比纯 R 实现快 60 倍以上,但对于大小为 10,000 的数据集,纯 R 实现的运行时间仍不到 10 毫秒,这对您来说可能没问题。

【讨论】:

    猜你喜欢
    • 2019-06-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-11
    • 2016-01-16
    • 1970-01-01
    相关资源
    最近更新 更多