使用 difftime + 减法运算符时错误 "Origin must be supplied" 在不同分钟内不起作用

Error "Origin must be supplied" when using difftime + subtraction operator not working for different minutes

我有一个包含两列 "start" 和 "end" 的数据框,格式为 HH:MM:SS

我想使用 difftime 函数

计算开始和结束之间的持续时间

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

我读了很多帖子,但 none 似乎对我有用。

正在加载包

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 个对象

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

修正

接受的响应工作得很好,除了 returns 一个错误: 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))


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

最终,直接如下:

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

更新:你的数据格式和我最初推断的完全不一样。我将在下面保留原始答案,但鉴于此数据结构,它没有太大帮助。

你总是可以尝试做 "modulus subtraction",但我认为最好的方法是转换为十进制并返回。首先,我以两种方式提供数据,使用户更容易准确地了解您的数据是什么样的。 (如果事先准备好,我将无法提供原来不太有用的答案。)请以后使用类似的东西,它意义重大!

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))

从这里开始,两个辅助函数可以转换 to/from 十进制分钟。这些都假设您只处理 minutes/seconds,再也不会。同样,转换回 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 的讨论意味着它很可能从数字转换为 time/date。这样做的一个常见原因是使用纪元秒(在 unix-y 中很常见)作为时间戳的数字描述。通常认为这种格式(1970-01-01 00:00:00)的“0秒”是绝对的,但不具有普遍性(excel不同),甚至realistic/desirable是可行的有不同的“0”时间。所以它什么都不假设,迫使你明确。

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,您首先需要在执行 difftime 之前用 as.POSIXct(..., origin="1970-01-01") 之类的东西转换这些数字。

但是,由于您需要秒,而数字纪元已经以秒为单位,您可以这样做

end - start

如果你真的需要它被标记为"seconds",那么做

`units<-`(end - start, "secs")
### such as
`units<-`(100-90, "secs")
# [1] 10
# attr(,"units")
# [1] "secs"