Ошибка «Источник должен быть указан», когда оператор difftime + вычитания не работает в течение разных минут - PullRequest
0 голосов
/ 24 февраля 2019

У меня есть фрейм данных из двух столбцов «начало» и «конец» в формате ЧЧ: ММ: СС .

Я хотел вычислить продолжительность между началом и концом, используя difftime function

Всегда возвращает эту ошибку: Ошибка в 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))


1 Ответ

0 голосов
/ 24 февраля 2019

Обновление 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) в качестве числового описания метки времени.Принято считать, что «0 секунд» этого формата (1970-01-01 00:00:00) является абсолютным, но он не универсален (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"
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...