lubridate 包被广泛认为是 R 中可用的最佳日期/时间包。它基于 R Date 和 POSIXct 类型,并添加了自己的 Interval、Duration、和Period 类型。
最适合纯 hh:mm:ss 次的数据类型是 Period 类型。从理论上讲,应该可以将您的字符串时间解析为Period 值,然后将sum() 与aggregate() 进行直接分组。
不幸的是,这比人们希望的要困难得多。我最终得到了它,有点,但它需要一些扭曲。
首先,这是将字符串时间解析为Period 值的方法。 lubridate 提供了一个方便的 hms() 方法来做到这一点:
df <- data.frame(year=c(2000L,2000L,2000L,2000L,2000L,2000L),flights.type=c('HR20','HR20','L4','L4','L4','HR20'),flights.duration=c('01:12:00','02:00:00','00:54:00','00:42:00','00:22:00','00:24:00'),stringsAsFactors=F);
library(lubridate);
df$flights.duration <- hms(df$flights.duration);
df;
## year flights.type flights.duration
## 1 2000 HR20 1H 12M 0S
## 2 2000 HR20 2H 0M 0S
## 3 2000 L4 54M 0S
## 4 2000 L4 42M 0S
## 5 2000 L4 22M 0S
## 6 2000 HR20 24M 0S
第二,不幸的是,lubridate 似乎没有为Period 类型提供sum() 方法:
sum(df$flights.duration);
## [1] 0
(如果您想知道为什么它返回零,Period 类型是通过将秒字段存储为向量的有效负载来实现的,它是双精度类型,其余字段(分钟、小时、天, 月, 年)存储为槽,也是双精度类型。df$flights.duration 中的所有值都为零秒,而基本的sum() 函数只看到向量有效负载,因此它的总和为零。)
我自己尝试使用 S3 方法来填补这个空白,但很快发现它不起作用,因为 Period 类型是 S4 类型。所以我写了这个S4方法:
setMethod('sum',signature(x='Period',na.rm='logical'),function(x,na.rm=FALSE) period(seconds=sum(as.double(x),na.rm=na.rm),minutes=sum(x@minute,na.rm=na.rm),hours=sum(x@hour,na.rm=na.rm),days=sum(x@day,na.rm=na.rm),months=sum(x@month,na.rm=na.rm),years=sum(x@year,na.rm=na.rm)));
## [1] "sum"
sum(df$flights.duration);
## [1] "3H 154M 0S"
不幸的是,还有一个问题:aggregate() 默认尝试简化聚合结果,这会将 S4 结果扁平化为非 S4 对象,丢失槽并损坏数据:
res <- aggregate(flights.duration~year+flights.type,df,sum);
res;
## Error in paste(x@year, "y ", x@month, "m ", x@day, "d ", x@hour, "H ", :
## trying to get slot "year" from an object (class "Period") that is not an S4 object
traceback();
## 8: paste(x@year, "y ", x@month, "m ", x@day, "d ", x@hour, "H ",
## x@minute, "M ", x@.Data, "S", sep = "")
## 7: format.Period(x[[i]], ..., justify = justify)
## 6: format(x[[i]], ..., justify = justify)
## 5: format.data.frame(x, digits = digits, na.encode = FALSE)
## 4: as.matrix(format.data.frame(x, digits = digits, na.encode = FALSE))
## 3: print.data.frame(list(year = c(2000L, 2000L), flights.type = c("HR20",
## "L4"), flights.duration = c(0, 0)))
## 2: print(list(year = c(2000L, 2000L), flights.type = c("HR20", "L4"
## ), flights.duration = c(0, 0)))
## 1: print(list(year = c(2000L, 2000L), flights.type = c("HR20", "L4"
## ), flights.duration = c(0, 0)))
res$flights.duration;
## [1] 0 0
## attr(,"class")
## [1] "Period"
## attr(,"class")attr(,"package")
## [1] "lubridate"
isS4(res$flights.duration);
## [1] FALSE
如您所见,aggregate() 调用成功,但对象已损坏。 print.data.frame() 方法在该列上失败,因为它恰好在其上调用了 format(),该方法调度到 S3 方法 format.Period(),这是 lubridate 命名空间下的私有方法。它在损坏的对象上失败。
我们可以防止简化:
res <- aggregate(flights.duration~year+flights.type,df,sum,simplify=F);
res;
## year flights.type flights.duration
## 1 2000 HR20 0
## 2 2000 L4 0
res$flights.duration;
## $`1`
## [1] "3H 36M 0S"
##
## $`4`
## [1] "118M 0S"
##
所以从技术上讲它是有效的,但是该列现在是列表类型,这并不理想。它也不再显示得很好;当显示为 data.frame 的一部分时,我们只会看到一个零。
我们可以通过手动转换列来组合列表组件来解决这个问题。不幸的是,unlist() 或 do.call(c,...) 的明显方法不起作用:
res <- transform(aggregate(flights.duration~year+flights.type,df,sum,simplify=F),flights.duration=do.call(c,flights.duration));
res;
## year flights.type flights.duration
## 1 2000 HR20 0
## 2 2000 L4 0
res$flights.duration;
## [1] 0 0
isS4(res$flights.duration);
## [1] FALSE
Period 值列表被展平为纯向量,类似于 aggregate() 所做的简化效果。
问题似乎出在列表名称上,导致 c() 调用无法按预期运行。我们可以用unname() 解决这个问题。所以这是最终的解决方案:
res <- transform(aggregate(flights.duration~year+flights.type,df,sum,simplify=F),flights.duration=do.call(c,unname(flights.duration)));
res;
## year flights.type flights.duration
## 1 2000 HR20 3H 36M 0S
## 2 2000 L4 118M 0S
因此,尽管我们最终到达了那里,但我不推荐此解决方案。 R 生态系统的不同派系之间存在太多复杂性、功能差距和不协调的交互。