2013-12-08 8 views
30

Questa è molto simile a una domanda che applica una funzione comune a più colonne di data.table uning .SDcolsanswered thoroughly here.Applicare la funzione su un sottoinsieme di colonne (.SDcols) mentre si applica una funzione diversa su un'altra colonna (all'interno dei gruppi)

La differenza è che mi piacerebbe applicare contemporaneamente una funzione diversa su un'altra colonna che non fa parte del sottoinsieme .SD. I post un semplice esempio di seguito per mostrare il mio tentativo di risolvere il problema:

dt = data.table(grp = sample(letters[1:3],100, replace = TRUE), 
       v1 = rnorm(100), 
       v2 = rnorm(100), 
       v3 = rnorm(100)) 
sd.cols = c("v2", "v3") 
dt.out = dt[, list(v1 = sum(v1), lapply(.SD,mean)), by = grp, .SDcols = sd.cols] 

restituisce il seguente errore:

Error in `[.data.table`(dt, , list(v1 = sum(v1), lapply(.SD, mean)), by = grp, 
: object 'v1' not found 

Ora, questo ha un senso perché la colonna v1 non è incluso nel sottoinsieme di colonne che deve essere valutato per primo. Così ho esplorato ulteriormente includendo nel mio sottoinsieme delle colonne:

sd.cols = c("v1","v2", "v3") 
dt.out = dt[, list(sum(v1), lapply(.SD,mean)), by = grp, .SDcols = sd.cols] 

Ora, questo non causa un errore, ma fornisce una risposta contenente 9 righe (per 3 gruppi), con la somma ripetuta tre volte nella colonna V1 e i mezzi per tutti e 3 colonne (come previsto, ma non voluto) poste in V2 come illustrato di seguito:

> dt.out 
    grp  V1     V2 
1: c -1.070608 -0.0486639841313638 
2: c -1.070608 -0.178154270921521 
3: c -1.070608 -0.137625003604012 
4: b -2.782252 -0.0794929150464099 
5: b -2.782252 -0.149529237116445 
6: b -2.782252 0.199925178109264 
7: a 6.091355 0.141659419355985 
8: a 6.091355 -0.0272192037753071 
9: a 6.091355 0.00815760216214876 

Soluzione Soluzione utilizzando 2 passi

Chiaramente è possibile risolvere il problema in più passaggi calcolando la mean dal gruppo per il sottoinsieme di colonne e di unirsi al sum dal gruppo per la singola colonna come segue:

dt.out1 = dt[, sum(v1), by = grp] 
dt.out2 = dt[, lapply(.SD,mean), by = grp, .SDcols = sd.cols] 
dt.out = merge(dt.out1, dt.out2, by = "grp") 

> dt.out 
    grp  V1   v2   v3 
1: a 6.091355 -0.0272192 0.008157602 
2: b -2.782252 -0.1495292 0.199925178 
3: c -1.070608 -0.1781543 -0.137625004 

sono sicuro che è una cosa abbastanza semplice I Mi manca, grazie in anticipo per qualsiasi consiglio.

+0

il fatto che la prima espressione non funzioni è un bug imo, quindi per favore invia un bug report – eddi

risposta

23

Aggiornamento: Problema #495 è risolto ora con this recent commit, ora possiamo farlo bene:

require(data.table) # v1.9.7+ 
set.seed(1L) 
dt = data.table(grp = sample(letters[1:3],100, replace = TRUE), 
       v1 = rnorm(100), 
       v2 = rnorm(100), 
       v3 = rnorm(100)) 
sd.cols = c("v2", "v3") 
dt.out = dt[, list(v1 = sum(v1), lapply(.SD,mean)), by = grp, .SDcols = sd.cols] 

tuttavia notare che in questo caso, v2 sarebbe ritornato come un elenco. Questo perché stai facendo in modo efficace list(val, list()). Che cosa si intende fare, forse è:

dt[, c(list(v1=sum(v1)), lapply(.SD, mean)), by=grp, .SDcols = sd.cols] 
# grp  v1   v2   v3 
# 1: a -6.440273 0.16993940 0.2173324 
# 2: b 4.304350 -0.02553813 0.3381612 
# 3: c 0.377974 -0.03828672 -0.2489067 

vedere la storia per la risposta più anziani.

+0

Arun, non penso che il collo di bottiglia '.SD' si applichi in questo caso - il normale collo di bottiglia' .SD' ha a che fare con il sovraccarico di '[.data.table', che qui è assente. – eddi

+0

hai ragione, * è * più lento e non capisco veramente perché atm - penso che questo significhi che c'è un'altra calcolatrice overhead di grandi dimensioni da qualche altra parte (o messa in modo diverso - dubito che il collo di bottiglia stia chiamando eval da Cdogroups) – eddi

+4

È 'eval' di' lapply' molte volte che è lento, non '.SD'. Guarda la fonte di 'base :: lapply' a livello C. Lo fa costruendo una chiamata 'list (...)' e poi evalenziandola, comunque. Quando 'lapply' è in loop, quella stessa costruzione viene ripetuta all'infinito, inutilmente. Quindi l'ottimizzazione è quella di rendere quella costruzione una volta sola (e a livello R lo farà all'interno di '[.data.table') e poi passarla a' dogroups'. Ma solo una semplice chiamata a 'lapply' è ottimizzata al momento. Combinato con 'c()' non viene raccolto. cc @eddi –

6

Prova questo:

dt[,list(sum(v1), mean(v2), mean(v3)), by=grp] 

In data.table, utilizzando list() nel secondo argomento permette di descrivere un insieme di colonne che si traducono in finale data.table.

Per quello che vale, .SD può essere piuttosto lento [^ 1] quindi si consiglia di evitarlo a meno che non si abbia veramente bisogno di tutti i dati forniti nel sottosistema data.table come si potrebbe per una funzione più sofisticata.

Un'altra opzione, se si dispone di molte colonne per .SDcols, è di eseguire l'unione in una riga utilizzando la sintassi di unione data.table.

Ad esempio:

dt[, sum(v1), by=grp][dt[,lapply(.SD,mean), by=grp, .SDcols=sd.cols]] 

Per poter utilizzare il merge da data.table, è necessario utilizzare per primo setkey() sul vostro data.table in modo che sa come far combaciare le cose.

Quindi, in realtà, prima è necessario:

setkey(dt, grp) 

quindi è possibile utilizzare la linea di cui sopra per produrre un risultato equivalente.

[^ 1]: Trovo che ciò sia particolarmente vero in quanto il numero di gruppi si avvicina al numero di righe totali. Ad esempio, questo potrebbe accadere dove la tua chiave è un ID individuale e molte persone hanno solo una o due osservazioni.

+0

+1 per la sintassi di fusione e una soluzione praticabile –

+0

Usando 'wmean' prova un po 'di mal di testa qui come vorrei richiede la colonna di ponderazione specificata nella parte '.SDcols' anche se non voglio usarla! Dato che sto già usando 'sum' su quella colonna, è un problema anche calcolare un' weighted.mean' sulla colonna ... Suppongo che dovrei escludere quella colonna PRIMA di fare 'data.table 'Unisci. –

Problemi correlati