根据“userID”和“timeStamp”的差异创建“sessionID”

时间:2014-02-22 00:39:15

标签: r loops indexing

对不起,另一个新手问题。我试图根据现有的ID或索引获取部分数据框,然后根据第二列中值的差异创建一个新的ID或索引列。

例如,在下面的示例数据中,userID 1似乎有2个会话:一个从timeStamp 1开始,到timeStamp 6结束,另一个从timeStamp 40开始,到timeStamp 47结束。如果两个timeStamps之间的差异是=< 30(比如分钟),然后两个timeStamps被认为是在同一个会话中。但是当相同的用户ID从6跳到40时,这被认为是新会话(差异大于30),那么这被认为是新会话。用户2只有1个会话; User3有3个。

理想情况下,我想在sessionID中保留userID信息;最后两列是所需格式的示例。如果只是使它们成为整数更容易,我可以稍后连接userID和sessID。 var1,var2,varN只是为了表明数据框中还有其他数据。

我试图避免传统的循环并获得R-esque。我获取了userID和timeStamp信息,并通过userID创建了一个list,其中timeStamps作为列表1到最后一个userID的向量:

byUser <- with(myDF, split(timeStamp, userID))

有些真实数据如下所示:

structure(list(`1` = c(50108, 50108, 50171, 50175, 121316, 121316, 
127228), `2` = c(55145, 745210, 1407020, 2283255),...

然后我使用diff来获得每个向量中timeStamps之间的差异:

myDiff2 <- lapply(byUser, diff)

有些真实数据如下所示:

structure(list(`1` = c(0, 63, 4, 71141, 0, 5912), `2` = c(690065, 
661810, 876235), `3` = c(109, 80, 98, 948417, 0),

...现在我觉得应该遍历每个列表,初始化sessID,然后如果myDiff2中的值是&gt; 1800秒(30分钟),增加sessID。

这似乎很长;请告诉我如何缩短它!提前谢谢!

   userID timeStamp var1 var2 varN sessID1 sessID2
1       1         1    x    y    N     1.0     1.1
2       1         3    x    y    N     1.0     1.1
3       1         6    x    y    N     1.0     1.1
4       1        40    x    y    N     1.1     1.2
5       1        42    x    y    N     1.1     1.2
6       1        43    x    y    N     1.1     1.2
7       1        47    x    y    N     1.1     1.2
8       2         5    x    y    N     2.0     2.1
9       2         8    x    y    N     2.0     2.1
10      3         2    x    y    N     3.0     3.1
11      3         5    x    y    N     3.0     3.1
12      3        38    x    y    N     3.1     3.2
13      3        39    x    y    N     3.1     3.2
14      3        39    x    y    N     3.1     3.2
15      3        82    x    y    N     3.2     3.3
16      3        83    x    y    N     3.2     3.3
17      3        90    x    y    N     3.2     3.3
18      3        91    x    y    N     3.2     3.3
19      3       102    x    y    N     3.2     3.3

数据示例的dput()在这里:

myDF <- structure(list(userID = c(1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 2L, 
3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L), timeStamp = c(1L, 3L, 
6L, 40L, 42L, 43L, 47L, 5L, 8L, 2L, 5L, 38L, 39L, 39L, 82L, 83L, 
90L, 91L, 102L), var1 = structure(c(1L, 1L, 1L, 1L, 1L, 1L, 1L, 
1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L), .Label = "x", class = "factor"), 
    var2 = structure(c(1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 
    1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L), .Label = "y", class = "factor"), 
    varN = structure(c(1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 
    1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L), .Label = "N", class = "factor"), 
    sessID1 = c(1, 1, 1, 1.1, 1.1, 1.1, 1.1, 2, 2, 3, 3, 3.1, 
    3.1, 3.1, 3.2, 3.2, 3.2, 3.2, 3.2), sessID2 = c(1.1, 1.1, 
    1.1, 1.2, 1.2, 1.2, 1.2, 2.1, 2.1, 3.1, 3.1, 3.2, 3.2, 3.2, 
    3.3, 3.3, 3.3, 3.3, 3.3)), .Names = c("userID", "timeStamp", 
"var1", "var2", "varN", "sessID1", "sessID2"), class = "data.frame", row.names = c(NA, 
-19L))

=== 以下答案的补遗:

对于下一个新手:

挑选'。' / decimal分隔符在我看来可能并不出色:当sessID计数器从9滚动到0时,它导致了一些奇怪和非唯一的sessID。

将分隔符更改为其他字符 - 如连字符 - 一切都很好。

@rawr和@jlhoward - 感谢您的快速,正确和非常有帮助的回复:两种方法都运作良好。 @jlhoward - 特别感谢addt'l,值得称道的解释。 (@rawr是第一个,所以我认可他的答案。)

两种解决方案之间的性能差异很小:data.table速度更快,但需要对data.frame进行一些addt'l upfront转换为data.table。

再次感谢,所有人。

2 个答案:

答案 0 :(得分:1)

和“数据表”方式......

library(data.table)
myDT <- data.table(myDF)
setkey(myDT,userID)
myDT[,sessID3:=paste(userID,cumsum(c(0,diff(timeStamp)>30)),sep="."),by=userID]
all.equal(myDT$sessID1,as.numeric(myDT$sessID3))
# [1] TRUE

<强>解释

by=userID与数据表一起使用按userID分组。使用diff(timeStamp)>30创建一个逻辑向量,其元素少于组中行数,因此我们将前缀为0与c(0,diff(timesStamp)> 30)。使用cumsum(c(0,diff(timeStamp>30))将逻辑强制转换为整数并计算累积和。每次遇到diff > 30时,cumsum都会增加1.最后,使用paste(...)只需将userID与二级索引连接。

一个注意事项:您已将其设置为sessID为数字。如果给定用户有超过10个会话,这会有点冒险。 IMO最好使用sessID的字符。

答案 1 :(得分:0)

library(plyr)

ddply(myDF, .(userID), transform, 
      sessID3 = paste(userID, 
                      c(0, cumsum(sapply(1:(length(userID) - 1),
                                         function(x)
                                           ifelse((timeStamp[x + 1] - timeStamp[x]) > 30,
                                                  1, 0)))), sep = '.'),
      sessID4 = paste(userID, 
                      c(0, cumsum(sapply(1:(length(userID) - 1),
                                         function(x)
                                           ifelse((timeStamp[x + 1] - timeStamp[x]) > 30,
                                                  1, 0)))) + 1, sep = '.'))

给我:

#    userID timeStamp var1 var2 varN sessID1 sessID2 sessID3 sessID4
# 1       1         1    x    y    N     1.0     1.1     1.0     1.1
# 2       1         3    x    y    N     1.0     1.1     1.0     1.1
# 3       1         6    x    y    N     1.0     1.1     1.0     1.1
# 4       1        40    x    y    N     1.1     1.2     1.1     1.2
# 5       1        42    x    y    N     1.1     1.2     1.1     1.2
# 6       1        43    x    y    N     1.1     1.2     1.1     1.2
# 7       1        47    x    y    N     1.1     1.2     1.1     1.2
# 8       2         5    x    y    N     2.0     2.1     2.0     2.1
# 9       2         8    x    y    N     2.0     2.1     2.0     2.1
# 10      3         2    x    y    N     3.0     3.1     3.0     3.1
# 11      3         5    x    y    N     3.0     3.1     3.0     3.1
# 12      3        38    x    y    N     3.1     3.2     3.1     3.2
# 13      3        39    x    y    N     3.1     3.2     3.1     3.2
# 14      3        39    x    y    N     3.1     3.2     3.1     3.2
# 15      3        82    x    y    N     3.2     3.3     3.2     3.3
# 16      3        83    x    y    N     3.2     3.3     3.2     3.3
# 17      3        90    x    y    N     3.2     3.3     3.2     3.3
# 18      3        91    x    y    N     3.2     3.3     3.2     3.3
# 19      3       102    x    y    N     3.2     3.3     3.2     3.3