2012-05-22 2 views
15

Давайте возьмем следующие данные:R: использование data.table: = операции для вычисления новых столбцов

dt <- data.table(TICKER=c(rep("ABC",10),"DEF"), 
     PERIOD=c(rep(as.Date("2010-12-31"),10),as.Date("2011-12-31")), 
     DATE=as.Date(c("2010-01-05","2010-01-07","2010-01-08","2010-01-09","2010-01-10","2010-01-11","2010-01-13","2010-04-01","2010-04-02","2010-08-03","2011-02-05")), 
     ID=c(1,2,1,3,1,2,1,1,2,2,1),VALUE=c(1.5,1.3,1.4,1.6,1.4,1.2,1.5,1.7,1.8,1.7,2.3)) 
setkey(dt,TICKER,PERIOD,ID,DATE) 

Теперь для каждой комбинации тикер/период, мне нужно следующее в новом столбце:

  • PRIORAVG: Среднее значение из VALUE каждого идентификатора, за исключением текущего идентификатора, при условии, что оно не превышает 180 дней.
  • PREV: Предыдущее значение из того же ID.

Результат должен выглядеть следующим образом:

 TICKER  PERIOD  DATE ID VALUE PRIORAVG PREV 
[1,] ABC 2010-12-31 2010-01-05 1 1.5  NA NA 
[2,] ABC 2010-12-31 2010-01-08 1 1.4  1.30 1.5 
[3,] ABC 2010-12-31 2010-01-10 1 1.4  1.45 1.4 
[4,] ABC 2010-12-31 2010-01-13 1 1.5  1.40 1.4 
[5,] ABC 2010-12-31 2010-04-01 1 1.7  1.40 1.5 
[6,] ABC 2010-12-31 2010-01-07 2 1.3  1.50 NA 
[7,] ABC 2010-12-31 2010-01-11 2 1.2  1.50 1.3 
[8,] ABC 2010-12-31 2010-04-02 2 1.8  1.65 1.2 
[9,] ABC 2010-12-31 2010-08-03 2 1.7  1.70 1.8 
[10,] ABC 2010-12-31 2010-01-09 3 1.6  1.35 NA 
[11,] DEF 2011-12-31 2011-02-05 1 2.3  NA NA 

Обратите внимание на PRIORAVG по строке 9 равен 1,7 (что равно VALUE на строке 5, которая является единственным перед наблюдением в прошлом 180 дней другим ID)

Я обнаружил пакет data.table, но я не могу полностью понять функцию :=. Когда я держу это просто, это работает. Для того, чтобы получить предыдущее значение для каждого идентификатора (я на основе этого на решение this question):

dt[,PREV:=dt[J(TICKER,PERIOD,ID,DATE-1),roll=TRUE,mult="last"][,VALUE]] 

Это прекрасно работает, и это занимает всего 0,13 секунды, чтобы выполнить эту операцию над моим набором данных с ~ 250K строк; моя функция векторного сканирования получает одинаковые результаты, но примерно в 30 000 раз медленнее.

Итак, у меня есть мое первое требование. Давайте перейдем ко второму, более сложному требованию. Прямо сейчас постиженный метод до сих пор использует пару векторных сканов и бросает функцию через функцию plyradply, чтобы получить результат для каждой строки.

calc <- function(df,ticker,period,id,date) { 
    df <- df[df$TICKER == ticker & df$PERIOD == period 
     & df$ID != id & df$DATE < date & df$DATE > date-180, ] 
    df <- df[order(df$DATE),] 
    mean(df[!duplicated(df$ID, fromLast = TRUE),"VALUE"]) 
} 

df <- data.frame(dt) 
adply(df,1,function(x) calc(df,x$TICKER,x$PERIOD,x$ID,x$DATE)) 

Я написал функцию для data.frame и это не похоже на работу с data.table. Для подмножества 5000 строк это занимает около 44 секунд, но мои данные состоят из> 1 миллиона строк. Интересно, может ли это быть более эффективным благодаря использованию :=.

dt[J("ABC"),last(VALUE),by=ID][,mean(V1)] 

Это работает, чтобы выбрать среднее значение из последних значений VALUE для каждого идентификатора для ABC.

dt[,PRIORAVG:=dt[J(TICKER,PERIOD),last(VALUE),by=ID][,mean(V1)]] 

Это, однако, не работает, как и следовало ожидать, поскольку она занимает среднее значение всех последних значений для всех тикер/периодов вместо только для текущего тикер/период. Таким образом, он заканчивается тем, что все строки получают одинаковое среднее значение. Я что-то делаю неправильно или это ограничение :=?

+1

Подсказки: присоедините унаследованную область для преобладающего наблюдения за последние 180 дней (с использованием префикса 'i.':' [, j = list (..., age = PERIOD-i.PERIOD, ...),] [age <180] 'и' mult = "last" ', а не' last() ', может быть. –

+1

Кажется, что панель данных по-разному связана с выпиской кода над ней, а в ней отсутствует') ' кажется. –

+0

добавлены данные, которые показывают ожидаемые результаты в течение 180 дней. – Dirk

ответ

12

Большой вопрос. Попробуйте следующее:

dt 
    TICKER  PERIOD  DATE ID VALUE 
[1,] ABC 2010-12-31 2010-01-05 1 1.5 
[2,] ABC 2010-12-31 2010-01-08 1 1.4 
[3,] ABC 2010-12-31 2010-01-10 1 1.4 
[4,] ABC 2010-12-31 2010-01-13 1 1.5 
[5,] ABC 2010-12-31 2010-01-07 2 1.3 
[6,] ABC 2010-12-31 2010-01-11 2 1.2 
[7,] ABC 2010-12-31 2010-01-09 3 1.6 
[8,] DEF 2011-12-31 2011-02-05 1 2.3 

ids = unique(dt$ID) 
dt[,PRIORAVG:=NA_real_] 
for (i in 1:nrow(dt)) 
    dt[i,PRIORAVG:=dt[J(TICKER[i],PERIOD[i],setdiff(ids,ID[i]),DATE[i]), 
         mean(VALUE,na.rm=TRUE),roll=TRUE,mult="last"]] 
dt 
    TICKER  PERIOD  DATE ID VALUE PRIORAVG 
[1,] ABC 2010-12-31 2010-01-05 1 1.5  NA 
[2,] ABC 2010-12-31 2010-01-08 1 1.4  1.30 
[3,] ABC 2010-12-31 2010-01-10 1 1.4  1.45 
[4,] ABC 2010-12-31 2010-01-13 1 1.5  1.40 
[5,] ABC 2010-12-31 2010-01-07 2 1.3  1.50 
[6,] ABC 2010-12-31 2010-01-11 2 1.2  1.50 
[7,] ABC 2010-12-31 2010-01-09 3 1.6  1.35 
[8,] DEF 2011-12-31 2011-02-05 1 2.3  NA 

Тогда у вас уже было небольшое облегчение ...

dt[,PREV:=dt[J(TICKER,PERIOD,ID,DATE-1),VALUE,roll=TRUE,mult="last"]] 

    TICKER  PERIOD  DATE ID VALUE PRIORAVG PREV 
[1,] ABC 2010-12-31 2010-01-05 1 1.5  NA NA 
[2,] ABC 2010-12-31 2010-01-08 1 1.4  1.30 1.5 
[3,] ABC 2010-12-31 2010-01-10 1 1.4  1.45 1.4 
[4,] ABC 2010-12-31 2010-01-13 1 1.5  1.40 1.4 
[5,] ABC 2010-12-31 2010-01-07 2 1.3  1.50 NA 
[6,] ABC 2010-12-31 2010-01-11 2 1.2  1.50 1.3 
[7,] ABC 2010-12-31 2010-01-09 3 1.6  1.35 NA 
[8,] DEF 2011-12-31 2011-02-05 1 2.3  NA NA 

Если это нормально в качестве прототипа, то большое увеличение скорости будет держать цикл, но использовать set() вместо :=, чтобы уменьшить накладные расходы:

for (i in 1:nrow(dt)) 
    set(dt,i,6L,dt[J(TICKER[i],PERIOD[i],setdiff(ids,ID[i]),DATE[i]), 
        mean(VALUE,na.rm=TRUE),roll=TRUE,mult="last"]) 
dt 
    TICKER  PERIOD  DATE ID VALUE PRIORAVG PREV 
[1,] ABC 2010-12-31 2010-01-05 1 1.5  NA NA 
[2,] ABC 2010-12-31 2010-01-08 1 1.4  1.30 1.5 
[3,] ABC 2010-12-31 2010-01-10 1 1.4  1.45 1.4 
[4,] ABC 2010-12-31 2010-01-13 1 1.5  1.40 1.4 
[5,] ABC 2010-12-31 2010-01-07 2 1.3  1.50 NA 
[6,] ABC 2010-12-31 2010-01-11 2 1.2  1.50 1.3 
[7,] ABC 2010-12-31 2010-01-09 3 1.6  1.35 NA 
[8,] DEF 2011-12-31 2011-02-05 1 2.3  NA NA 

Это должно быть намного быстрее, чем повторяющиеся векторные проверки, показанные в вопросе.

Или операция может быть векторизованной. Но это было бы легче писать и читать из-за особенностей этой задачи.

Btw, нет данных в вопросе, которые будут проверять требование 180 дней. Если вы добавите некоторые из них и покажете ожидаемый результат, я добавлю счет возраста, используя присоединенный к унаследованной области, о котором я упомянул в комментариях.

+0

Отличный ответ. Для вычисления первой части моего набора данных (180 тыс. Строк) требуется всего лишь 20 минут против нескольких часов для векторного метода. Мне нравится использовать setdiff(), чтобы выбрать все, кроме текущего ID, но я думаю, что это может немного замедлить работу с большим количеством идентификаторов (в моем наборе данных 6000 и только в среднем 16 идентификаторов за тикер). – Dirk

+0

Хорошо. 20 минут все еще звучат очень долго для этой задачи. Использование 'set()'? Во всяком случае, как мантра идет, 'Rprof',' Rprof', 'Rprof'. Да на 'setdiff()' (если 'Rprof' показывает, что вызывает время), вы можете сделать это заранее и сохранить список или среду« других »идентификаторов для каждого идентификатора, а затем просто просмотреть его. Или может быть более простой способ, которого я пропускаю. –

+0

Это действительно с использованием 'set()'. 'setdiff()' сам не занимает много времени, это подмножество, используя вывод 'setdiff()', который делает. Тестирование с подмножеством в 5 тыс. Строк, увеличение «идентификаторов» с 738 до 5866 добавляет 60% времени вычисления. – Dirk

Смежные вопросы