Найдите минимальное расстояние между двумя кадрами данных, для каждого элемента во втором кадре данных
У меня есть два кадра данных 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