......如果可能的话
我的任务是找到用户参与游戏的最长连续日数。
我没有编写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矩阵。
提前谢谢
答案 0 :(得分:7)
apply
函数并不总是(甚至通常)比for
循环更快。这是R与S-Plus的联系的残余(在后者中,申请速度快于)。一个例外是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
循环。
[编辑:] 显然有很多方法可以做到这一点,即使它更紧凑,这也绝不是更好。只需使用您的代码,这是另一种方法:
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))))
您可能需要进一步操作输出。
答案 1 :(得分:3)
这不是关于* 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
答案 2 :(得分: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))
})
答案 3 :(得分:0)
如果你有一个非常长的数据列表而不是听起来可能是一个群集问题。每个群集将由用户定义,并且日期的最大间隔距离为1。然后按用户检索最大的群集。如果我想到一个特定的方法,我会编辑它。
答案 4 :(得分:0)
这是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)