Вы также можете сделать это, если ваши наборы данных слишком велики, чтобы сделать декартово произведение:
df1 %>%
split(1:NROW(.)) %>%
map( ~merge(.x,
df2[ abs(difftime(df2$sent, .x$endtime, units='s')) < 20, ],
by=NULL) ) %>%
bind_rows()
РЕДАКТИРОВАТЬ
TLDR
Используйте неэквивалентное объединение из data.table , оно имеет лучшую производительность в целом.
dt1 = as.data.table(df1)
dt2 = as.data.table(df2)
dt1[, `:=`(endtime_min = endtime - 20, endtime_max = endtime + 20) ]
dt1[dt2,
.(ID, ID1, endtime, sent),
on = .(endtime_min < sent, endtime_max > sent), nomatch = 0L, allow.cartesian=T]
Более длинная версия
Ответ Я разместил бы лучше в сценарии ios, где фреймы данных слишком велики, так как при перекрестном соединении сначала получается фрейм данных с таким количеством строк, которое равно произведению количества строк в обоих фреймах данных. Первой фильтрацией и после объединения это позволяет избежать ненужного выделения памяти. Однако для каждой строки df1
накладные расходы проверяются на наличие совпадений в df2
.
. Другой вариант использования, где этот ответ был бы лучше, - это когда один из данных кадры намного меньше других, даже если они не такие большие. Я проверил несколько тестов, чтобы проверить это.
Однако, после того, как натолкнулся на этот ответ и сделал версию решения в data.table для вопроса, представленного ФП, ни один из ответы сравниваются с производительностью этой реализации.
В тестах, которые я выполнял, использовались наборы данных, заданные OP, где для имитации большего набора данных я просто реплицировал эти наборы данных определенное количество раз. Было выполнено 2 теста:
- Реплицированные оба набора данных одинаковое количество раз
- Исправлен размер
df1
и реплицировано df2
Для каждого теста я измерял среднее время выполнения принятого ответа (merge_filter
), мой оригинальный ответ (filter_merge
) и решение data.table (datatable
).
Перед запуском тестов я подготовил df1
и df2
для получения правильных типов данных и переименовал столбец ID
с df1
на ID1
. Для решения data.table я преобразовал оба фрейма данных в их data.tables
аналоги, dt1
и dt2
.
Что касается каждого метода, я должен внести некоторые изменения, в основном используя вместо этого merge(..., by=NULL)
из crossing(...)
, так как последний не поддерживает перекрестное соединение с дублирующимися строками, удаляя все дублирующиеся строки из результирующего набора данных.
Вот код, который я использовал для запуска тестов:
library(tidyverse)
library(data.table)
run_test = function(n, n1=n, n2=n) {
df1 = bind_rows(rep(list(df1_op), n1))
df2 = bind_rows(rep(list(df1_op), n2))
dt1 = as.data.table(df1)
dt2 = as.data.table(df2)
microbenchmark::microbenchmark(
merge_filter = df1 %>%
merge(df2, by=NULL) %>%
filter(abs(difftime(sent, endtime, 'secs')) < 20),
filter_merge = df1 %>%
split(1:NROW(.)) %>%
map(~merge( .x,
df2[ abs(difftime(df2$sent, .x$endtime, units='s')) < 20, ],
by=NULL) ) %>%
bind_rows(),
datatable={
dt1[, `:=`(endtime_min = endtime - 20, endtime_max = endtime + 20) ]
dt1[dt2,
.(ID, ID1, endtime, sent),
on = .(endtime_min < sent, endtime_max > sent), nomatch = 0L, allow.cartesian=T]
}
)
}
test_1_list = list()
for( n in c(1, 2, 5, 10, 20, 50, 100, 200, 500) ) {
test_1_list[[ toString(n) ]] <- run_test(n)
}
test_2_list = list()
for( n in c(1, 2, 5, 10, 20, 50, 100, 200, 500,
1000, 2000, 5000, 10000, 20000, 50000) ) {
test_2_list[[ toString(n) ]] <- run_test(n, n1=1)
}
А вот результаты теста 1 и 2 соответственно:
РЕДАКТИРОВАТЬ 2
Вы можете сделать левостороннее объединение без равенства, например:
filter_merge
df1 %>%
split(1:NROW(.)) %>%
map( ~merge(mutate(.x, k=1),
df2 %>%
filter( abs(difftime(df2$sent, .x$endtime, units='s')) < 20 ) %>%
mutate(k=1),
by="k",
all.x=T) %>%
select(-k) ) %>%
bind_rows() %>%
select(ID1, endtime, ID, sent)
# ID1 endtime ID sent
# 1 A 2020-01-07 01:35:08 E 2020-01-07 01:35:20
# 2 B 2020-01-07 01:39:00 <NA> <NA>
# 3 C 2020-01-10 01:45:00 <NA> <NA>
datatable
dt1[, `:=`(endtime_min = endtime - 20, endtime_max = endtime + 20) ]
dt2[dt1,
.(i.ID1, i.endtime, x.ID, x.sent),
on = .(sent > endtime_min, sent < endtime_max), allow.cartesian=T]
# i.ID1 i.endtime x.ID x.sent
# 1: A 2020-01-07 01:35:08 E 2020-01-07 01:35:20
# 2: B 2020-01-07 01:39:00 <NA> <NA>
# 3: C 2020-01-10 01:45:00 <NA> <NA>