Правильный/быстрый способ изменения таблицы данных.
У меня есть таблица данных в R:
library(data.table)
set.seed(1234)
DT <- data.table(x=rep(c(1,2,3),each=4), y=c("A","B"), v=sample(1:100,12))
DT
x y v
[1,] 1 A 12
[2,] 1 B 62
[3,] 1 A 60
[4,] 1 B 61
[5,] 2 A 83
[6,] 2 B 97
[7,] 2 A 1
[8,] 2 B 22
[9,] 3 A 99
[10,] 3 B 47
[11,] 3 A 63
[12,] 3 B 49
Я легко могу суммировать переменную v по группам в data.table:
out <- DT[,list(SUM=sum(v)),by=list(x,y)]
out
x y SUM
[1,] 1 A 72
[2,] 1 B 123
[3,] 2 A 84
[4,] 2 B 119
[5,] 3 A 162
[6,] 3 B 96
Однако я хотел бы иметь группы (y) как столбцы, а не строки. Я могу выполнить это, используя reshape
:
out <- reshape(out,direction='wide',idvar='x', timevar='y')
out
x SUM.A SUM.B
[1,] 1 72 123
[2,] 2 84 119
[3,] 3 162 96
Есть ли более эффективный способ изменить данные после его агрегации? Есть ли способ объединить эти операции в один шаг, используя операции data.table?
Ответы
Ответ 1
В пакете data.table
реализованы более быстрые функции melt/dcast
(в C). Он также имеет дополнительные функции, позволяя расплавить и слить несколько столбцов. См. Новый Эффективная перестройка с использованием data.tables в Github.
функции melt/dcast для data.table доступны с версии v.1.9.0, а функции включают в себя:
-
Нет необходимости загружать пакет reshape2
до кастинга. Но если вы хотите, чтобы он был загружен для других операций, загрузите его перед загрузкой data.table
.
-
dcast
также является общим S3. Больше dcast.data.table()
. Просто используйте dcast()
.
-
melt
:
-
способен плавить столбцы типа "list".
-
получает variable.factor
и value.factor
, которые по умолчанию являются TRUE
и FALSE
соответственно для совместимости с reshape2
. Это позволяет напрямую управлять типом вывода столбцов variable
и value
(в качестве факторов или нет).
-
melt.data.table
na.rm = TRUE
параметр внутренне оптимизирован для удаления NA непосредственно во время плавления и, следовательно, намного более эффективен.
-
NEW: melt
может принять список для measure.vars
, а столбцы, указанные в каждом элементе списка, будут объединены вместе. Это облегчается путем использования patterns()
. См. Виньетку или ?melt
.
-
dcast
:
-
принимает несколько fun.aggregate
и несколько value.var
. См. Виньетку или ?dcast
.
-
используйте rowid()
функцию непосредственно в формуле для генерации идентификатора-столбца, который иногда требуется для однозначного определения строк. См.? Dcast.
-
Старые контрольные показатели:
-
melt
: 10 миллионов строк и 5 столбцов, 61,3 секунды сокращено до 1,2 секунды.
-
dcast
: 1 миллион строк и 4 столбца, 192 секунды сокращены до 3,6 секунды.
Напоминание слайда презентаций Cologne (декабрь 2013 г.) 32: Почему бы не отправить запрос на вывод dcast
на reshape2
?
Ответ 2
Эта функция теперь реализована в data.table(начиная с версии 1.8.11), как видно из ответа Zach выше.
Я только что увидел этот большой кусок кода от Arun здесь, на SO. Поэтому я предполагаю, что есть решение data.table
. Применительно к этой проблеме:
library(data.table)
set.seed(1234)
DT <- data.table(x=rep(c(1,2,3),each=1e6),
y=c("A","B"),
v=sample(1:100,12))
out <- DT[,list(SUM=sum(v)),by=list(x,y)]
# edit (mnel) to avoid setNames which creates a copy
# when calling `names<-` inside the function
out[, as.list(setattr(SUM, 'names', y)), by=list(x)]
})
x A B
1: 1 26499966 28166677
2: 2 26499978 28166673
3: 3 26500056 28166650
Это дает те же результаты, что и подход DWin:
tapply(DT$v,list(DT$x, DT$y), FUN=sum)
A B
1 26499966 28166677
2 26499978 28166673
3 26500056 28166650
Кроме того, это быстро:
system.time({
out <- DT[,list(SUM=sum(v)),by=list(x,y)]
out[, as.list(setattr(SUM, 'names', y)), by=list(x)]})
## user system elapsed
## 0.64 0.05 0.70
system.time(tapply(DT$v,list(DT$x, DT$y), FUN=sum))
## user system elapsed
## 7.23 0.16 7.39
UPDATE
Итак, это решение также работает для несбалансированных наборов данных (т.е. некоторые комбинации не существуют), вы должны сначала ввести те, что указаны в таблице данных:
library(data.table)
set.seed(1234)
DT <- data.table(x=c(rep(c(1,2,3),each=4),3,4), y=c("A","B"), v=sample(1:100,14))
out <- DT[,list(SUM=sum(v)),by=list(x,y)]
setkey(out, x, y)
intDT <- expand.grid(unique(out[,x]), unique(out[,y]))
setnames(intDT, c("x", "y"))
out <- out[intDT]
out[, as.list(setattr(SUM, 'names', y)), by=list(x)]
Резюме
Объединяя комментарии к вышесказанному, здесь однострочное решение:
DT[, sum(v), keyby = list(x,y)][CJ(unique(x), unique(y)), allow.cartesian = T][,
setNames(as.list(V1), paste(y)), by = x]
Также легко изменить это, чтобы иметь больше, чем просто сумму, например:
DT[, list(sum(v), mean(v)), keyby = list(x,y)][CJ(unique(x), unique(y)), allow.cartesian = T][,
setNames(as.list(c(V1, V2)), c(paste0(y,".sum"), paste0(y,".mean"))), by = x]
# x A.sum B.sum A.mean B.mean
#1: 1 72 123 36.00000 61.5
#2: 2 84 119 42.00000 59.5
#3: 3 187 96 62.33333 48.0
#4: 4 NA 81 NA 81.0
Ответ 3
Объекты Data.table наследуют от 'data.frame', поэтому вы можете просто использовать tapply:
> tapply(DT$v,list(DT$x, DT$y), FUN=sum)
AA BB
a 72 123
b 84 119
c 162 96
Ответ 4
Вы можете использовать библиотеку dcast
из reshape2
. Вот код
# DUMMY DATA
library(data.table)
mydf = data.table(
x = rep(1:3, each = 4),
y = rep(c('A', 'B'), times = 2),
v = rpois(12, 30)
)
# USE RESHAPE2
library(reshape2)
dcast(mydf, x ~ y, fun = sum, value_var = "v")
ПРИМЕЧАНИЕ. Решение tapply
будет намного быстрее.