R - Ускорение приблизительной даты совпадения. idata.frame? - PullRequest
7 голосов
/ 14 февраля 2012

Я изо всех сил пытаюсь эффективно выполнить «близкое» совпадение даты между двумя фреймами данных.В этом вопросе рассматривается решение с использованием idata.frame из пакета plyr, но я был бы очень рад и другим предлагаемым решениям.

Вот очень упрощенная версия двух фреймов данных:

sampleticker<-data.frame(cbind(ticker=c("A","A","AA","AA"),
  date=c("2005-1-25","2005-03-30","2005-02-15","2005-04-21")))
sampleticker$date<-as.Date(sampleticker$date,format="%Y-%m-%d")

samplereport<-data.frame(cbind(ticker=c("A","A","A","AA","AA","AA"),
  rdate=c("2005-2-15","2005-03-15","2005-04-15",
  "2005-03-01","2005-04-20","2005-05-01")))
samplereport$rdate<-as.Date(samplereport$rdate,format="%Y-%m-%d")

В реальных данных sampleticker - это более 30 000 строк с 40 столбцами, а samplereport - почти 300 000 строк с 25 столбцами.

Я хотел бы объединить двафреймы данных, так что каждая строка в sampleticker объединяется с ближайшим совпадением даты в samplereport, которое происходит ПОСЛЕ даты в sampleticker.В прошлом я решал подобные проблемы, выполняя простое слияние в поле тикера, сортируя по возрастанию, а затем выбирая уникальные комбинации тикера и даты.Однако из-за размера этого набора данных слияние происходит очень быстро.

Насколько я могу судить, merge не допускает такого рода приблизительного сопоставления.Я видел некоторые решения, в которых используется findInterval, но, поскольку расстояние между датами будет различаться, я не уверен, что смогу указать интервал, который будет работать для всех строк.

После другой записи здесь , я написал следующий код для использования adply в каждой строке и для выполнения объединения:

library(plyr)
merge<-adply(sampleticker,1,function(x){
  y<-subset(samplereport,ticker %in% x$ticker & rdate > x$date)
  y[which.min(y$rdate),]
  }))

Это работает довольно хорошо: для примеров данных я получаю следующее, котороеэто то, что я хочу.

   date       ticker      rdate
 1 2005-01-25  A          2005-02-15
 2 2005-03-30  A          2005-04-15
 3 2005-02-15  AA         2005-03-01
 4 2005-04-21  AA         2005-05-01

Однако, поскольку код выполняет более 30 000 операций с подмножествами, он очень медленный: я выполнил указанный выше запрос более чем за день до того, как окончательно его убил.

Я вижу здесь , что plyr 1.0 имеет структуру idata.frame, которая вызывает фрейм данных по ссылке, значительно ускоряя операцию поднабора.Однако я не могу заставить работать следующий код:

isamplereport<-idata.frame(samplereport)
adply(sampleticker,1,function(x){
  y<-subset(isamplereport,isamplereport$ticker %in% x$ticker & 
    isamplereport$rdate > x$date)
  y[which.min(y$rdate),]
})

Я получаю ошибку

Error in list_to_dataframe(res, attr(.data, "split_labels")) : 
Results must be all atomic, or all data frames

Это имеет смысл для меня, так как операция возвращает idata.frame (я предполагаю,).Однако изменение последней строки на:

as.data.frame(y[which.min(y$rdate),]) 

также приводит к ошибке:

Error in `[.data.frame`(x$`_data`, x$`_rows`, x$`_cols`) : 
undefined columns selected.

Обратите внимание, что вызов as.data.frame для простого старого samplereport возвращает исходный кадр данных,как и ожидалось.

Я знаю, что idata.frame является экспериментальным, поэтому я не обязательно ожидал, что он будет работать должным образом.Однако, если у кого-то есть идея, как это исправить, я был бы признателен.С другой стороны, если бы кто-то мог предложить совершенно другой подход, который работает более эффективно, это было бы фантастически.

Matt

UPDATE Data.table - правильный путьэтот.Смотри ниже.

Ответы [ 3 ]

8 голосов
/ 07 марта 2013

Благодаря Мэтью Доулю и его добавлению возможности перекатываться назад и вперед в data.table, теперь это объединение стало намного проще.

ST <- data.table(sampleticker)
SR <- data.table(samplereport)
setkey(ST,ticker,date)
SR[,mergerdate:=rdate]
setkey(SR,ticker,mergerdate)
merge<-SR[ST,roll=-Inf]
setnames(merge,"mergerdate","date")

#    ticker       date      rdate
# 1:      A 2005-01-25 2005-02-15
# 2:      A 2005-03-30 2005-04-15
# 3:     AA 2005-02-15 2005-03-01
# 4:     AA 2005-04-21 2005-05-01
6 голосов
/ 14 февраля 2012

Вот решение на основе data.table, которое, вероятно, будет работать лучше, чем то, что вы используете в настоящее время:

library(data.table)
ST <- data.table(sampleticker, key="ticker")
SR <- data.table(samplereport, key="ticker")
SR <- SR[with(SR, order(ticker, rdate)),] # rdates need to be in increasing order

SR[ST, list(date = date,
            rdate = rdate[match(TRUE, (rdate > date))]), ]
     ticker       date      rdate
[1,]      A 2005-01-25 2005-02-15
[2,]      A 2005-03-30 2005-04-15
[3,]     AA 2005-02-15 2005-03-01
[4,]     AA 2005-04-21 2005-05-01

Конечно, кажется, что вы действительно хотите объединить двагораздо шире data.frames.Чтобы продемонстрировать один из способов достижения этого, в следующем примере я добавляю несколько столбцов в оба data.tables, а затем показываю, как можно объединить соответствующие строки:

# Add some columns to both data.tables
ST$alpha <- letters[seq_len(nrow(ST))]
SR$n     <- seq_len(nrow(SR))
SR$ALPHA <- LETTERS[seq_len(nrow(SR))]

# Perform a merge that includes the whole rows from samplereport
# corresponding to the selected rdate
RES <- SR[ST, cbind(date, .SD[match(TRUE,(rdate>date)),-1, with=FALSE]), ]

# Merge res (containing the selected rows from samplereport) back together
# with sampleticker
keycols <- c("ticker", "date")
setkeyv(RES, keycols)
setkeyv(ST, keycols)
ST[RES]
#      ticker       date alpha      rdate n ALPHA
# [1,]      A 2005-01-25     a 2005-02-15 1     A
# [2,]      A 2005-03-30     b 2005-04-15 3     C
# [3,]     AA 2005-02-15     c 2005-03-01 4     D
# [4,]     AA 2005-04-21     d 2005-05-01 6     F
4 голосов
/ 14 февраля 2012

Вот решение, которое следует из наблюдения Мэтью Доула, что это естественное место для применения data.table roll=TRUE аргумента.

Если вы хотите его применить, есть одна морщинка, которую нужно сгладить. roll=TRUE разработан таким образом, что когда точное совпадение не найдено для последнего столбца ключа (здесь дата), значение ближайшей предыдущей даты будет свернуто вперед, Тем не менее, вы хотите противоположного (и даже при точном совпадении вы все еще хотите получить значение со следующей доступной даты).

Первой попыткой может быть сортировка по "ticker", а по "rdate" в обратном порядке , слияние с результирующим переупорядочением SR. Это сработало бы, за исключением того, что data.table не хочет разрешать вам сортировку в обратном порядке: при нажатии "rdate" этот столбец переводится в порядке возрастания. (data.table необходимо сделать это для того, чтобы реализовать быстрое сопоставление и соединение, для которых оно было разработано).

Мое решение ниже - создать новый столбец - "rnd" для «обратной числовой даты» - в обеих таблицах data.table, значения которых формируются с помощью -as.numeric(date). Это присваивает уникальное значение каждой дате. Более того, поскольку значения были умножены на -1, сортировка их в порядке возрастания приводит к сортировке дат в порядке убывания .

(Еще одна деталь: поскольку вам не нужны точные совпадения, а вместо этого всегда требуется следующая дата после текущей, я вычел 1 из rnd образца выборки, что дает желаемый эффект. Чтобы подтвердить что он делает свою работу правильно, я немного отредактировал данные вашего примера, чтобы включить одно возможное точное совпадение ("2005-1-25"), которое не должно быть выбрано слиянием).

# Create sample data.tables
library(data.table)

ST <- data.table(ticker = c("A","A","AA","AA"),
                 date = as.Date(c("2005-1-25","2005-03-30","2005-02-15",
                                  "2005-04-21"), format="%Y-%m-%d"),
                 alpha = letters[1:4])    

SR <- data.table(ticker = c("A","A","A","AA","AA","AA"),
                 rdate = as.Date(c("2005-1-25","2005-03-15","2005-04-15",
                                   "2005-03-01","2005-04-20","2005-05-01"), 
                                   format="%Y-%m-%d"),
                 ALPHA = LETTERS[1:6])

Имея образцы данных в руках, настройте и выполните желаемое объединение:

# Create a "reverse numerical date" column, which will uniquely
# identify date, and allow them to be sorted in reverse temporal order
ST$rnd <- -(as.numeric(ST$date) + 1)
SR$rnd <- -(as.numeric(SR$rdate))

# key (and thus sort) both data.tables by ticker and "reverse numerical date"
keycols <- c("ticker", "rnd")
setkeyv(ST, keycols)
setkeyv(SR, keycols)

# The syntax of the merge is now as simple as can be
res <- SR[ST, roll=TRUE]

# Finally, put the results back in temporal order, and pretty up the column order
setkeyv(res, c("ticker", "date"))
setcolorder(res, c("ticker", "date", "rdate", "alpha", "ALPHA", "rnd"))
res
#      ticker       date      rdate alpha ALPHA    rnd
# [1,]      A 2005-01-25 2005-03-15     a     B -12809
# [2,]      A 2005-03-30 2005-04-15     b     C -12873
# [3,]     AA 2005-02-15 2005-03-01     c     D -12830
# [4,]     AA 2005-04-21 2005-05-01     d     F -12895
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...