使用 R 将 SQL-Interval-String 转换为分钟

Converting SQL-Interval-String to minutes using R

我正在使用 R,其中我有一个变量 '2 month 3 day 6 hour 70 minute' 作为字符串。变量随时间变化,因此不具有相同的 length/structure。我需要这个变量通过将它转换为一个间隔来对 PostgreSQL 数据库进行查询。这很好用。

现在我需要这个 interval/string-variable 作为整数来做一些数学计算。

我想到使用 sqldf 以下内容:

library(sqldf)
my_interval = '2 month 3 day 6 hour 70 minute'
interval_minutes <- sqldf(paste("SELECT EXTRACT(EPOCH FROM '",my_interval,"'::INTERVAL)/60"))
interval_minutes_novar <- sqldf("SELECT EXTRACT(EPOCH FROM '2 month 3 day 6 hour 70 minute'::INTERVAL)/60")

但我得到 Error: near "FROM": syntax error。从我的研究中我知道 sqldf 使用 SQLite,它不支持 EXTRACT().

如何使用 R 将 SQL-间隔转换为分钟?

我的解决方法是使用我的 PostgreSQL 连接来完成:

library(sf)
library(RPostgres)

my_postgresql_connection <- dbConnect(Postgres(), dbname = "my_db", host = "my_host", port = 1234, user = "my_user", password = "my_password")
my_interval = '2 month 3 day 6 hour 70 minute'
my_dataframe <- st_read(my_postgresql_connection, query = paste("SELECT EXTRACT(EPOCH FROM '",my_interval,"'::INTERVAL)/60 as minutes"))
my_interval_in_minutes <- as.double(my_dataframe$minutes[1])

1) sqldf/gsubfn 使用 gsubfn 将 my_interval 中的每个单词替换为 *、适当的分钟数和 + 。删除任何尾随的 + 和空格,然后解析和评估 mins 或将 mins 替换到 sql 语句中。 4 个日历年的平均月份有 365.25 / 12 天,有一个闰年,但如果你想获得与 PostgreSQL 相同的答案,请将 365.25 / 12 替换为 30,如评论中所述。

library(sqldf)  # this also pulls in gsubfn

# input
my_interval = '2 month 3 day 6 hour 70 minute'

L <- list(minute = " +", hour = "*60 +", day = "*60*24 +", 
       month = "*365.25 * 60 * 24 /12 +")
mins <- my_interval |>
  gsubfn(pattern = "\w+", replacement = L) |> 
  trimws(whitespace = "[+ ]")

eval(parse(text = mins))
## [1] 92410

fn$sqldf("select $mins mins")
##    mins
## 1 92410

2) Base R 这是一个base R 解决方案。将数字和单词提取到单独的向量中,将单词转换为适当的因子并获取它们的内积。 (1) 中关于每月 30 天的讨论也适用于此。

v <- c(minute = 1, hour = 60, day = 60 * 24, month = 365.25 * 60 * 24 /12)
nums <- my_interval |>
  gsub(pattern = "[a-z]", replacement = "") |>
  textConnection() |>
  scan(quiet = TRUE)
words <- my_interval |>
  gsub(pattern = "\d", replacement = "") |>
  textConnection() |>
  scan(what = "", quiet = TRUE)
sum(v[words] * nums)
## [1] 92410

3) lubridate 可以使用 lubridate 持续时间对象。

library(lubridate)
as.numeric(duration(my_interval), "minute")
## [1] 92410

虽然 lubridate 不处理 30 天的月份(并且 Hadley 说它是 未计划) 我们可以预处理my_interval 以获得效果。

library(gsubfn)
library(lubridate)

my_interval |>
 gsubfn(pattern = "(\d+) +month", replacement = ~paste(30*as.numeric(x),"day")) |>
 duration() |>
 as.numeric("minute")
## [1] 91150

根据此调整我的回答 here,我将重申此转换的一个相当大的问题:将“月”转换为“秒”不是恒定的,因为月份在 28-31 天之间变化。但是,如果我们假设 30,为了论证,那么:

func <- function(x, ptn) {
  out <- gsub(paste0(".*?\b([0-9.]+)\s*", ptn, ".*"), "\1", x, ignore.case = TRUE)
  ifelse(out == x, NA, out)
}
res1 <- lapply(c(mon = "month", day = "day", hr = "hour", min = "minute"),
               function(ptn) as.numeric(func(my_interval, ptn)))
res2 <- lapply(res1, function(z) ifelse(is.na(z), 0, z))
res2
# $mon
# [1] 2
# $day
# [1] 3
# $hr
# [1] 6
# $min
# [1] 70
86400 * (res2$mon*31 + res2$day) + 3600*res2$mon + 60*res2$hr
# [1] 5623560

因为我在这里使用 lapply 和简单的向量化操作,如果 my_interval 是多个字符串(格式相似),这也适用。它对缺失变量(假定 0)具有鲁棒性,并且可以包括“年”(尽管闰年不准确)and/or 如果需要,“秒”。

intervals <- c("2 month 3 day 6 hour 70 minute", "1 year", "1 hour 1 second")
res1 <- lapply(c(yr = "year", mon = "month", day = "day", hr = "hour", min = "minute", sec = "second"),
               function(ptn) as.numeric(func(intervals, ptn)))
res2 <- lapply(res1, function(z) ifelse(is.na(z), 0, z))
str(res2)
# List of 6
#  $ yr : num [1:3] 0 1 0
#  $ mon: num [1:3] 2 0 0
#  $ day: num [1:3] 3 0 0
#  $ hr : num [1:3] 6 0 1
#  $ min: num [1:3] 70 0 0
#  $ sec: num [1:3] 0 0 1
86400 * (res2$yr*365 + res2$mon*31 + res2$day) + 3600*res2$mon + 60*res2$hr + res2$sec
# [1] 5.6e+06 3.2e+07 6.1e+01