Эффективен ли мой способ дублирования строк в data.table?

#r #data.table

#r #data.table

Вопрос:

У меня есть ежемесячные данные в одном data.table и годовые данные в другом data.table , и теперь я хочу сопоставить годовые данные с соответствующим наблюдением в ежемесячных данных.

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

Вот пример data.table DT для моих годовых данных и того, как я в настоящее время дублирую:

 library(data.table)
DT <- data.table(ID = paste(rep(c("a", "b"), each=3), c(1:3, 1:3), sep="_"),
                    values = 10:15,
                    startMonth = seq(from=1, by=2, length=6),
                    endMonth = seq(from=3, by=3, length=6))
DT
      ID values startMonth endMonth
[1,] a_1     10          1        3
[2,] a_2     11          3        6
[3,] a_3     12          5        9
[4,] b_1     13          7       12
[5,] b_2     14          9       15
[6,] b_3     15         11       18
#1. Alternative
DT1 <- DT[, list(MONTH=startMonth:endMonth), by="ID"]
setkey(DT,  ID)
setkey(DT1, ID)
DT1[DT]
ID MONTH values startMonth endMonth
a_1     1     10          1        3
a_1     2     10          1        3
a_1     3     10          1        3
a_2     3     11          3        6
[...]
  

Последнее соединение — это именно то, что я хочу. Тем не менее, DT[, list(MONTH=startMonth:endMonth), by="ID"] уже делает все, что я хочу, кроме добавления других столбцов в DT , поэтому мне было интересно, могу ли я избавиться от последних трех строк в моем коде, то есть от операций setkey и join . Оказывается, вы можете просто сделать следующее:

 #2. Alternative: More intuitiv and just one line of code
DT[, list(MONTH=startMonth:endMonth, values, startMonth, endMonth), by="ID"]
 ID MONTH values startMonth endMonth
a_1    1     10          1        3
a_1    2     10          1        3
a_1    3     10          1        3
a_2    3     11          3        6
...
  

Это, однако, работает только потому, что я жестко закодировал имена столбцов в list выражение. В моих реальных данных я не знаю названия всех столбцов заранее, поэтому мне было интересно, могу ли я просто сказать, data.table чтобы я вернул столбец MONTH , который я вычисляю, как показано выше, и все остальные столбцы DT . .SD казалось, что это удалось, но:

 DT[, list(MONTH=startMonth:endMonth, .SD), by="ID"]
Error in `[.data.table`(DT, , list(YEAR = startMonth:endMonth, .SD), by = "ID") : 
  maxn (4) is not exact multiple of this j column's length (3)
  

Итак, подводя итог, я знаю, как это было сделано, но мне просто интересно, лучший ли это способ сделать это, потому что я все еще немного борюсь с синтаксисом data.table и часто читаю в сообщениях и в вики, что есть хорошие и плохие способы сделать что-то. Кроме того, я не совсем понимаю, почему я получаю ошибку при использовании .SD . Я думал, что это просто любой простой способ сообщить data.table , что вам нужны все столбцы. Что я упускаю?

Ответ №1:

Глядя на это, я понял, что ответ был возможен только потому, что ID был уникальным ключом (без дубликатов). Вот еще один ответ с дубликатами. Но, кстати, некоторые NA , похоже, проникают. Может ли это быть ошибкой? Я использую версию v1.8.7 (фиксация 796).

 library(data.table)
DT <- data.table(x=c(1,1,1,1,2,2,3),y=c(1,1,2,3,1,1,2))

DT[,rep:=1L][c(2,7),rep:=c(2L,3L)]   # duplicate row 2 and triple row 7
DT[,num:=1:.N]                       # to group each row by itself

DT
   x y rep num
1: 1 1   1   1
2: 1 1   2   2
3: 1 2   1   3
4: 1 3   1   4
5: 2 1   1   5
6: 2 1   1   6
7: 3 2   3   7

DT[,cbind(.SD,dup=1:rep),by="num"]
    num x y rep dup
 1:   1 1 1   1   1
 2:   2 1 1   1  NA      # why these NA?
 3:   2 1 1   2  NA
 4:   3 1 2   1   1
 5:   4 1 3   1   1
 6:   5 2 1   1   1
 7:   6 2 1   1   1
 8:   7 3 2   3   1
 9:   7 3 2   3   2
10:   7 3 2   3   3
  

Просто для полноты картины более быстрый способ — rep указать номера строк, а затем взять подмножество за один шаг (без группировки и без использования cbind или .SD ) :

 DT[rep(num,rep)]
    x y rep num
 1: 1 1   1   1
 2: 1 1   2   2
 3: 1 1   2   2
 4: 1 2   1   3
 5: 1 3   1   4
 6: 2 1   1   5
 7: 2 1   1   6
 8: 3 2   3   7
 9: 3 2   3   7
10: 3 2   3   7
  

где в этом примере данных столбец rep имеет то же имя, что и rep() базовая функция.

Комментарии:

1. Спасибо. Я запустил его (v1.8.7), но я не вижу NA . Какая версия у вас есть?

2. Спасибо. Я все еще не вижу NA , но теперь я получаю два одинаковых предупреждения: In 1:rep : numerical expression has 2 elements: only the first used

3. Попробуйте последнюю версию (796) в качестве первого шага, пожалуйста, просто чтобы исключить это.

4. Хорошо, я попробую еще раз. Тогда давайте оставим этот вариант в S.O., а не в datatable-help. Спасибо…

5. @MatthewDowle Я могу воспроизвести NA s, закомментировав последнюю строку или изменив назначение на что-то другое, чем DT в первом блоке кода. Я думаю, что DT <- DT[,cbind(dup=1:rep,.SD),by="num"] и DT <- DT[,cbind(.SD,dup=1:rep),by="num"] должны быть альтернативами, но первый заменяет DT .

Ответ №2:

Отличный вопрос. То, что вы пробовали, было очень разумным. Предполагая, что вы используете версию v1.7.1, теперь стало проще создавать list столбцы. В этом случае он пытается создать один list столбец из .SD (3 элемента) рядом со столбцом МЕСЯЦА 2-й группы (4 элемента). Я назову это ошибкой [РЕДАКТИРОВАТЬ: теперь исправлено в версии v1.7.5], спасибо.

А пока попробуйте :

 DT[, cbind(MONTH=startMonth:endMonth, .SD), by="ID"]
 ID MONTH values startMonth endMonth
a_1     1     10          1        3
a_1     2     10          1        3
a_1     3     10          1        3
a_2     3     11          3        6
...
  

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

Ответ №3:

Вот функция, которую я написал, которая имитирует disaggregate (мне нужно было что-то, что обрабатывало сложные данные). Это может быть полезно для вас, если это не перебор. Чтобы развернуть только строки, задайте аргументу fact значение c(1,12), где 12 будет для 12 строк «месяца» для каждой строки «года».

 zexpand<-function(inarray, fact=2, interp=FALSE,  ...)  {
fact<-as.integer(round(fact))
switch(as.character(length(fact)),
        '1' = xfact<-yfact<-fact,
        '2'= {xfact<-fact[1]; yfact<-fact[2]},
        {xfact<-fact[1]; yfact<-fact[2];warning(' fact is too long. First two values used.')})
if (xfact < 1) { stop('fact[1] must be > 0') } 
if (yfact < 1) { stop('fact[2] must be > 0') }
# new nonloop method, seems to work just ducky
bigtmp <- matrix(rep(t(inarray), each=xfact), nrow(inarray), ncol(inarray)*xfact, byr=T)   
#does column expansion
bigx <- t(matrix(rep((bigtmp),each=yfact),ncol(bigtmp),nrow(bigtmp)*yfact,byr=T))
return(invisible(bigx))
}
  

Ответ №4:

Самый быстрый и лаконичный способ сделать это:

 DT[rep(1:nrow(DT), endMonth - startMonth)]
  

Мы также можем перечислять по группам с помощью:

 dd <- DT[rep(1:nrow(DT), endMonth - startMonth)]
dd[, nn := 1:.N, by = ID]
dd
  

Комментарии:

1. Мы также можем перечислять по группам с помощью: dd= DT[rep(1: nrow (DT), endMonth-startMonth)]

2. Мы также можем перечислять по группам с помощью: code dd=DT[rep(1:nrow (DT), endMonth-startMonth)] dd[,nn:=1:.N,by=ID] code

3. Пожалуйста, отредактируйте свой ответ, не заполняйте раздел комментариев. Используйте ссылку редактировать выше.

4. Это не отвечает на вопрос и даже не воспроизводит ожидаемый результат. В вашем результате всего 27 строк, в то время как ожидаемый результат имеет 33. Перечисление по группам всегда начинается с 1 для каждой группы и, следовательно, не возвращает MONTH числа. Все это стало бы сразу видно, если бы вы добавили некоторые выходные данные к своему ответу.