的lubridate
包被廣泛認爲是在R.可用的最佳日期/時間包它建立在基礎R Date
和POSIXct
類型,並增加了其自身的Interval
,Duration
,和Period
類型。
hh:mm:ss
時間的最合適的數據類型是Period
類型。理論上講,應該可以將您的字符串時間解析爲Period
值,然後使用aggregate()
執行直分組sum()
。
不幸的是,這顯然是一個比人們希望的任務更困難的任務。我最終得到了它,但它需要一些扭曲。
首先,下面是如何將字符串時間解析爲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([email protected],na.rm=na.rm),hours=sum([email protected],na.rm=na.rm),days=sum([email protected],na.rm=na.rm),months=sum([email protected],na.rm=na.rm),years=sum([email protected],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([email protected], "y ", [email protected], "m ", [email protected], "d ", [email protected], "H ", :
## trying to get slot "year" from an object (class "Period") that is not an S4 object
traceback();
## 8: paste([email protected], "y ", [email protected], "m ", [email protected], "d ", [email protected], "H ",
## [email protected], "M ", [email protected], "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生態系統不同派別之間不協調的相互作用。
你的問題之一是你沒有正確地打破「01:12:00」,類似於正確的時間組件。我使用的兩種方法是如果所有持續時間少於24小時並且使用posix函數作爲與午夜之間的差異,或者拉開這個變量並自己執行計算,則提供日期。時間序列包可能有一個更清晰的方法。 – lmo
感謝大家的寶貴意見和支持:) – richpiana