您将如何查找下一个最接近的值?
How would you lookup the next-closest value?
我有以下 2 个 data.frames:
data.df <- data.frame(dt = as.POSIXct(c('2020-01-08 11:30:00',
'2020-01-10 11:30:00', '2020-01-11 12:30:00')),
v1=c(1,2,3))
lookup.df <- data.frame(ldt = as.POSIXct(c('2020-01-08 11:29:00',
'2020-01-08 11:30:00', '2020-01-08 11:31:00', '2020-01-10 10:30:00',
'2020-01-10 11:31:00', '2020-01-11 11:30:00', '2020-01-12 11:30:00')),
lv = 1:7)
对于 data.df 中的每一行,我想在 lookup.df 中获取行索引(以匹配合并中的行),其中 lookup.df$ldt >= data.df$dt 在同一天。如果没有日期满足该要求,则 NA。所以在这个例子中,理想的输出是:
dt | v1 | ldt | lv
2020-01-08 11:30:00 1 2020-01-08 11:30:00 2
2020-01-10 11:30:00 2 2020-01-10 11:31:00 5
2020-01-11 12:30:00 3 NA NA
NOTE: I would prefer a base R implementation or a zoo implementation
假设您的查找时间是有序的,在 base R 中您可以这样做:
lv <- sapply(data.df$dt, function(x){
which(substr(lookup.df$ldt, 1, 10) == substr(x, 1, 10) & lookup.df$ldt >= x)[1]
})
cbind(data.df, lookup.df[lv,])
#> dt v1 ldt lv
#> 2 2020-01-08 11:30:00 1 2020-01-08 11:30:00 2
#> 5 2020-01-10 11:30:00 2 2020-01-10 11:31:00 5
#> NA 2020-01-11 12:30:00 3 <NA> NA
如果您不介意使用 lubridate
,您可以使用 date()
而不是 substr()
1) Base R - sapply 这使用 base R。对于 data.df
中 dt
的每个组件,它会在 lookup.df
在同一天然后 returns 第一个索引。最后,它将 data.df 和 lookup.df
的那些索引的行放在一起。
ix <- sapply(data.df$dt, function(dt) with(lookup.df,
which(ldt >= dt & as.Date(ldt, tz = "") == as.Date(dt, tz = ""))[1]
))
res <- cbind(data.df, lookup.df[ix, ])
rownames(res) <- NULL
给予:
> res
dt v1 ldt lv
1 2020-01-08 11:30:00 1 2020-01-08 11:30:00 2
2 2020-01-10 11:30:00 2 2020-01-10 11:31:00 5
3 2020-01-11 12:30:00 3 <NA> NA
2) Base R - merge 这是另一种 base R 方法。将日期列附加到每个输入数据框,然后按该列合并两者。删除 lookup.df date/time 小于 data.df date/time 的任何行,然后取从同一原始 data.df 派生的每组行的第一行] 排。这将获得匹配项,但它会错过根本没有匹配项的行,因此执行第二次合并以取回这些匹配项。
data.df$date <- as.Date(data.df$dt, tz = "")
lookup.df$date <- as.Date(lookup.df$ldt, tz = "")
m <- merge(data.df, lookup.df, by = "date", all.x = TRUE, all.y = FALSE)
m <- subset(m, dt <= ldt)
m <- m[!duplicated(m[1:3]), ]
merge(data.df[-3], m[-1], by = c("dt", "v1"), all.x = TRUE, all.y = FALSE)
给予:
dt v1 ldt lv
1 2020-01-08 11:30:00 1 2020-01-08 11:30:00 2
2 2020-01-10 11:30:00 2 2020-01-10 11:31:00 5
3 2020-01-11 12:30:00 3 <NA> NA
3) SQL 尽管问题要求的是基本 R 解决方案,但此处还添加了 sql 解决方案,因为它提供了一个特别直接的解决方案将问题向前转换为代码,作为具有复杂条件的自连接。它在指定条件下执行左连接,并采用从 data.df.
中同一行派生的所有行中找到的最小值 ldt
library(sqldf)
data.df$date <- as.Date(data.df$dt, tz = "")
lookup.df$date <- as.Date(lookup.df$ldt, tz = "")
sqldf("select D.dt, D.v1, min(L.ldt) as ldt, L.lv
from [data.df] D left join [lookup.df] L
on D.dt <= L.ldt and D.date == L.date
group by D.rowid")
给予:
dt v1 ldt lv
1 2020-01-08 11:30:00 1 2020-01-08 11:30:00 2
2 2020-01-10 11:30:00 2 2020-01-10 11:31:00 5
3 2020-01-11 12:30:00 3 <NA> NA
备注
问题中有 R 无法读取的花哨引号,因此我们将其用作输入:
data.df <- data.frame(dt = as.POSIXct(c('2020-01-08 11:30:00',
'2020-01-10 11:30:00', '2020-01-11 12:30:00')),
v1=c(1,2,3))
lookup.df <- data.frame(ldt = as.POSIXct(c('2020-01-08 11:29:00',
'2020-01-08 11:30:00', '2020-01-08 11:31:00', '2020-01-10 10:30:00',
'2020-01-10 11:31:00', '2020-01-11 11:30:00', '2020-01-12 11:30:00')),
lv = 1:7)
为了完整起见,这是一个使用 data.table
的 rolling join.
的解决方案
如果我没理解错的话,OP 正在寻找匹配项
- 同一天
- 在
lookup.df
中遇到的第一个时间戳上或之后
在`data.df 中给出
第二个条件可以通过简单的rolling join来实现:
library(data.table)
setDT(lookup.df)[setDT(data.df), on = .(ldt = dt), .(dt, v1, ldt = x.ldt, lv), roll = -Inf]
dt v1 ldt lv
1: 2020-01-08 11:30:00 1 2020-01-08 11:30:00 2
2: 2020-01-10 11:30:00 2 2020-01-10 11:31:00 5
3: 2020-01-11 12:30:00 3 2020-01-12 11:30:00 7
但是,第3行显然违反了第一个条件。为了满足第一个条件,我们也必须在同一天匹配。这需要向两个数据框添加类型为 Date
的 day
列:
library(data.table)
setDT(lookup.df)[, .(ldt, lv, day = as.IDate(ldt))][
setDT(data.df)[, .(dt, v1, day = as.IDate(dt))],
on = .(day, ldt = dt), .(dt, v1, ldt = x.ldt, lv), roll = -Inf]
dt v1 ldt lv
1: 2020-01-08 11:30:00 1 2020-01-08 11:30:00 2
2: 2020-01-10 11:30:00 2 2020-01-10 11:31:00 5
3: 2020-01-11 12:30:00 3 <NA> NA
请注意 data.df
和 lookup.df
未修改。
为了完全完整,这里有一个带有 fuzzyjoin 风格的 dplyr 版本:
library(fuzzyjoin)
library(dplyr)
fuzzy_left_join(data.df, lookup.df, by = c("day" = "day", "dt" = "ldt"),
match_fun = list(`==`, `<=`)) %>%
select(-c(day.x, day.y)) %>%
group_by(v1) %>% slice(1)
dt v1 ldt lv
<dttm> <dbl> <dttm> <int>
1 2020-01-08 11:30:00 1 2020-01-08 11:30:00 2
2 2020-01-10 11:30:00 2 2020-01-10 11:31:00 5
3 2020-01-11 12:30:00 3 NA NA
我有以下 2 个 data.frames:
data.df <- data.frame(dt = as.POSIXct(c('2020-01-08 11:30:00',
'2020-01-10 11:30:00', '2020-01-11 12:30:00')),
v1=c(1,2,3))
lookup.df <- data.frame(ldt = as.POSIXct(c('2020-01-08 11:29:00',
'2020-01-08 11:30:00', '2020-01-08 11:31:00', '2020-01-10 10:30:00',
'2020-01-10 11:31:00', '2020-01-11 11:30:00', '2020-01-12 11:30:00')),
lv = 1:7)
对于 data.df 中的每一行,我想在 lookup.df 中获取行索引(以匹配合并中的行),其中 lookup.df$ldt >= data.df$dt 在同一天。如果没有日期满足该要求,则 NA。所以在这个例子中,理想的输出是:
dt | v1 | ldt | lv
2020-01-08 11:30:00 1 2020-01-08 11:30:00 2
2020-01-10 11:30:00 2 2020-01-10 11:31:00 5
2020-01-11 12:30:00 3 NA NA
NOTE: I would prefer a base R implementation or a zoo implementation
假设您的查找时间是有序的,在 base R 中您可以这样做:
lv <- sapply(data.df$dt, function(x){
which(substr(lookup.df$ldt, 1, 10) == substr(x, 1, 10) & lookup.df$ldt >= x)[1]
})
cbind(data.df, lookup.df[lv,])
#> dt v1 ldt lv
#> 2 2020-01-08 11:30:00 1 2020-01-08 11:30:00 2
#> 5 2020-01-10 11:30:00 2 2020-01-10 11:31:00 5
#> NA 2020-01-11 12:30:00 3 <NA> NA
如果您不介意使用 lubridate
,您可以使用 date()
而不是 substr()
1) Base R - sapply 这使用 base R。对于 data.df
中 dt
的每个组件,它会在 lookup.df
在同一天然后 returns 第一个索引。最后,它将 data.df 和 lookup.df
的那些索引的行放在一起。
ix <- sapply(data.df$dt, function(dt) with(lookup.df,
which(ldt >= dt & as.Date(ldt, tz = "") == as.Date(dt, tz = ""))[1]
))
res <- cbind(data.df, lookup.df[ix, ])
rownames(res) <- NULL
给予:
> res
dt v1 ldt lv
1 2020-01-08 11:30:00 1 2020-01-08 11:30:00 2
2 2020-01-10 11:30:00 2 2020-01-10 11:31:00 5
3 2020-01-11 12:30:00 3 <NA> NA
2) Base R - merge 这是另一种 base R 方法。将日期列附加到每个输入数据框,然后按该列合并两者。删除 lookup.df date/time 小于 data.df date/time 的任何行,然后取从同一原始 data.df 派生的每组行的第一行] 排。这将获得匹配项,但它会错过根本没有匹配项的行,因此执行第二次合并以取回这些匹配项。
data.df$date <- as.Date(data.df$dt, tz = "")
lookup.df$date <- as.Date(lookup.df$ldt, tz = "")
m <- merge(data.df, lookup.df, by = "date", all.x = TRUE, all.y = FALSE)
m <- subset(m, dt <= ldt)
m <- m[!duplicated(m[1:3]), ]
merge(data.df[-3], m[-1], by = c("dt", "v1"), all.x = TRUE, all.y = FALSE)
给予:
dt v1 ldt lv
1 2020-01-08 11:30:00 1 2020-01-08 11:30:00 2
2 2020-01-10 11:30:00 2 2020-01-10 11:31:00 5
3 2020-01-11 12:30:00 3 <NA> NA
3) SQL 尽管问题要求的是基本 R 解决方案,但此处还添加了 sql 解决方案,因为它提供了一个特别直接的解决方案将问题向前转换为代码,作为具有复杂条件的自连接。它在指定条件下执行左连接,并采用从 data.df.
中同一行派生的所有行中找到的最小值ldt
library(sqldf)
data.df$date <- as.Date(data.df$dt, tz = "")
lookup.df$date <- as.Date(lookup.df$ldt, tz = "")
sqldf("select D.dt, D.v1, min(L.ldt) as ldt, L.lv
from [data.df] D left join [lookup.df] L
on D.dt <= L.ldt and D.date == L.date
group by D.rowid")
给予:
dt v1 ldt lv
1 2020-01-08 11:30:00 1 2020-01-08 11:30:00 2
2 2020-01-10 11:30:00 2 2020-01-10 11:31:00 5
3 2020-01-11 12:30:00 3 <NA> NA
备注
问题中有 R 无法读取的花哨引号,因此我们将其用作输入:
data.df <- data.frame(dt = as.POSIXct(c('2020-01-08 11:30:00',
'2020-01-10 11:30:00', '2020-01-11 12:30:00')),
v1=c(1,2,3))
lookup.df <- data.frame(ldt = as.POSIXct(c('2020-01-08 11:29:00',
'2020-01-08 11:30:00', '2020-01-08 11:31:00', '2020-01-10 10:30:00',
'2020-01-10 11:31:00', '2020-01-11 11:30:00', '2020-01-12 11:30:00')),
lv = 1:7)
为了完整起见,这是一个使用 data.table
的 rolling join.
如果我没理解错的话,OP 正在寻找匹配项
- 同一天
- 在
lookup.df
中遇到的第一个时间戳上或之后 在`data.df 中给出
第二个条件可以通过简单的rolling join来实现:
library(data.table)
setDT(lookup.df)[setDT(data.df), on = .(ldt = dt), .(dt, v1, ldt = x.ldt, lv), roll = -Inf]
dt v1 ldt lv 1: 2020-01-08 11:30:00 1 2020-01-08 11:30:00 2 2: 2020-01-10 11:30:00 2 2020-01-10 11:31:00 5 3: 2020-01-11 12:30:00 3 2020-01-12 11:30:00 7
但是,第3行显然违反了第一个条件。为了满足第一个条件,我们也必须在同一天匹配。这需要向两个数据框添加类型为 Date
的 day
列:
library(data.table)
setDT(lookup.df)[, .(ldt, lv, day = as.IDate(ldt))][
setDT(data.df)[, .(dt, v1, day = as.IDate(dt))],
on = .(day, ldt = dt), .(dt, v1, ldt = x.ldt, lv), roll = -Inf]
dt v1 ldt lv 1: 2020-01-08 11:30:00 1 2020-01-08 11:30:00 2 2: 2020-01-10 11:30:00 2 2020-01-10 11:31:00 5 3: 2020-01-11 12:30:00 3 <NA> NA
请注意 data.df
和 lookup.df
未修改。
为了完全完整,这里有一个带有 fuzzyjoin 风格的 dplyr 版本:
library(fuzzyjoin)
library(dplyr)
fuzzy_left_join(data.df, lookup.df, by = c("day" = "day", "dt" = "ldt"),
match_fun = list(`==`, `<=`)) %>%
select(-c(day.x, day.y)) %>%
group_by(v1) %>% slice(1)
dt v1 ldt lv
<dttm> <dbl> <dttm> <int>
1 2020-01-08 11:30:00 1 2020-01-08 11:30:00 2
2 2020-01-10 11:30:00 2 2020-01-10 11:31:00 5
3 2020-01-11 12:30:00 3 NA NA