Как бы вы нашли следующую ближайшую ценность? - PullRequest
2 голосов
/ 07 марта 2020

У меня есть следующие 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

ПРИМЕЧАНИЕ. Я бы предпочел реализацию base R или реализацию в зоопарке

Ответы [ 4 ]

2 голосов
/ 07 марта 2020

1) Base R - sapply Используется база R. Для каждого компонента dt в data.df он находит все значения времени и даты, превышающие его в lookup.df в ту же дату, а затем возвращает Индекс первый. Наконец, он объединяет 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) База R - объединение Это альтернативный подход базы R. Добавьте столбец даты к каждому фрейму входных данных, а затем объедините их по этому столбцу. Удалите все строки, для которых дата / время lookup.df меньше, чем дата / время data.df, а затем возьмите первую строку каждого набора строк, полученных из той же исходной строки 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, поскольку оно обеспечивает особенно прямой перевод задачи в код в виде самостоятельного соединения со сложным условием. , Он выполняет левое соединение с указанным условием и принимает минимум ldt, найденный во всех строках, полученных из одной и той же строки в data.df.

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)
2 голосов
/ 08 марта 2020

Ради полноты, вот решение, которое использует data.table подвижное соединение .

Если я правильно понимаю, ОП ищет совпадения

  1. в тот же день и
  2. на первой отметке времени, обнаруженной в lookup.df на или после отметки времени, указанной в `data.df

Второе условие может быть достигнуто простым скользящим соединением :

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. В порядке Чтобы выполнить первое условие, мы должны соответствовать в тот же день. Для этого необходимо добавить столбец day типа Date к обоим фреймам данных:

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 не изменены .

1 голос
/ 08 марта 2020

А для полной и полной полноты, вот версия dplyr с ароматом fuzzyjoin :

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
1 голос
/ 07 марта 2020

При условии, что ваши времена поиска упорядочены, в базе 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()

...