在R中缓慢循环,是否有任何建议加快速度?

时间:2012-04-15 07:06:57

标签: r

我有一个数据框“m”,如下所示:

m

我正在尝试查找每个帐户最活跃的月份(大多数为V1)。例如对于帐户“2”,它将是“第6个月”,对于帐户3,它将是“第1个月”,....

我编写了下面的循环,它工作正常,但只需要很长时间,即使我只使用8000行,整个数据集有250,000行,所以下面的代码是不可用的。有没有人可以提出更好的方法来实现这一目标?

非常感谢。

code

5 个答案:

答案 0 :(得分:3)

你可以使用plyr

来做到这一点
library(plyr)
ddply(m, "AccountID", subset, V1==max(V1))

编辑:要按月获得结果,只需更改de“id”变量

即可
library(plyr)
ddply(m, "Month", subset, V1==max(V1))

答案 1 :(得分:2)

我认为Owe Jessen的评论是正确的,这不是问题的答案。所以这是我在data.table的帮助下拍摄的。

首先,让我们使用一个更容易理解的例子:

library(data.table)
DT <- data.table(AccountID = rep(1:3, each=4),
                 V1        = sample(1:100, 12, replace=FALSE),
                 Month     = rep(1:4, times=3))
      AccountID V1 Month
 [1,]         1 81     1
 [2,]         1 23     2
 [3,]         1 72     3
 [4,]         1 36     4
 [5,]         2 22     1
 [6,]         2 13     2
 [7,]         2 50     3
 [8,]         2 40     4
 [9,]         3 74     1
[10,]         3 83     2
[11,]         3  4     3
[12,]         3  3     4

所以这里我们有3个帐户和4个月,每个帐户/月组合,我们有一个V1。因此,找到每个帐户的最大V1,我会执行以下操作:

setkey(DT, AccountID)
DT <- DT[, list(maxV1=max(V1)), by="AccountID"][DT]
DT[maxV1==V1]
     AccountID maxV1 V1 Month
[1,]         1    81 81     1
[2,]         2    50 50     3
[3,]         3    83 83     2

这有点难以理解,所以让我尝试解释一下:我将AccountID设置为DT的关键。现在,我基本上在DT[, list(maxV1=max(V1)), by="AccountID"][DT]中执行了两个步骤。首先,我计算每个帐户(DT[, list(maxV1=max(V1)), by="AccountID"])的最大V1值,然后在其后面调用[DT],我将新列maxV1添加到旧DT 。显然,那时我只需要获取maxV1==V1所持有的所有行。

将此解决方案应用于Nico的更高级示例,并向您展示如何将data.frame转换为data.table

library(data.table)
DT <- as.data.table(m)
#Note that this line is only necessary if there are more than one rows per Month/AccountID combination
DT <- DT[, sum(V1), by="Month,AccountID"]
setkey(DT, AccountID)
DT <- DT[, list(maxV1=max(V1)), by="AccountID"][DT]
DT[maxV1==V1]
   AccountID maxV1 Month    V1
           1 24660     1 24660
           2 22643     2 22643
           3 23642     3 23642
           4 22766     5 22766
           5 22445    12 22445
...

这恰好提供了50行。

编辑:

这是一个基础R解决方案:

df <- data.frame(AccountID = rep(1:3, each=4),
                 V1        = sample(1:100, 12, replace=FALSE),
                 Month     = rep(1:4, times=3))
df$maxV1 <- ave(df$V1, df$AccountID, FUN = max)
df[df$maxV1==df$V1, ]

我的灵感来自here

答案 2 :(得分:1)

我没有看到对这种算法进行矢量化的方法(如果其他人这样做,我很想知道如何)。

以下是我将如何编码(p.s:请在将来包含自包含的代码。看看?dput也是为了帮助):

make.data <- function(n = 100) # 250000
{
# Generate some random data
AccountID <- sample(1:50, n, replace=T)
V1 <- sample(1:100, n, replace=T)
Month <- sample(1:12, n, replace=T)

m <- data.frame(AccountID, V1, Month)
m
}



fo <- function(X)
{
unique_ID <- unique(X$AccountID)
M_max <- numeric(length(unique_ID ))

for(i in seq_along(unique_ID))
{
    ss <- X$AccountID == unique_ID[i]
    M_max [i] <- X[ss,"Month"][which.max(X[ss,"V1"])]
}

# results:
# M_max
data.frame(unique_ID , M_max)
}


X <- make.data(1000000)
system.time(fo(X))
#   user  system elapsed 
#   2.32    0.33    2.70 

我怀疑其中一些功能可能比您使用的功能更快(但值得测试时间)。

编辑: R的新JIT可能对您有所帮助(您可以在此处阅读更多相关信息:Speed up your R code using a just-in-time (JIT) compiler我也尝试使用JIT,它没有加快速度。

将循环并行化可能也是值得的(但我现在不会进入)。

如果时机不切实际,可能需要使用data.table包(但我没有使用它的经验),甚至可以使用SQL ...

祝你好运,Tal

UPDATE :我使用了nico的示例,并将解决方案包装在函数中。时机非常好,不需要更先进的解决方案......

答案 3 :(得分:1)

这在我的笔记本电脑上使用250000行(加上它更干净)几乎是即时的

# Generate some random data
AccountID <- sample(1:50, 250000, replace=T)
V1 <- sample(1:100, 250000, replace=T)
Month <- sample(1:12, 250000, replace=T)

m <- data.frame(AccountID, V1, Month)

# Aggregate the data by month
V1.per.month <- aggregate(m$V1, sum, by=list(Month = m$Month))

编辑:重新阅读我意识到我忘记考虑帐户(双关语)的问题

但这应该是

V1.per.month <- aggregate(m$V1, sum, 
             by=list(Month = m$Month, Account= m$AccountID))

时序图(误差条为SD)。正如你所看到的那样,每100万行需要大约2.5s,这是非常可以接受的,我认为。

Elapsed time per number of rows

答案 4 :(得分:1)

我认为基本上这与Tal的相同解决方案

我通过以下循环获得合理的时间

# Generate some random data
AccountID <- sample(1:50, 250000, replace=T)
V1 <- sample(1:100, 250000, replace=T)
Month <- sample(1:12, 250000, replace=T)

m <- data.frame(AccountID, V1, Month)

# Aggregate the data by month

ac = as.numeric(levels(as.factor(m$AccountID)))
active.month = rep(NA, length(ac))
names(active.month) = ac

system.time(
{
  for(i in ac)
  {
    subm = subset(m, AccountID == i)
    active.month[i] = subm[which.max(subm[,"V1"]),"Month"]
  }
})
   User      System verstrichen 
   0.78        0.14        0.92