【问题标题】:Slow loop in R, any suggestion to speed it up?R中的慢循环,有什么建议可以加快速度吗?
【发布时间】:2012-04-15 07:06:56
【问题描述】:

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

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

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

非常感谢。

【问题讨论】:

  • 以后,您可以直接在问题中输入代码,然后使用顶部的 {} 图标将其格式化为代码。这将根据代码类型为您提供语法高亮显示,并且我们其他人将更容易复制您所做的工作并将其放入我们的代码编辑器中。

标签: r


【解决方案1】:

你可以用 plyr 做到这一点

library(plyr)
ddply(m, "AccountID", subset, V1==max(V1))

已编辑:要按月获取结果,只需更改 de "id" 变量

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

【讨论】:

  • -1:这不是他要问的。它只会为每个月/帐户对返回一行,全局最大值为 V1。您需要按月和帐户汇总数据。
  • @nico,我不明白你的反对票。不知道是不是误解了问题,但是提出的解决方案同样有效,只需在ddply函数中更改ID变量
  • 对不起,我似乎误解了 OP 发布的数据集。
  • @ManuelRamón +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 的 key。现在,我基本上在DT[, list(maxV1=max(V1)), by="AccountID"][DT] 中做了两个步骤。首先,我计算每个帐户的最大 V1 值 (DT[, list(maxV1=max(V1)), by="AccountID"]),然后通过紧跟其后调用 [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 行。

编辑:

这是一个 base-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

【讨论】:

    【解决方案3】:

    这在我使用 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.5 秒,我认为这是非常可接受的。

    【讨论】:

    • 我认为这不是 OP 正在寻找的答案:您每月汇总 V1(导致 df 有 12 行)。 OP 正在寻找每个帐户最活跃的月份(这将是 50 行的 df)
    • @Owe Jessen:我也在按帐户汇总(见编辑)
    • @nico 我同意 Owe Jessen 的观点。如果我的解决方案正确,您只需获得每个月和帐户的 V1 总和。但是,我认为 OP 的数据集对于每个月/帐户组合只有一个 V1 值。但现在,他想找出每个账户的 V1 最大的月份。我用 data.table 发布了一个答案(希望是正确的;-)。因此,如果您同意,也许您会找到使用 base R 的解决方案。我不会,因为 base R 不是我的强项(我现在已经习惯了 data.table ;-)
    • 您好亲爱的 Nico,我怀疑(正如其他人所提到的),您的解决方案没有给出 OP 问题的相关答案。但是由于您的代码,我更新了我的答案以给出解决方案的时间 - 它在基础 R 中非常好......
    • @Tal Galili:我现在担心我生成数据的方式并不正确......请参阅 Christoph 评论。如果 OP 对此提供澄清,那就太好了。
    【解决方案4】:

    我没有看到向量化这个算法的方法(如果其他人有,我很想知道如何)。

    这是我的编码方式(ps:请在将来包含自包含的代码。也请查看 ?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 来完成......

    祝你好运,塔尔

    更新:我使用了 nico 的示例,并将解决方案包装在函数中。时机绝对好,不需要更高级的解决方案...

    【讨论】:

    • 不用JIT,看我的回答
    【解决方案5】:

    我想这基本上与 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 
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-08-30
      • 1970-01-01
      • 2012-03-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-10-11
      • 1970-01-01
      相关资源
      最近更新 更多