Намерете минималното разстояние между два кадъра с данни за всеки елемент във втория кадър с данни

Имам два кадъра с данни 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, само за да я филтрирам до едно. Изглежда, че трябва да има начин за "филтриране в движение", по време на сливането. Е там?

Актуализация: Следният случай с две колони „групиране по“ е неуспешен, когато се използва подход 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, enclos): обектът „i.time“ не е намерен


person Stan    schedule 14.12.2014    source източник
comment
пробвал ли си left_join(ev2,ev1, by=c("test_id") ) вместо merge?   -  person Khashaa    schedule 14.12.2014
comment
@Khashaa Не, не съм. Сега го пробвах и е безкрайно по-бързо. Това просто много по-добро внедряване на същото нещо ли е или се случва нещо друго?   -  person Stan    schedule 14.12.2014
comment
@Stan Опитвал ли си подхода data.table. Трябва да е много бързо.   -  person akrun    schedule 14.12.2014
comment
@Stan Това е dplyr аналогът на merge(., all.x=TRUE).   -  person Khashaa    schedule 14.12.2014


Отговори (2)


Ето как бих го направил с 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), тъй като изобщо не материализира целия резултат от свързването. Всъщност това прави точно това, което казвате в публикацията си - филтрирайте в движение, докато обединявате.

  1. Що се отнася до скоростта, в повечето случаи наистина няма значение. Ако има много редове в i, това може да е малко по-бавно, тъй като j-изразът ще трябва да бъде оценен за всеки ред в i. За разлика от това, отговорът на @akrun прави декартово съединение, последвано от едно филтриране. Така че, докато има много памет, той не оценява j за всеки ред в i. Но отново, това дори не би трябвало да има значение, освен ако не работите с наистина големи i, което не е често случаят.

HTH

person Arun    schedule 14.12.2014
comment
Това е перфектно -- както казахте, точно това, което се надявах да намеря, нещо, което не създава огромното присъединяване като начало. Когато казвате „за сметка на малка скорост“, какво имате предвид? Какво прави това малко по-бавно от създаването на пълно присъединяване и след това филтриране? На моя голям тест изглеждаше дори малко по-бързо. - person Stan; 14.12.2014
comment
@Стан, страхотно! Радвам се да чуя, че помогна. Редактирано, за да изясни вашето Q в публикацията. - person Arun; 14.12.2014

Може би това помага:

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

Or

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

Актуализация

Използване на новото групиране

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
person akrun    schedule 14.12.2014
comment
Не съм запознат с data.table, така че ще трябва да го разгледам по-отблизо, за да го разбера :). Едно нещо, което трябваше да променя, е да преместя abs() на втория ред - abs(distance) == min(abs(distance)), така че да мога да получа отрицателни разстояния (т.е. ev2 се случи след ev1). - person Stan; 14.12.2014
comment
Един въпрос -- моят пример от реалния живот имаше две колони (да ги наречем test_id и group_id), които заедно образуваха ключа за групиране. Как да променя кода по-горе, за да стане това? Опитах setkey(setDT(ev1), test_id, group_id), който премина, но след това получих грешка на следващия ред, че обектът 'i.time' не е намерен, което ме озадачи. - person Stan; 14.12.2014
comment
@Stan Да, можете да преместите това на втория ред. Гледах само min_data като справка. Можете ли да покажете пример, който дава грешката (улеснява гледането)? - person akrun; 14.12.2014
comment
Добавих актуализация с пример, който ми дава грешката. - person Stan; 14.12.2014
comment
@Stan Можете да използвате setkey(setDT(ev2), test_id, group_id) и след това да опитате стъпките с by=list(test_id, group_id, i.time)] - person akrun; 14.12.2014
comment
Не стигам дотам обаче, грешката се случва на линията DT ‹- .... След малко експериментиране редът на колоните изглежда има значение. Ако group_id точно след test_id, вашият код работи. Ако е в края (както в примера по-горе), тогава не е така. - person Stan; 14.12.2014
comment
@Stan Моля, покажете пример, който имитира вашия набор от данни. От тестването на кода с кода, който показахте, той дава същия резултат. - person akrun; 14.12.2014
comment
@Stan Опитах и ​​двата начина и работи setkey(setDT(ev1), group_id, test_id); setkey(setDT(ev2), group_id, test_id); ev1[ev2, allow.cartesian=TRUE][,distance:=i.time-time][] test_id time group_id i.time distance 1: 0 1 0 5 4 2: 0 2 0 5 3 - person akrun; 14.12.2014
comment
Нямах реда 'setkey(setDT(ev2))', когато тествах -- нямаше го в първия ви пример. Въпреки това, след като го поставих, кодът работи и е много бърз. Благодаря ти! - person Stan; 14.12.2014