Правильный / быстрый способ изменить таблицу данных - PullRequest
66 голосов
/ 01 августа 2011

У меня есть таблица данных в 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?

Ответы [ 4 ]

73 голосов
/ 02 августа 2011

Пакет data.table реализует более быстрые функции melt/dcast (в C).Он также имеет дополнительные функции, позволяя плавить и разливать несколько столбцов .Пожалуйста, ознакомьтесь с новым Эффективным изменением формы с использованием data.tables на Github.

функции melt / dcast для data.table были доступны начиная с v1.9.0 и включают следующие функции:

  • Нет необходимости загружать пакет reshape2 перед разливкой.Но если вы хотите, чтобы он загружался для других операций, пожалуйста, загрузите его до загрузки data.table.

  • dcast также является универсальным S3.Не более dcast.data.table().Просто используйте dcast().

  • melt:

    • способен плавиться на столбцах типа «список».

    • усиления variable.factor и value.factor, которые по умолчанию равны TRUE и FALSE соответственно для совместимости с reshape2.Это позволяет напрямую управлять типом вывода столбцов variable и value (как факторы или нет).

    • melt.data.table '* * * * * * * * * * * * * na.rm = TRUE оптимизирован для внутренней очистки непосредственно во время плавления и поэтому намного эффективнее.

    • NEW: melt может принять список для measure.vars, и столбцы, указанные в каждом элементе списка, будут объединены.Этому способствует дальнейшее использование patterns().См. Виньетка или ?melt.

  • dcast:

    • принимает несколько fun.aggregate и несколько value.var.См. Виньетка или ?dcast.

    • использование функции rowid() непосредственно в формуле для создания столбца id, который иногда требуется для уникальной идентификации строк.Смотрите? Dcast.

  • Старые тесты:

    • melt: 10 миллионов строк и 5 столбцов, 61,3 секунды уменьшено до 1,2секунд.
    • dcast: 1 миллион строк и 4 столбца, 192 секунды уменьшены до 3,6 секунд.

Слайд-презентация «Напоминание о Кельне» (декабрь 2013 г.) 32: Почему бы не отправить запрос dcast на reshape2?

32 голосов
/ 20 марта 2013

Эта функция теперь реализована в data.table (начиная с версии 1.8.11), как видно из ответа Заха выше.

Я только что видел этот большой кусок кода из 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 

ОБНОВЛЕНИЕ

Чтобы это решение также работало с несбалансированными наборами данных (т. Е. Некоторые комбинации не существуют), сначала необходимо ввести их в таблицу данных:

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)]

Резюме

Комбинируя комментарии с вышеприведенным, вот решение в 1 строку:

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
21 голосов
/ 01 августа 2011

Объекты 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
7 голосов
/ 01 августа 2011

Вы можете использовать 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 будет намного быстрее.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...