当使用difftime +减法运算符在不同分钟内不工作时,错误“必须提供原点”

时间:2019-02-24 18:25:05

标签: r lubridate difftime

我有一个以 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

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

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


1 个答案:

答案 0 :(得分:-1)

更新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"作为本机格式,所以您仍然需要进行一些数据处理才能开始使用它。 / p>


有关R的origin=的讨论POSIXt意味着它很可能从数字转换为时间/日期。这样做的一个常见原因是使用纪元秒(在Unix-y东西中很常见)作为时间戳的数字表示。通常会假定这种格式(1970-01-01 00:00:00的“ 0秒”是绝对的,但不是通用的(excel有所不同),并且设置不同的“ 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 ,首先需要在进行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"