2016-07-07 14 views
5

Vorrei utilizzare la funzione mutate_if() di dplyr per convertire le colonne di elenco in colonne di frame di dati, ma si verifica un errore enigmatico quando provo a fare così. Sto usando dplyr 0.5.0, purrr 0.2.2, R 3.3.0.Muting di colonne di un frame di dati basato su una funzione di predicato (dplyr :: mutate_if)

La configurazione di base assomiglia a questo: ho un frame di dati d, alcuni dei cui colonne sono liste:

d <- dplyr::data_frame(
    A = list(
    list(list(x = "a", y = 1), list(x = "b", y = 2)), 
    list(list(x = "c", y = 3), list(x = "d", y = 4)) 
), 
    B = LETTERS[1:2] 
) 

Vorrei convertire la colonna di liste (in questo caso, d$A) per un colonna del frame di dati utilizzando la seguente funzione:

tblfy <- function(x) { 
    x %>% 
    purrr::transpose() %>% 
    purrr::simplify_all() %>% 
    dplyr::as_data_frame() 
} 

Cioè, vorrei la lista colonne d$A per essere sostituito da lista lapply(d$A, tblfy), che è

[[1]] 
# A tibble: 2 x 2 
     x  y 
    <chr> <dbl> 
1  a  1 
2  b  2 

[[2]] 
# A tibble: 2 x 2 
     x  y 
    <chr> <dbl> 
1  c  3 
2  d  4 

Naturalmente, in questo semplice caso, potrei semplicemente effettuare una semplice riassegnazione. Il punto, tuttavia, è che mi piacerebbe farlo a livello di programmazione, idealmente con dplyr, in un modo generalmente applicabile che potrebbe trattare qualsiasi numero di colonne di elenchi.

Ecco dove inciampo: Quando provo a convertire l'elenco colonne di data-frame-colonne utilizzando la seguente applicazione

d %>% dplyr::mutate_if(is.list, funs(tblfy)) 

ricevo un messaggio di errore che non so come interpretare:

Error: Each variable must be named. 
Problem variables: 1, 2 

Perché mutate_if() falliscono? Come posso applicarlo correttamente per ottenere il risultato desiderato?

Osservazione

Un commentatore ha osservato che la funzione tblfy() dovrebbe essere vettorializzato. Questo è un suggerimento ragionevole. Ma - a meno che non abbia vettorializzato in modo errato - non sembra che si arrivi alla radice del problema. Inserendo un vettorizzati versione del tblfy(),

tblfy_vec <- Vectorize(tblfy) 

in mutate_if() fallisce con l'errore

Error: wrong result size (4), expected 2 or 1 

Aggiornamento

Dopo aver acquisito una certa esperienza con purrr, ora trovo il seguente approccio naturale, se un po 'prolisso:

d %>% 
    map_if(is.list, ~ map(., ~ map_df(., identity))) %>% 
    as_data_frame() 

Questo è più o meno identico alla soluzione di @ alistaire, di seguito, ma utilizza map_if(), risp. map(), al posto di mutate_if(), risp. Vectorize().

+2

Quindi, qual è esattamente l'output previsto? Vuoi cambiare A da un elenco di elenchi a un elenco di tibbles? – MrFlick

+1

La funzione non è vettorizzata, accetta solo una lista. Guarda 'tblfy (d $ A)'. C'è un errore perché ci sono due liste in 'd $ A'. Non stai confrontando le mele con le mele. Nel tuo 'lapply (d $ A, tblfy)' stai dando alla tua funzione una lista alla volta, ecco perché funziona. 'tblfy (d $ A [[1]])' e 'tblfy (d $ A [[2]])'. Nella tua funzione dplyr stai fornendo due liste. Cambia 'tblfy' per accettare più di una lista, o cambia la chiamata dplyr. O come chiede MrFlick, pensa più in generale a quello che stai costruendo. –

+0

@MrFlick Ho modificato la domanda per rendere esplicito l'output desiderato. È chiaro ora? – egnha

risposta

5

L'originale tblfy errori di funzione fuori per me (anche se i suoi elementi sono incatenati direttamente), quindi cerchiamo di ricostruire un po ', aggiungendo vettorializzazione pure, che ci permette di evitare un altrimenti-necessario prima rowwise() chiamata:

tblfy <- Vectorize(function(x){x %>% purrr::map_df(identity) %>% list()}) 

Ora possiamo usare mutate_if bene:

d %>% mutate_if(purrr::is_list, tblfy) 
## Source: local data frame [2 x 2] 
## 
##    A  B 
##   <list> <chr> 
## 1 <tbl_df [2,2]>  A 
## 2 <tbl_df [2,2]>  B 

... e se UNNEST per vedere cosa c'è,

d %>% mutate_if(purrr::is_list, tblfy) %>% tidyr::unnest() 
## Source: local data frame [4 x 3] 
## 
##  B  x  y 
## <chr> <chr> <dbl> 
## 1  A  a  1 
## 2  A  b  2 
## 3  B  c  3 
## 4  B  d  4 

Un paio di note:

  • map_df(identity) sembra essere più efficiente a costruire un Tibble rispetto a qualsiasi delle formulazioni alternative. So che la chiamata identity non sembra necessaria, ma la maggior parte di tutto il resto si interrompe.
  • Non sono sicuro di quanto sia utile tblfy, in quanto dipende in qualche modo dalla struttura degli elenchi nella colonna elenco, che può variare enormemente. Se hai molto con una struttura simile, suppongo sia comunque utile.
  • Potrebbe esserci un modo per farlo con pmap invece di Vectorize, ma non riesco a farlo funzionare con alcuni tentativi superficiali.
+1

Grazie, lo fa! La tua versione di 'tblfy()' usando 'map_df()' è più concisa della mia. Non avevo pensato di farlo in quel modo. Infatti, guardando il codice sorgente per 'map_df()' spiega perché la tua soluzione funziona, e in particolare, perché 'list()' è necessario (che inizialmente mi aveva perplesso): perché 'map_df' è in realtà' map' seguito da 'bind_rows', tralasciando' lista() 'risulterebbe in un frame di dati di dimensione 4 x 2. – egnha

6

in luogo conversione senza alcuna copia:

library(data.table) 

for (col in d) if (is.list(col)) lapply(col, setDF) 

d 
#Source: local data frame [2 x 2] 
# 
#    A B 
#1 <S3:data.frame> A 
#2 <S3:data.frame> B 
+0

Non risponde alla domanda originale su' mutate_if', ma una soluzione alternativa molto interessante al problema di base. data.table è nuova per me. Grazie! – egnha

Problemi correlati