Является ли способ дублирования строк в 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 length (3)
Итак, чтобы обобщить, я знаю, как это было сделано, но мне было просто интересно, если это лучший способ сделать это, потому что я все еще немного борется с синтаксисом data.table
и часто читаю в сообщениях и на вики, что есть хорошие и плохие способы делать что-то. Кроме того, я не совсем понимаю, почему я получаю сообщение об ошибке при использовании .SD
. Я думал, что просто любой простой способ сказать data.table
, что вы хотите все столбцы. Что мне не хватает?
Ответы
Ответ 1
Отличный вопрос. То, что вы пробовали, было очень разумным. Предполагая, что вы используете v1.7.1, теперь проще сделать столбцы list
. В этом случае он пытается сделать один столбец list
из .SD
(3 объекта) вместе с столбцом MONTH второй группы (4 элемента). Я подниму его как ошибку [EDIT: теперь исправлено в версии 1.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
присоединиться к нему. Данные вашего примера имеют перекрывающиеся диапазоны месяцев, так что это усложняет его.
Ответ 2
Глядя на это, я понял, что ответ был возможен только потому, что ID
был уникальным ключом (без дубликатов). Вот еще один ответ с дубликатами. Но, кстати, некоторые NA
, кажется, ползают. Может ли это быть ошибкой? Я использую v1.8.7 (commit 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()
.
Ответ 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