使用data.table按组计算和格式化行之间的时间差

3

我是一名新手,对于data.table“场景”不太熟悉,所以如果我的问题过于简单,请谅解。我通常需要应用一些分析或对按唯一ID分组的数据进行子集操作。通常,每个唯一ID有大约1,000行数据,大约有30个唯一ID。因此,我被建议改用data.table而不是尝试弄清楚lapply、sapply或plyr包。

这里是我数据类型的样本

    structure(list(ID = c(1L, 1L, 1L, 1L, 2L, 2L, 2L, 2L, 3L, 3L, 
3L, 3L, 3L, 4L, 4L, 4L, 4L, 4L, 4L, 4L), dt = structure(c(1138366975, 
1138370472, 1138374064, 1138377669, 1138381264, 1138384873, 1138388503, 
1138399312, 1138402842, 1138406507, 1138413700, 1138417261, 1138420848, 
1138424444, 1138428071, 1138431695, 1138435287, 1138438938, 1138442428, 
1138446098), class = c("POSIXct", "POSIXt"), tzone = "GMT")), .Names = c("ID", 
"dt"), row.names = c(NA, -20L), class = "data.frame")

我将把这转化为一个数据表。
X = data.table(test)

将我的“key”设置为个体

setkey(X,ID)

那么目标是计算时间差,以小时为单位(希望这将是容易的),因此需要将Time2-Time1相减来得出每个人(在此情况下为ID)在连续位置之间所花费的小时和分钟。

X[, diff:=c(NA,diff(dt)),by = ID]

这里的diff命令计算的是分钟,但我希望以最有效的方式将其转换/四舍五入为小时,并仍然将值保持为POSIX或时间对象。我知道我可以创建另一列并将diff除以60。但我希望有一种方法只需在某个地方键入“小时”或“分钟”或其他内容即可。因为我不理解data.table如何处理时间。
我尝试使用for循环和difftime命令在data.frame中完成这个操作,但它太繁琐了,将数据链接回原始数据框对我来说很困惑,因为我不精通for循环。
一旦我将数据转换为小时,我想选择仅相隔0.5小时、4小时和12小时的数据,但我还没有找到在data.table中如何实现这一点。
2个回答

2
这是一种实现方式,可能不是最高效的方法...
X[ , diff := c( NA_character_ , difftime( tail( dt , -1 ) , head( dt , -1 ) , units = "hours" ) ) , by = ID ]
#    ID                V1
# 1:  1                NA
# 2:  1 0.971388888888889
# 3:  1 0.997777777777778
# 4:  1  1.00138888888889
# 5:  2                NA

你能解释一下为什么你需要引用'tail(dt,-1)'和head版本吗?我不明白这为什么会起作用。我不清楚为什么你需要引用“最后”或尾部,但不是最后一个(-1)?此外,NA_character_是什么意思?这是一个命令还是指其他东西? - Kerry
此外,当我运行这个命令时,它似乎会产生一个“新”的表格?不确定,但基本上它并不只是在数据表的末尾添加一个额外的列?我尝试添加类似于X$timediff<-X[ , c( NA_character_ , difftime( tail( dt , -1 ) , head( dt , -1 ) , units = "hours" ) ) , by = ID ]的内容,但出现了错误消息,并且新的信息列更像是与1个变量相关联的3个新元素的列表。非常令人困惑。 - Kerry
@Kerry,你混淆了data.framedata.table的语法。不要使用X$timediff <- ...。正确的方法是使用X[ , c( NA_character_ , ...。运行后,查看X,你会发现一个新的列。NA_character_只是NA的字符版本,需要将结果强制转换为character向量,否则data.table会抱怨数据类型不匹配(数字和字符)。 - Simon O'Hanlon
关于 headtail...试试吧!例如:x <- 1:5; head( x , -1 ); tail( x , -1 )。第一个函数返回向量中除了最后一个元素的所有元素,而使用 -1tail 函数则返回一个没有第一个元素的向量,因此我们可以从紧接着它之前的时间中减去后续时间。(尝试一下 tail(x,-1) - head(x,-1),它等同于 2:5 - 1:4,所以结果是 1 1 1 1 - Simon O'Hanlon
我还更新了代码,使其将该列作为新列返回到原始的data.table中。希望现在清楚了。 - Simon O'Hanlon

1

这样不是更简单吗?X[, diff := c(NA,round(diff(dt)/60)),by=ID] 看起来没有时间惩罚。

f1 <- function(X){return(X[, diff := {tmp = diff(dt); units(tmp) <- "hours"; c(NA, as.numeric(tmp))}, by=ID][])}
f2 <- function(X){return(X[, diff := c(NA,round(diff(dt)/60)),by=ID])}

library(microbenchmark)
microbenchmark(f1(X),f2(X))
# Unit: milliseconds
#   expr      min       lq   median       uq      max neval
#  f1(X) 4.676918 4.772861 5.233032 5.324829 7.387008   100
#  f2(X) 4.615325 4.854294 5.161371 5.383165 7.147151   100

我也考虑过这个问题,但是如果可能的话,我想保留它作为“时间”对象的属性。这样,以防下次我想知道两个位置之间相隔了多少天或月份。这种方法肯定有效,只是不容易转换成其他计量单位。 - Kerry

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接