Я читаю данные с другой платформы, где комбинация строк, перечисленных ниже, используется для выражения временных меток:
\* = current time
t = current day (00:00)
mo = month
d = days
h = hours
m = minutes
Например, *-3d
— это текущее время минус 3 дня, t-3h
— это три часа до утра сегодняшнего дня (полночь вчерашнего дня).
Я хотел бы иметь возможность вводить эти уравнения в R и получать соответствующее значение POSIXct
. Я пытаюсь использовать регулярное выражение в приведенной ниже функции, но теряю числовой множитель для каждой строки:
strTimeConverter <- function(z){
ret <- stringi::stri_replace_all_regex(
str = z,
pattern = c('^\\*',
'^t',
'([[:digit:]]{1,})mo',
'([[:digit:]]{1,})d',
'([[:digit:]]{1,})h',
'([[:digit:]]{1,})m'),
replacement = c('Sys.time()',
'Sys.Date()',
'*lubridate::months(1)',
'*lubridate::days(1)',
'*lubridate::hours(1)',
'*lubridate::minutes(1)'),
vectorize_all = F
)
return(ret)
# return(eval(expr = parse(text = ret)))
}
> strTimeConverter('*-5mo+3d+4h+2m')
[1] "Sys.time()-*lubridate::months(1)+*lubridate::days(1)+*lubridate::hours(1)+*lubridate::minutes(1)"
> strTimeConverter('t-5mo+3d+4h+2m')
[1] "Sys.Date()-*lubridate::months(1)+*lubridate::days(1)+*lubridate::hours(1)+*lubridate::minutes(1)"
Ожидаемый результат:
# *-5mo+3d+4h+2m
"Sys.time()-5*lubridate::months(1)+3*lubridate::days(1)+4*lubridate::hours(1)+4*lubridate::minutes(1)"
# t-5mo+3d+4h+2m
"Sys.Date()-5*lubridate::months(1)+3*lubridate::days(1)+4*lubridate::hours(1)+4*lubridate::minutes(1)"
Я предполагал, что заключение [[:digit]]{1,}
в круглые скобки ()
сохранит их, но очевидно, что это не работает. Я определил шаблон следующим образом, иначе код заменяет повторяющиеся вхождения, например. *
преобразуется в Sys.time()
, но затем m
в Sys.time()
заменяется на *lubridate::minutes(1)
.
Я планирую преобразовать (ожидаемый) вывод в дату-время R, используя eval(parse(text = ...))
— в настоящее время закомментировано в функции.
Я открыт для использования других пакетов или подходов.
Обновлять
Немного поработав, я обнаружил, что приведенная ниже версия работает — я заменяю строки в таком порядке, чтобы вновь замененные символы не заменялись снова:
strTimeConverter <- function(z){
ret <- stringi::stri_replace_all_regex(
str = z,
pattern = c('y', 'd', 'h', 'mo', 'm', '^t', '^\\*'),
replacement = c('*years(1)',
'*days(1)',
'*hours(1)',
'*days(30)',
'*minutes(1)',
'Sys.Date()',
'Sys.time()'),
vectorize_all = F
)
ret <- gsub(pattern = '\\*', replacement = '*lubridate::', x = ret)
rdate <- (eval(expr = parse(text = ret)))
attr(rdate, 'tzone') <- 'UTC'
return(rdate)
}
sample_string <- '*-5mo+3d+4h+2m'
strTimeConverter(sample_string)
Это работает, но не очень элегантно и, скорее всего, потерпит неудачу, поскольку я вынужден включать другие выражения (например, yd
для дня года, например, 124).
Вы можете использовать обратные ссылки в заменах следующим образом:
library(stringr)
x <- c("*-5mo+3d+4h+2m", "t-5mo+3d+4h+2m")
repl <- c('^\\*' = 'Sys.time()', '^t' = 'Sys.Date()', '(\\d+)mo' = '\\1*lubridate::months(1)', '(\\d+)d' = '\\1*lubridate::days(1)', '(\\d+)h' = '\\1*lubridate::hours(1)', '(\\d+)m' = '\\1*lubridate::minutes(1)')
stringr::str_replace_all(x, repl)
## => [1] "Sys.time()-5*lubridate::months(1)+3*lubridate::days(1)+4*lubridate::hours(1)+2*lubridate::minutes(1)"
## [2] "Sys.Date()-5*lubridate::months(1)+3*lubridate::days(1)+4*lubridate::hours(1)+2*lubridate::minutes(1)"
Смотрите демоверсию R онлайн.
См., например, '(\\d+)mo' = '\\1*lubridate::months(1)'
. Здесь (\d+)mo
соответствует и захватывает в группу 1 одну или несколько цифр, а mo
просто соответствует. Затем, когда совпадение найдено, \1
в \1*lubridate::months(1)
вставляет содержимое группы 1 в результирующую строку.
Обратите внимание, что замена может быть более безопасной, если вы закроете совпадение периода времени границей слова (\b
) справа:
repl <- c('^\\*' = 'Sys.time()', '^t' = 'Sys.Date()', '(\\d+)mo\\b' = '\\1*lubridate::months(1)', '(\\d+)d\\b' = '\\1*lubridate::days(1)', '(\\d+)h\\b' = '\\1*lubridate::hours(1)', '(\\d+)m\\b' = '\\1*lubridate::minutes(1)')
Это не сработает, если интервалы времени склеены друг с другом без разделителей, отличных от слов, но у вас есть +
в строках примеров, так что здесь это безопасно.
На самом деле, вы можете заставить его работать и с той функцией, которую вы использовали. Просто убедитесь, что обратные ссылки имеют синтаксис $n
:
x <- c("*-5mo+3d+4h+2m", "t-5mo+3d+4h+2m")
pattern = c('^\\*', '^t', '(\\d+)mo', '(\\d+)d', '(\\d+)h', '(\\d+)m')
replacement = c('Sys.time()', 'Sys.Date()', '$1*lubridate::months(1)', '$1*lubridate::days(1)', '$1*lubridate::hours(1)', '$1*lubridate::minutes(1)')
stringi::stri_replace_all_regex(x, pattern, replacement, vectorize_all=FALSE)
Выход:
[1] "Sys.time()-5*lubridate::months(1)+3*lubridate::days(1)+4*lubridate::hours(1)+2*lubridate::minutes(1)"
[2] "Sys.Date()-5*lubridate::months(1)+3*lubridate::days(1)+4*lubridate::hours(1)+2*lubridate::minutes(1)"
@Gautam Да, я на самом деле взломал его: вы можете использовать stringi::stri_replace_all_regex
, но синтаксис обратной ссылки $n
, а не \n
.
Другой вариант прямого производства времени будет следующим:
strTimeConvert <- function(base=Sys.time(), delta = "-5mo+3d+4h+2m"){
mo <- gsub(".*([+-]\\d+)mo.*", "\\1", x)
ds <- gsub(".*([+-]\\d+)d.*", "\\1", x)
hs <- gsub(".*([+-]\\d+)h.*", "\\1", x)
ms <- gsub(".*([+-]\\d+)m.*", "\\1", x)
out <- base + months(as.numeric(mo)) + days(as.numeric(ds)) +
hours(as.numeric(hs)) + minutes(as.numeric(ms))
out
}
strTimeConvert()
# [1] "2020-07-21 20:32:19 EDT"
Спасибо! Я попробую - мне было интересно, есть ли у
stringr
лучший метод, чемstringi
для моего случая использования - похоже, он есть!