Найдите минимальное расстояние между двумя кадрами данных, для каждого элемента во втором кадре данных

У меня есть два кадра данных ev1 и ev2, описывающие временные метки двух типов событий, собранных во многих тестах. Таким образом, каждый кадр данных имеет столбцы "test_id" и "timestamp". То, что мне нужно найти, - это минимальное расстояние ev1 для каждого ev2 в том же тесте.

У меня есть рабочий код, который объединяет два набора данных, вычисляет расстояния и затем использует dplyr для фильтрации минимального расстояния:

ev1 = data.frame(test_id = c(0, 0, 0, 1, 1, 1), time=c(1, 2, 3, 2, 3, 4))
ev2 = data.frame(test_id = c(0, 0, 0, 1, 1, 1), time=c(6, 1, 8, 4, 5, 11))

data <- merge(ev2, ev1, by=c("test_id"), suffixes=c(".ev2", ".ev1"))

data$distance <- data$time.ev2 - data$time.ev1

min_data <- data %>%
  group_by(test_id, time.ev2) %>%
  filter(abs(distance) == min(abs(distance)))

Пока это работает, часть слияния очень медленная и кажется неэффективной - я создаю огромную таблицу со всеми комбинациями ev2- > ev1 для одного и того же test_id, только чтобы отфильтровать ее до единицы. Кажется, должен быть способ "фильтровать" на лету "во время слияния. Здесь?

Обновить. Следующий случай с двумя столбцами "group by" не работает, если используется метод data.table, описанный akrun:

ev1 = data.frame(test_id = c(0, 0, 0, 1, 1, 1), time=c(1, 2, 3, 2, 3, 4), group_id=c(0, 0, 0, 1, 1, 1))
ev2 = data.frame(test_id = c(0, 0, 0, 1, 1, 1), time=c(5, 6, 7, 1, 2, 8), group_id=c(0, 0, 0, 1, 1, 1))
setkey(setDT(ev1), test_id, group_id)
DT <- ev1[ev2, allow.cartesian=TRUE][,distance:=abs(time-i.time)]

Ошибка в eval (expr, envir, enc): объект "i.time" не найден

Ответ 1

Вот как бы я это сделал, используя data.table:

require(data.table)
setkey(setDT(ev1), test_id)
ev1[ev2, .(ev2.time = i.time, ev1.time = time[which.min(abs(i.time - time))]), by = .EACHI]
#    test_id ev2.time ev1.time
# 1:       0        6        3
# 2:       0        1        1
# 3:       0        8        3
# 4:       1        4        4
# 5:       1        5        4
# 6:       1       11        4

При объединении формы x[i] в data.table префикс i. используется для ссылки на столбцы в i, когда оба x и i используют одно и то же имя для определенного столбца.

Пожалуйста, см. этот SO сообщение для объяснения того, как это работает.

Синтаксически проще понять, что происходит, и эффективно с точки зрения памяти (за счет небольшой скорости 1), поскольку она вообще не материализует весь результат объединения. Фактически, это делает именно то, что вы говорите в своем постфильтре "на лету", при слиянии.

  • На скорости это не имеет значения в большинстве случаев. Если в i есть много строк, это может быть немного медленнее, так как выражение j должно быть оценено для каждой строки в i. Напротив, ответ @akrun делает декартовое соединение, за которым следует одна фильтрация. Поэтому, несмотря на то, что он имеет высокую память, он не оценивает j для каждой строки в i. Но опять же, это не должно даже иметь значения, если вы не работаете с действительно большим i, что не так часто бывает.

НТН

Ответ 2

Возможно, это поможет:

library(data.table)
setkey(setDT(ev1), test_id)
DT <- ev1[ev2, allow.cartesian=TRUE][,distance:=time-i.time]
DT[DT[,abs(distance)==min(abs(distance)), by=list(test_id, i.time)]$V1]
#    test_id time i.time distance
#1:       0    3      6        3
#2:       0    1      1        0
#3:       0    3      8        5
#4:       1    4      4        0
#5:       1    4      5        1
#6:       1    4     11        7

или

 ev1[ev2, allow.cartesian=TRUE][,distance:= time-i.time][,
      .SD[abs(distance)==min(abs(distance))], by=list(test_id, i.time)]

Update

Использование новой группировки

setkey(setDT(ev1), test_id, group_id)
setkey(setDT(ev2), test_id, group_id)
DT <- ev1[ev2, allow.cartesian=TRUE][,distance:=i.time-time]
DT[DT[,abs(distance)==min(abs(distance)), by=list(test_id, 
                                group_id,i.time)]$V1]$distance
#[1]  2  3  4 -1  0  4

На основе кода, который вы предоставили

min_data$distance
#[1]  2  3  4 -1  0  4