【问题标题】:Help me replace a for loop with an "apply" function帮我用“应用”函数替换 for 循环
【发布时间】:2010-12-03 01:17:31
【问题描述】:

...如果可能的话

我的任务是找出用户连续参与游戏的最长连续天数。

我没有编写 sql 函数,而是选择使用 R 的 rle 函数来获取最长的条纹,然后用结果更新我的 db 表。

(附加的)数据框是这样的:

    day      user_id
2008/11/01    2001
2008/11/01    2002
2008/11/01    2003
2008/11/01    2004
2008/11/01    2005
2008/11/02    2001
2008/11/02    2005
2008/11/03    2001
2008/11/03    2003
2008/11/03    2004
2008/11/03    2005
2008/11/04    2001
2008/11/04    2003
2008/11/04    2004
2008/11/04    2005

我尝试了以下方法来获得每个用户最长的连胜记录

# turn it to a contingency table
my_table <- table(user_id, day)

# get the streaks
rle_table <- apply(my_table,1,rle)

# verify the longest streak of "1"s for user 2001
# as.vector(tapply(rle_table$'2001'$lengths, rle_table$'2001'$values, max)["1"])

# loop to get the results
# initiate results matrix
res<-matrix(nrow=dim(my_table)[1], ncol=2)

for (i in 1:dim(my_table)[1]) {
string <- paste("as.vector(tapply(rle_table$'", rownames(my_table)[i], "'$lengths, rle_table$'", rownames(my_table)[i], "'$values, max)['1'])", sep="")
res[i,]<-c(as.integer(rownames(my_table)[i]) , eval(parse(text=string)))
}

不幸的是,这个 for 循环耗时太长,我想知道是否有一种方法可以使用“apply”系列中的函数生成 res 矩阵。

提前谢谢你

【问题讨论】:

  • 如果你想在 sql 中做(或在 R 中使用 sqldf)有一个很好的讨论在这个其他 SO 线程 stackoverflow.com/questions/1176011/…
  • 你为什么使用那个粘贴/评估方案?似乎这会给您带来巨大的性能影响?
  • 我同意乔纳森的观点;这也使它很难阅读。那是你想要做的吗? res.1
  • 发布一个可重现的示例,以便我们比较时间并贡献更好的代码怎么样?
  • 应该这个 rle_table

标签: r loops for-loop apply


【解决方案1】:

另一种选择

# convert to Date
day_table$day <- as.Date(day_table$day, format="%Y/%m/%d")
# split by user and then look for contiguous days
contig <- sapply(split(day_table$day, day_table$user_id), function(.days){
    .diff <- cumsum(c(TRUE, diff(.days) != 1))
    max(table(.diff))
})

【讨论】:

    【解决方案2】:

    编辑:已修复。我最初认为我必须修改大部分 rle(),但结果只需要进行一些调整。

    这不是关于 *apply 方法的答案,但我想知道这可能不是整个过程的更快方法。正如 Shane 所说,循环并不是那么糟糕。而且...我很少向任何人展示我的代码,所以我很乐意听到对此的批评。

    #Shane, I told you this was awesome
    dat <- getSOTable("http://stackoverflow.com/questions/1504832/help-me-replace-a-for-loop-with-an-apply-function", 1)
    colnames(dat) <- c("day", "user_id")
    #Convert to dates so that arithmetic works properly on them
    dat$day <- as.Date(dat$day)
    
    #Custom rle for dates
    rle.date <- function (x)
    {
        #Accept only dates
        if (class(x) != "Date")
            stop("'x' must be an object of class \"Date\"")
        n <- length(x)
        if (n == 0L)
            return(list(lengths = integer(0L), values = x))
        #Dates need to be sorted
        x.sort <- sort(x)
        #y is a vector indicating at which indices the date is not consecutive with its predecessor
        y <- x.sort[-1L] != (x.sort + 1)[-n]
        #i returns the indices of y that are TRUE, and appends the index of the last value
        i <- c(which(y | is.na(y)), n)
        #diff tells you the distances in between TRUE/non-consecutive dates. max gets the largest of these.
        max(diff(c(0L, i)))
    }
    
    #Loop
    max.consec.use <- matrix(nrow = length(unique(dat$user_id)), ncol = 1)
    rownames(max.consec.use) <- unique(dat$user_id)
    
    for(i in 1:length(unique(dat$user_id))){
        user <- unique(dat$user_id)[i]
        uses <- subset(dat, user_id %in% user)
        max.consec.use[paste(user), 1] <- rle.date(uses$day)
    }
    
    max.consec.use
    

    【讨论】:

    【解决方案3】:

    apply 函数并不总是(甚至通常)比for 循环快。这是 R 与 S-Plus 关联的残余(在后者中,apply 比 for 快)。一个例外是lapply,它通常比for 快(因为它使用C 代码)。 See this related question.

    所以你应该使用apply 主要是为了提高代码的清晰度,而不是为了提高性能。

    你可以find Dirk's presentation on high-performance computing useful。另一种蛮力方法是"just-in-time compilation" with Ra instead of the normal R version,它经过优化以处理for 循环。

    [Edit:] 显然有很多方法可以实现这一点,即使它更紧凑,这也绝不是更好的。只需使用您的代码,这是另一种方法:

    dt <- data.frame(table(dat))[,2:3]
    dt.b <- by(dt[,2], dt[,1], rle)
    t(data.frame(lapply(dt.b, function(x) max(x$length))))
    

    您可能需要进一步操作输出。

    【讨论】:

      【解决方案4】:

      这是Chris's suggestion for how to get the data

      dat <- read.table(textConnection(
       "day      user_id
       2008/11/01    2001
       2008/11/01    2002
       2008/11/01    2003
       2008/11/01    2004
       2008/11/01    2005
       2008/11/02    2001
       2008/11/02    2005
       2008/11/03    2001
       2008/11/03    2003
       2008/11/03    2004
       2008/11/03    2005
       2008/11/04    2001
       2008/11/04    2003
       2008/11/04    2004
       2008/11/04    2005
       "), header=TRUE)
      

      【讨论】:

      • ...是的,这可能更明智一些。但我喜欢时不时在我的编程中加入一点魔法。
      【解决方案5】:

      如果您有一个非常长的数据列表,那么这听起来可能是一个聚类问题。每个集群将由用户和日期定义,最大间隔距离为 1。然后按用户检索最大的集群。如果我想到一个具体的方法,我会编辑它。

      【讨论】:

        猜你喜欢
        • 2020-05-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-04-08
        • 2011-12-06
        相关资源
        最近更新 更多