1

我有一个HH:MM:SS格式的两列“开始”和“结束”的数据框。

我想使用difftime函数计算开始和结束之间的持续时间

它总是返回此错误: as.POSIXct.numeric(time1) 中的错误:必须提供'origin'

我读了很多帖子,但似乎没有一个对我有用。

加载包

library(dplyr)
library(tidyverse)
library(lubridate)

我删除了几个小时来处理分钟和秒

get_time <- function(x){str_sub(x, start = -5) %>%  ms()} 
df <- df %>% mutate(start = get_time(start)) %>%
  mutate(end = get_time(end))

对象类别

class(df$start)
gives: 
[1] "Period"
attr(,"package")
[1] "lubridate"
start                 end       

26M 22S               26M 23S        
26M 25S               26M 37S      
29M 47S               30M 13S

我使用difftime函数计算了持续时间

df$duration <- with(df, difftime(end, start, units="secs"))
gives error:
Error in as.POSIXct.numeric(time1) : 'origin' must be supplied

我使用减法运算符,它工作正常,除了第 3 行分钟不同时,它给出了错误的答案。

start                 end            duration

26M 22S               26M 23S        1S
26M 25S               26M 37S        12S
29M 47S               30M 13S        1M -34S

修正案

接受的响应工作得很好,除了它返回一个错误: mtx1[3, ] 中的错误:每当应用于我在同一数据框中的第二个两列“start2”和“end2”时,维度数不正确。

来自我的 df 的样本

df <- structure(list(item = c("manatee", "manatee", "pile", "pile"), prestart = new("Period", .Data = c(22, 
25, 41, 49), year = c(0, 0, 0, 0), month = c(0, 
0, 0, 0), day = c(0, 0, 0, 0), hour = c(0, 0, 0, 
0), minute = c(26, 26, 26, 26)), preend = new("Period", 
    .Data = c(23, 37, 48, 50), year = c(0, 0, 0, 0), month = c(0, 0, 0, 0), day = c(0, 0, 0, 0
    ), hour = c(0, 0, 0, 0), minute = c(26, 26, 26, 26)), poststart = new("Period", .Data = c(23, 41, 50, 
54), year = c(0, 0, 0, 0), month = c(0, 0, 0, 0), day = c(0, 0, 0, 0), hour = c(0, 0, 0, 0), 
    minute = c(26, 26, 26, 26)), postend = new("Period", 
    .Data = c(37, 48, 52, 22), year = c(0, 0, 0, 0), month = c(0, 0, 0, 0), day = c(0, 0, 0, 0
    ), hour = c(0, 0, 0, 0), minute = c(26, 26, 26, 27))), row.names = c(NA, -6L), class = c("tbl_df", "tbl", 
"data.frame"))

仅以分钟和秒为单位组织数据(删除小时)


get_time <- function(x){str_sub(x, start = -5) %>%  ms()} 
df <- df %>% mutate(prestart = get_time(prestart)) %>%
  mutate(preend = get_time(preend)) %>% 
  mutate(poststart = get_time(poststart)) %>% 
  mutate(postend = get_time(postend))


4

1 回答 1

0

更新 2:我将之前的两个答案都记录下来(以防有人确实拥有包含此类字符串的数据)。但是,数据实际上是从 派生的lubridate,因此"26M 22S"仅仅是numeric对象的表示。

最终,它就像这样直接:

lubridate::as.difftime(df$preend - df$prestart, units="secs")
# Time differences in secs
# [1]  1 12  7  1

更新:您的数据格式与我最初推断的完全不同。我将在下面保留原始答案,但鉴于这种数据结构,它并没有太大帮助。

你总是可以尝试做“模减法”,但我认为最好的方法是转换为十进制并返回。首先,我将以两种方式提供数据,让用户更容易准确地了解您的数据是什么样子。(提前做好这件事会让我无法提供原始的不太有用的答案。)请在未来使用类似的东西,这意味着很多!

x <- data.frame(
  start = c("26M 22S", "26M 25S", "29M 47S"),
  end = c("26M 23S", "26M 37S", "30M 13S"),
  stringsAsFactors = FALSE
)

# if you don't want to generate a frame like that, then you can
# provide the output from dput(head(x))
structure(list(start = c("26M 22S", "26M 25S", "29M 47S"), end = c("26M 23S", 
"26M 37S", "30M 13S")), class = "data.frame", row.names = c(NA, 
-3L))

从这里开始,两个辅助函数可以转换为十进制分钟数。这些都假设您只处理分钟/秒,永远不会更多。同样,转换回character假设您始终使用整数秒,这可能很草率。如果不是这种情况,您可以删除round并接受小数部分,也许sprintf("%dM %02.3f", ...)改用控制小数部分。

decimal_minutes <- function(s) {
  nums <- strsplit(gsub("[^0-9 ]", "", s), "\\s+")
  mtx <- sapply(nums, as.integer)
  mtx[1,] + mtx[2,] / 60
}
minutes_seconds <- function(num, keep0 = TRUE) {
  out <- sprintf("%dM %02dS", as.integer(num), as.integer(round(60 * (num %% 1), 0)))
  if (!keep0) out <- gsub("^0M ", "", out)
  out
}

从这里开始,如果您想在其他地方使用它们,您始终可以保留数字版本:

x[,c("startnum", "endnum")] <- lapply(x[,c("start", "end")], decimal_minutes)
x
#     start     end startnum   endnum
# 1 26M 22S 26M 23S 26.36667 26.38333
# 2 26M 25S 26M 37S 26.41667 26.61667
# 3 29M 47S 30M 13S 29.78333 30.21667
x$endnum - x$startnum
# [1] 0.01666667 0.20000000 0.43333333
minutes_seconds(x$endnum - x$startnum)
# [1] "0M 01S" "0M 12S" "0M 26S"
minutes_seconds(x$endnum - x$startnum, keep0 = FALSE)
# [1] "01S" "12S" "26S"

但是,如果你想要的只是一次性减法,你可以在一次调用中完成它:

x$duration <- minutes_seconds(
  decimal_minutes(x$end) - decimal_minutes(x$start),
  keep0 = TRUE
)
x
#     start     end duration
# 1 26M 22S 26M 23S   0M 01S
# 2 26M 25S 26M 37S   0M 12S
# 3 29M 47S 30M 13S   0M 26S
x$duration <- minutes_seconds(
  decimal_minutes(x$end) - decimal_minutes(x$start),
  keep0 = FALSE
)
x
#     start     end duration
# 1 26M 22S 26M 23S      01S
# 2 26M 25S 26M 37S      12S
# 3 29M 47S 30M 13S      26S

理想情况下,这可以而且应该推广到接受更多(例如小时,如"1H 23M 11S")。一个简单的步骤是更新decimal_minutes以查找和处理更长的格式。我想知道它是否lubridate适合你,虽然我怀疑它会"26M 22S"作为一种原生格式,所以你仍然需要做一些数据处理才能开始使用它。


关于origin=R 的讨论POSIXt意味着它很可能从数字转换为时间/日期。这样做的一个常见原因是使用纪元秒(在 unix-y 的东西中很常见)作为时间戳的数字描述。通常假设这种格式1970-01-01 00:00:00(所以它没有任何假设,迫使你明确表达。

as.POSIXct(100, origin="1970-01-01 00:00:00")
# [1] "1969-12-31 16:01:40 PST"
as.POSIXct(100, origin="1970-01-01 00:00:00", tz="UTC")
# [1] "1970-01-01 00:01:40 UTC"
### or even just 
as.POSIXct(100, origin="1970-01-01")

所以要difftime数字上使用,你首先需要as.POSIXct(..., origin="1970-01-01")在做之前用类似的东西转换这些数字difftime

但是,由于您想要秒,而数字时代已经以秒为单位,您可以这样做

end - start

如果您确实需要将其标记为“秒”,请执行

`units<-`(end - start, "secs")
### such as
`units<-`(100-90, "secs")
# [1] 10
# attr(,"units")
# [1] "secs"
于 2019-02-24T19:49:14.960 回答