R use ddply ou agregado

Eu tenho um quadro de dados com 3 colunas: custId, saleDate, DelivDateTime.

> head(events22) custId saleDate DelivDate 1 280356593 2012-11-14 14:04:59 11/14/12 17:29 2 280367076 2012-11-14 17:04:44 11/14/12 20:48 3 280380097 2012-11-14 17:38:34 11/14/12 20:45 4 280380095 2012-11-14 20:45:44 11/14/12 23:59 5 280380095 2012-11-14 20:31:39 11/14/12 23:49 6 280380095 2012-11-14 19:58:32 11/15/12 00:10 

Aqui está o dput:

 > dput(events22) structure(list(custId = c(280356593L, 280367076L, 280380097L, 280380095L, 280380095L, 280380095L, 280364279L, 280364279L, 280398506L, 280336395L, 280364376L, 280368458L, 280368458L, 280368456L, 280368456L, 280364225L, 280391721L, 280353458L, 280387607L, 280387607L), saleDate = structure(c(1352901899.215, 1352912684.484, 1352914714.971, 1352925944.429, 1352925099.247, 1352923112.636, 1352922476.55, 1352920666.968, 1352915226.534, 1352911135.077, 1352921349.592, 1352911494.975, 1352910529.86, 1352924755.295, 1352907511.476, 1352920108.577, 1352906160.883, 1352905925.134, 1352916810.309, 1352916025.673), class = c("POSIXct", "POSIXt"), tzone = "UTC"), DelivDate = c("11/14/12 17:29", "11/14/12 20:48", "11/14/12 20:45", "11/14/12 23:59", "11/14/12 23:49", "11/15/12 00:10", "11/14/12 23:35", "11/14/12 22:59", "11/14/12 20:53", "11/14/12 19:52", "11/14/12 23:01", "11/14/12 19:47", "11/14/12 19:42", "11/14/12 23:31", "11/14/12 23:33", "11/14/12 22:45", "11/14/12 18:11", "11/14/12 18:12", "11/14/12 19:17", "11/14/12 19:19")), .Names = c("custId", "saleDate", "DelivDate" ), row.names = c("1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20" ), class = "data.frame") 

Eu estou tentando encontrar o DelivDate para o saleDate mais recente para cada custId .

Eu posso fazer isso usando plyr :: ddply assim:

 dd1 <-ddply(events22, .(custId),.inform = T, function(x){ x[x$saleDate == max(x$saleDate),"DelivDate"] }) 

Minha pergunta é se existe uma maneira mais rápida de fazer isso, pois o método ddply é um pouco demorado (o dataset completo é ~ 400k linhas). Eu olhei usando aggregate() mas não sei como obter um valor diferente daquele que eu estou classificando por.

Alguma sugestão?

EDITAR:

Aqui estão os resultados de benchmark para 10k linhas @ 10 iterações:

  test replications elapsed relative user.self 2 AGG2() 10 5.96 1.000 5.93 1 AGG1() 10 20.87 3.502 20.75 5 DATATABLE() 10 61.32 1 60.31 3 DDPLY() 10 80.04 13.430 79.63 4 DOCALL() 10 90.43 15.173 88.39 

EDIT2: Apesar de ser mais rápido, o AGG2 () não fornece a resposta correta.

  > head(agg2) custId saleDate DelivDate 1 280336395 2012-11-14 16:38:55 11/14/12 19:52 2 280353458 2012-11-14 15:12:05 11/14/12 18:12 3 280356593 2012-11-14 14:04:59 11/14/12 17:29 4 280364225 2012-11-14 19:08:28 11/14/12 22:45 5 280364279 2012-11-14 19:47:56 11/14/12 23:35 6 280364376 2012-11-14 19:29:09 11/14/12 23:01 > agg2  head(agg2) custId DelivDate 1 280336395 11/14/12 17:29 2 280353458 11/14/12 17:29 3 280356593 11/14/12 17:29 4 280364225 11/14/12 17:29 5 280364279 11/14/12 17:29 6 280364376 11/14/12 17:29 > agg2  head(agg2) custId V1 1 280336395 11/14/12 19:52 2 280353458 11/14/12 18:12 3 280356593 11/14/12 17:29 4 280364225 11/14/12 22:45 5 280364279 11/14/12 23:35 6 280364376 11/14/12 23:01 

Eu também recomendaria data.table aqui, mas desde que você solicitou uma solução aggregate , aqui está uma que combina aggregate e merge para obter todas as colunas:

 merge(events22, aggregate(saleDate ~ custId, events22, max)) 

Ou aggregate apenas se você deseja apenas as colunas “custId” e “DelivDate”:

 aggregate(list(DelivDate = events22$saleDate), list(custId = events22$custId), function(x) events22[["DelivDate"]][which.max(x)]) 

Finalmente, aqui está uma opção usando o sqldf :

 library(sqldf) sqldf("select custId, DelivDate, max(saleDate) `saleDate` from events22 group by custId") 

Referências

Eu não sou um especialista em benchmarking ou data.table , mas me surpreendeu que data.table não seja mais rápido aqui. Minha suspeita é de que os resultados seriam bem diferentes em um dataset maior , digamos, por exemplo, sua linha de 400k. De qualquer forma, aqui está um código de benchmarking modelado após a resposta do @mnel aqui, para que você possa fazer alguns testes no seu dataset real para referência futura.

 library(rbenchmark) 

Primeiro, configure suas funções para o que você deseja avaliar.

 DDPLY <- function() { x <- ddply(events22, .(custId), .inform = T, function(x) { x[x$saleDate == max(x$saleDate),"DelivDate"]}) } DATATABLE <- function() { x <- dt[, .SD[which.max(saleDate), ], by = custId] } AGG1 <- function() { x <- merge(events22, aggregate(saleDate ~ custId, events22, max)) } AGG2 <- function() { x <- aggregate(list(DelivDate = events22$saleDate), list(custId = events22$custId), function(x) events22[["DelivDate"]][which.max(x)]) } SQLDF <- function() { x <- sqldf("select custId, DelivDate, max(saleDate) `saleDate` from events22 group by custId") } DOCALL <- function() { do.call(rbind, lapply(split(events22, events22$custId), function(x){ x[which.max(x$saleDate), ] }) ) } 

Em segundo lugar, faça o benchmarking.

 benchmark(DDPLY(), DATATABLE(), AGG1(), AGG2(), SQLDF(), DOCALL(), order = "elapsed")[1:5] # test replications elapsed relative user.self # 4 AGG2() 100 0.285 1.000 0.284 # 3 AGG1() 100 0.891 3.126 0.896 # 6 DOCALL() 100 1.202 4.218 1.204 # 2 DATATABLE() 100 1.251 4.389 1.248 # 1 DDPLY() 100 1.254 4.400 1.252 # 5 SQLDF() 100 2.109 7.400 2.108 

O mais rápido entre ddply e aggregate , suponho que seria aggregate , especialmente em dados enormes como você tem. No entanto, o mais rápido seria data.table .

 require(data.table) dt <- data.table(events22) dt[, .SD[which.max(saleDate),], by=custId] 

De ?data.table : .SD é um data.table contendo o subdataset de x para cada grupo, excluindo a (s) coluna (s) de grupo.

Isso deve ser muito rápido, mas o data.table provavelmente é mais rápido:

 do.call(rbind, lapply(split(events22, events22$custId), function(x){ x[which.max(x$saleDate), ] }) ) 

Aqui está uma function data.table muito mais data.table :

 DATATABLE <- function() { dt <- data.table(events, key=c('custId', 'saleDate')) dt[, maxrow := 1:.N==.N, by = custId] return(dt[maxrow==TRUE, list(custId, DelivDate)]) } 

Observe que essa function cria um data.table e classifica os dados, o que é uma etapa que você só precisa executar uma vez. Se você remover essa etapa (talvez você tenha um pipeline de processamento de dados em várias etapas e crie os dados. data.table vez, como primeiro passo), a function será mais de duas vezes mais rápida.

Eu também modifiquei todas as funções anteriores para retornar o resultado, para facilitar a comparação:

 DDPLY <- function() { return(ddply(events, .(custId), .inform = T, function(x) { x[x$saleDate == max(x$saleDate),"DelivDate"]})) } AGG1 <- function() { return(merge(events, aggregate(saleDate ~ custId, events, max)))} SQLDF <- function() { return(sqldf("select custId, DelivDate, max(saleDate) `saleDate` from events group by custId"))} DOCALL <- function() { return(do.call(rbind, lapply(split(events, events$custId), function(x){ x[which.max(x$saleDate), ] }) )) } 

Aqui estão os resultados para 10k linhas, repetidas 10 vezes:

 library(rbenchmark) library(plyr) library(data.table) library(sqldf) events <- do.call(rbind, lapply(1:500, function(x) events22)) events$custId <- sample(1:nrow(events), nrow(events)) benchmark(a <- DDPLY(), b <- DATATABLE(), c <- AGG1(), d <- SQLDF(), e <- DOCALL(), order = "elapsed", replications=10)[1:5] test replications elapsed relative user.self 2 b <- DATATABLE() 10 0.13 1.000 0.13 4 d <- SQLDF() 10 0.42 3.231 0.41 3 c <- AGG1() 10 12.11 93.154 12.03 1 a <- DDPLY() 10 32.17 247.462 32.01 5 e <- DOCALL() 10 56.05 431.154 55.85 

Como todas as funções retornam seus resultados, podemos verificar que todas elas retornam a mesma resposta:

 c <- c[order(c$custId),] dim(a); dim(b); dim(c); dim(d); dim(e) all(a$V1==b$DelivDate) all(a$V1==c$DelivDate) all(a$V1==d$DelivDate) all(a$V1==e$DelivDate) 

/ Edit: No dataset menor de 20 linhas, o data.table ainda é o mais rápido, mas por uma margem mais fina:

  test replications elapsed relative user.self 2 b <- DATATABLE() 100 0.22 1.000 0.22 3 c <- AGG1() 100 0.42 1.909 0.42 5 e <- DOCALL() 100 0.48 2.182 0.49 1 a <- DDPLY() 100 0.55 2.500 0.55 4 d <- SQLDF() 100 1.00 4.545 0.98 

/ Edit2: Se removermos a criação do data.table da function, obtemos os seguintes resultados:

 dt <- data.table(events, key=c('custId', 'saleDate')) DATATABLE2 <- function() { dt[, maxrow := 1:.N==.N, by = custId] return(dt[maxrow==TRUE, list(custId, DelivDate)]) } benchmark(a <- DDPLY(), b <- DATATABLE2(), c <- AGG1(), d <- SQLDF(), e <- DOCALL(), order = "elapsed", replications=10)[1:5] test replications elapsed relative user.self 2 b <- DATATABLE() 10 0.09 1.000 0.08 4 d <- SQLDF() 10 0.41 4.556 0.39 3 c <- AGG1() 10 11.73 130.333 11.67 1 a <- DDPLY() 10 31.59 351.000 31.50 5 e <- DOCALL() 10 55.05 611.667 54.91