Объединение значений из 2 разных наборов данных за определенный промежуток времени в один набор данных (R) - PullRequest
0 голосов
/ 19 февраля 2020

У меня есть два отдельных набора данных: df1 и df2. Я хотел бы создать новый набор данных, df3, который бы соответствовал столбцу конечного времени df1 с отправленным столбцом df2, если даты и время находятся в пределах 20 секунд друг от друга.

 df1

 endtime                     ID

 1/7/2020  1:35:08 AM         A
 1/7/2020  1:39:00 AM         B
 1/20/2020 1:45:00 AM         C



 df2

sent                         ID

1/7/2020  1:35:20 AM          E
1/7/2020  1:42:00 AM          F
1/20/2020 1:55:00 AM          G
1/20/2020 2:00:00 AM          E

Это мой желаемый вывод для df3. Есть только одна строка, потому что есть только два значения, которые соответствуют условию нахождения в пределах 20 секунд от конечного времени и отправленных столбцов.

endtime                  sent 

1/7/2020 1:35:08 AM      1/7/2020  1:35:20 AM       

Вот результат:

df1

structure(list(endtime = structure(c(2L, 3L, 1L), .Label = c("1/10/2020 1:45:00 AM", 
"1/7/2020 1:35:08 AM", "1/7/2020 1:39:00 AM"), class = "factor"), 
ID = structure(1:3, .Label = c("A", "B", "C"), class = "factor")), class = "data.frame", row.names =   c(NA, 
 -3L))





 df2

 structure(list(sent = structure(c(3L, 4L, 1L, 2L), .Label = c("1/20/2020 1:55:00 AM", 
 "1/20/2020 2:00:00 AM", "1/7/2020 1:35:20 AM", "1/7/2020 1:42:00 AM"
 ), class = "factor"), ID = structure(c(1L, 2L, 3L, 1L), .Label = c("E", 
"F", "G"), class = "factor")), class = "data.frame", row.names = c(NA, 
-4L))

Это то, что я пробовал:

Я думаю о выполнении левого соединения и сопоставлении значений, или я могу использовать merge (), но сложная часть сопоставляет значения с условным оператором. Любое предложение приветствуется.

library(dplyr)
left_join(df1, df2)

Ответы [ 2 ]

1 голос
/ 19 февраля 2020

Вы также можете сделать это, если ваши наборы данных слишком велики, чтобы сделать декартово произведение:

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 теста:

  1. Реплицированные оба набора данных одинаковое количество раз
  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 соответственно:

enter image description here

enter image description here

РЕДАКТИРОВАТЬ 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>
1 голос
/ 19 февраля 2020

Поскольку нет общего столбца для объединения, мы можем использовать crossing для создания всех комбинаций строк, а затем filter те, которые соответствуют критериям.

library(dplyr)

df1 %>%
  rename(ID1 = 'ID') %>%
  tidyr::crossing(df2) %>%
  mutate_at(vars(endtime, sent), lubridate::mdy_hms) %>%
  filter(abs(difftime(sent, endtime, 'secs')) < 20)

#  endtime             ID1   sent                ID   
#  <dttm>              <fct> <dttm>              <fct>
#1 2020-01-07 01:35:08 A     2020-01-07 01:35:20 E    
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...