2015-05-25 12 views
6

Ecco il mio problema:dplyr sintesi: creare variabili dal nome vettore

Sto usando una funzione che restituisce un vettore di nome. Ecco un esempio di giocattoli:

toy_fn <- function(x) { 
    y <- c(mean(x), sum(x), median(x), sd(x)) 
    names(y) <- c("Right", "Wrong", "Unanswered", "Invalid") 
    y 
} 

Sto usando group_by in dplyr di applicare questa funzione per ogni gruppo (tipico split-apply-unire). Quindi, ecco il mio giocattolo data.frame:

set.seed(1234567) 
toy_df <- data.frame(id = 1:1000, 
        group = sample(letters, 1000, replace = TRUE), 
        value = runif(1000)) 

Ed ecco il risultato sto puntando:

toy_summary <- 
    toy_df %>% 
    group_by(group) %>% 
    summarize(Right = toy_fn(value)["Right"], 
       Wrong = toy_fn(value)["Wrong"], 
       Unanswered = toy_fn(value)["Unanswered"], 
       Invalid = toy_fn(value)["Invalid"]) 

> toy_summary 
Source: local data frame [26 x 5] 

    group  Right Wrong Unanswered Invalid 
1  a 0.5038394 20.15358 0.5905526 0.2846468 
2  b 0.5048040 15.64892 0.5163702 0.2994544 
3  c 0.5029442 21.62660 0.5072733 0.2465612 
4  d 0.5124601 14.86134 0.5382463 0.2681955 
5  e 0.4649483 17.66804 0.4426197 0.3075080 
6  f 0.5622644 12.36982 0.6330269 0.2850609 
7  g 0.4675324 14.96104 0.4692404 0.2746589 

Funziona! Ma non è bello chiamare quattro volte la stessa funzione. Mi piacerebbe piuttosto che dplyr ottenga il vettore con nome e crei una nuova variabile per ogni elemento nel vettore. Qualcosa di simile a questo:

toy_summary <- 
    toy_df %>% 
    group_by(group) %>% 
    summarize(toy_fn(value)) 

Questo, purtroppo, non funziona perché "Errore: in attesa di un singolo valore".

Ho pensato, ok, convertiamo il vettore in un data.frame utilizzando data.frame(as.list(x)). Ma neanche questo funziona. Ho provato molte cose ma non ho potuto ingannare Dplyr per pensare che stia effettivamente ricevendo un singolo valore (osservazione) per 4 diverse variabili. C'è un modo per aiutare Dplyr a rendersene conto?

risposta

2

Si può anche provare questo con do():

toy_df %>% 
    group_by(group) %>% 
    do(res = toy_fn(.$value)) 
+1

non sembra funzionare, però. –

+0

L'ho provato sul mio computer - funziona, il frame dei dati risultante richiede un po 'di analisi. –

+0

che tipo di parsing ?, ..., non ho potuto guardarlo attentamente perche 'l'ho controllato nel mio telefono. –

3

Questa non è una soluzione dplyr, ma se vi piace tubi:

library(magrittr) 

toy_summary <- 
    toy_df %>% 
    split(.$group) %>% 
    lapply(function(x) toy_fn(x$value)) %>% 
    do.call(rbind, .) 

# > head(toy_summary) 
#   Right Wrong Unanswered Invalid 
# a 0.5038394 20.15358 0.5905526 0.2846468 
# b 0.5048040 15.64892 0.5163702 0.2994544 
# c 0.5029442 21.62660 0.5072733 0.2465612 
# d 0.5124601 14.86134 0.5382463 0.2681955 
# e 0.4649483 17.66804 0.4426197 0.3075080 
# f 0.5622644 12.36982 0.6330269 0.2850609  
+0

Molte grazie. Mi piace molto. Stavo cercando una soluzione basata su dplyr perché devo chiamare questa funzione dopo un gruppo di 'full_join', filtraggio e altre discussioni sui dati fatte usando dplyr. Quindi sembra naturale usare dplyr pure. Ma questo è bello e funziona perfettamente. –

+0

@HernandoCasas È possibile combinare le funzioni dplyr prima o dopo questa catena (poiché l'input e l'output è un data.frame). Ma non puoi usarlo tra la sequenza. – bergant

5

Una possibile soluzione è quella di utilizzare dplyrSE capacità. Ad esempio, impostare si funzione come segue

dots <- setNames(list( ~ mean(value), 
         ~ sum(value), 
         ~ median(value), 
         ~ sd(value)), 
       c("Right", "Wrong", "Unanswered", "Invalid")) 

Quindi, è possibile utilizzare summarize_ (con un _) come segue

toy_df %>% 
    group_by(group) %>% 
    summarize_(.dots = dots) 
# Source: local data table [26 x 5] 
# 
# group  Right Wrong Unanswered Invalid 
# 1  o 0.4490776 17.51403 0.4012057 0.2749956 
# 2  s 0.5079569 15.23871 0.4663852 0.2555774 
# 3  x 0.4620649 14.78608 0.4475117 0.2894502 
# 4  a 0.5038394 20.15358 0.5905526 0.2846468 
# 5  t 0.5041168 24.19761 0.5330790 0.3171022 
# 6  m 0.4806628 21.14917 0.4805273 0.2825026 
# 7  c 0.5029442 21.62660 0.5072733 0.2465612 
# 8  w 0.4932484 17.75694 0.4891746 0.3309680 
# 9  q 0.5350707 22.47297 0.5608505 0.2749941 
# 10  g 0.4675324 14.96104 0.4692404 0.2746589 
# .. ...  ...  ...  ...  ... 

Anche se sembra bello, c'è una grande fermo qui. È necessario conoscere la colonna su cui si opererà a priori (value) quando si imposta la funzione, quindi non funzionerà su alcun altro nome di colonna, se non si imposta correttamente dots.


Come bonus ecco una soluzione semplice utilizzando data.table di utilizzare la funzione originale

library(data.table) 
setDT(toy_df)[, as.list(toy_fn(value)), by = group] 
#  group  Right Wrong Unanswered Invalid 
# 1:  o 0.4490776 17.51403 0.4012057 0.2749956 
# 2:  s 0.5079569 15.23871 0.4663852 0.2555774 
# 3:  x 0.4620649 14.78608 0.4475117 0.2894502 
# 4:  a 0.5038394 20.15358 0.5905526 0.2846468 
# 5:  t 0.5041168 24.19761 0.5330790 0.3171022 
# 6:  m 0.4806628 21.14917 0.4805273 0.2825026 
# 7:  c 0.5029442 21.62660 0.5072733 0.2465612 
# 8:  w 0.4932484 17.75694 0.4891746 0.3309680 
# 9:  q 0.5350707 22.47297 0.5608505 0.2749941 
# 10:  g 0.4675324 14.96104 0.4692404 0.2746589 
#... 
+0

Nice using data.table. La soluzione basata su dplyr che hai proposto non funziona per me perché non posso modificare la funzione. Mi piace molto il modo data.table, anche se stavo cercando una soluzione basata su dplyr perché dovevo chiamare questa funzione dopo un mucchio di full_join, filtraggio e altri conflitti di dati fatti usando dplyr. Quindi sembra naturale usare dplyr pure. –

+0

Cosa intendi con "Non riesco a modificare la funzione"? –

+0

Intendevo dire che non posso impostare la funzione come suggerisci, perché crei un oggetto con una formula per ciascuno dei valori di ritorno della mia funzione di esempio (toy_fn). Quello, comunque, era solo un esempio e la mia applicazione reale non comportare il calcolo della media, somma, mediana e sd. È invece una funzione che confronta i dati con i valori di riferimento in un altro database (utilizza RODBC per connettersi all'altro database e ottiene valori di riferimento aggiornati) e restituisce quattro valori (in un vettore denominato) che indicano il risultato del confronto. Non posso chiama una singola funzione per ottenere ognuno di questi valori –

3

A quanto pare c'è un problema quando si utilizza median (non so cosa sta succedendo lì), ma a parte che normalmente è possibile utilizzare un approccio come il seguente con summarise_each per applicare più funzioni.Si noti che è possibile specificare i nomi delle colonne risultanti utilizzando un vettore chiamato come input per funs_():

x <- c(Right = "mean", Wrong = "sd", Unanswered = "sum") 

toy_df %>% 
    group_by(group) %>% 
    summarise_each(funs_(x), value) 

#Source: local data frame [26 x 4] 
# 
# group  Right  Wrong Unanswered 
#1  a 0.5038394 0.2846468 20.15358 
#2  b 0.5048040 0.2994544 15.64892 
#3  c 0.5029442 0.2465612 21.62660 
#4  d 0.5124601 0.2681955 14.86134 
#5  e 0.4649483 0.3075080 17.66804 
#6  f 0.5622644 0.2850609 12.36982 
#7  g 0.4675324 0.2746589 14.96104 
#8  h 0.4921506 0.2879830 21.16248 
#9  i 0.5443600 0.2945428 22.31876 
#10  j 0.5276048 0.3236814 20.57659 
#.. ...  ...  ...  ... 
+2

Non penso che tu abbia bisogno di 'funs_' qui. Un "carattere vettoriale di nomi di funzioni" dovrebbe essere sufficiente. Vedi per es. l'esempio 'summarise_each (c (" min "," max "))'. Davvero strano con 'mediana'. – Henrik

+0

Buon punto, @ Henrik –

+0

Grazie. Per questo particolare esempio funziona molto bene. Ma nella mia vera applicazione non posso chiamare una funzione diversa per ciascuno dei valori che ho bisogno di calcolare. È colpa mia comunque. Non ero abbastanza chiaro che la funzione che ho inserito nel post fosse solo per avere un esempio riproducibile, ma la funzione che ho bisogno di chiamare su ciascun gruppo è molto più complessa e non solo le chiamate a significare, mediana, ecc. , è una funzione che non posso cambiare. –

1

utilizzando la sequenza di list(as_tibble(as.list(...)) seguito da un unnest da tidyr fa il trucco

toy_summary2 <- toy_df %>% group_by(group) %>% 
summarize(Col = list(as_tibble(as.list(toy_fn(value))))) %>% unnest() 
Problemi correlati