Присоединитесь к R data.tables, где ключевые значения не совсем равны - объедините строки с ближайшим временем

Есть ли удобный способ объединить таблицы данных в R, где ключевые значения времени близки, но не совсем одинаковы? Например, предположим, что у меня есть таблица данных с результатами за разные периоды времени:

DT1 = data.table(x=rep(c("a","b","c"),each=3), time=c(10,30,60), v=1:9)

Здесь у нас есть некоторые значения (v) для разных категорий (x), взятые в разное время (время). Теперь предположим, что у меня есть данные из другого источника, который предоставляет некоторые значения времени для разных категорий:

DT2=data.table(x=rep(c("a","b","c"),each=1),time=c(10,10,60))

Мне может быть интересно попытаться сопоставить время в DT2 как можно точнее с DT1, чтобы предсказать значение v для моих категорий DT2. Я бы хотел сделать что-то вроде

setkeyv(DT2,c("x","time"))
merge(DT1,DT2,by=c("time","v")

Что возвращает:

   time x v
1:   10 a 1
2:   10 b 4
3:   60 c 9

Но что, если бы мои времена не имели такой же точности? Например:

DT2=data.table(x=rep(c("a","b","c"),each=1),time=c(17,54,3))

Есть ли способ выполнить подобное слияние, но выбрать время DT2, близкое к времени DT1? То есть 17 будет близко к 30, 54 - к 60, а 3 - к 10?

Если этот простой пример непонятен, я кратко объясню большую проблему, с которой я столкнулся. У меня есть таблица данных со столбцами: категория, время, вывод1, вывод2 ... Существуют сотни категорий со связанными временами. Я мог бы захотеть вывести результат 1 для всех категорий в определенное время. Поскольку время было выбрано без какой-либо очевидной логики, иногда время округляется до ближайшей четной секунды; в других случаях время округляется до ближайшей минуты или даже до 10 минут.

Я мог бы написать сценарий для перезаписи времени в более распространенном формате, но мне любопытно, есть ли интересное решение data.table, которого я не видел. Я безуспешно исследовал скользящее слияние.


person Docuemada    schedule 29.03.2013    source источник
comment
Попробуйте объединить (..., all = TRUE) и просуммировать похожие строки по своему усмотрению.   -  person bfb    schedule 30.03.2013


Ответы (2)


Другой вариант может быть roll='nearest' (новый в версии 1.8.8 для CRAN).

> setkey(DT1,x,time)
> DT1
   x time v
1: a   10 1
2: a   30 2
3: a   60 3
4: b   10 4
5: b   30 5
6: b   60 6
7: c   10 7
8: c   30 8
9: c   60 9
> DT2
   x time
1: a   17
2: b   54
3: c    3
> DT1[DT2,roll="nearest"]
   x time v
1: a   17 1
2: b   54 6
3: c    3 7

Обратите внимание, что 17 кажется ближе к 10, чем к 30, отсюда и результат в первой строке.

Если вам нужно перейти к следующему наблюдению (следующее наблюдение перенесено назад):

> DT1[DT2,roll=-Inf]
   x time v
1: a   17 2
2: b   54 6
3: c    3 7
person Matt Dowle    schedule 30.03.2013
comment
Можно ли также добавить столбец для различий во времени между data.tables? Например. 10-17 = -7 или abs (10-17) = 7. Если, например, нужно исключить значения, которые объединены более чем на 5 шагов друг от друга, или хотите оценить эффект разделения времени. - person ego_; 15.05.2015
comment
@ EndreGrünerOfstad Ограничение устаревания встроено в roll: передача числа ограничивает устаревание этим значением (следовательно, Inf не является пределом). См. ?data.table, презентации и главу 10 курса DataCamp data.table. Вы также можете использовать префиксы x. и i. для явного извлечения разницы в j, например x.time-i.time. - person Matt Dowle; 15.05.2015

Для этого можно использовать findInterval:

setkey(DT2, time)
DT1[, id := findInterval(DT1$time, DT2$time)]
DT2[, id := 1:3]

setkey(DT1, "x", "id")
setkey(DT2, "x", "id")
print(DT1[DT2][, id := NULL])
#    x time v time.1
# 1: a   30 2     17
# 2: b   60 6     54
# 3: c   10 7      3

Идея: сначала отсортируйте data.table по времени, потому что второй аргумент findInterval требует увеличения порядка значений. Теперь используйте findInterval, чтобы найти, в какой интервал 3, 17, 54 попадают значения в DT1$time, и сохраните его в id. В данном конкретном случае это бывает от 1 до 3. Итак, установите эти значения как столбец id для DT2. Как только вы найдете интервалы и получите id, все будет просто. Вместо установки x и time установите x и id в качестве ключей и выполните объединение.

Примечание. Предположим, ваш DT1$time имел значение 0, тогда интервал для этого был бы 0. Итак, вы получите 4 уникальных значения (0: 3). В этом случае может быть лучше иметь DT2 со значением time = 0. Я просто хотел отметить здесь этот момент. Я оставлю это тебе.

person Arun    schedule 30.03.2013
comment
Фантастически полезный ответ. Спасибо за отличное решение, а также за полезное объяснение. - person Docuemada; 30.03.2013
comment
+1! Блестящий метод. Незначительное предложение для обобщения: возможно, используйте DT2[, id := as.numeric(factor(time))] вместо явного идентификатора. или DT2[, id := seq(nrow(DT2))], если не допускаются повторяющиеся значения времени - person Ricardo Saporta; 03.04.2013