2016-07-07 17 views
5

Ich würde gerne mutate_if() Funktion dflyr verwenden, um List-Spalten in Daten-Frame-Spalten zu konvertieren, aber rätselhafte Fehler, wenn ich es versuche tun Sie dies. Ich benutze dplyr 0.5.0, purrr 0.2.2, R 3.3.0.Muting Spalten eines Datenrahmens auf der Grundlage einer Prädikat-Funktion (dplyr :: mutate_if)

Die Grundeinstellung sieht wie folgt aus: Ich habe einen Datenrahmen d, einige von deren Spalten Listen:

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

Ich möchte die Spalte von Listen konvertieren (in diesem Fall d$A) auf ein Spalte von Datenrahmen die folgende Funktion:

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

das heißt, würde ich die Liste-Spalte wie d$A von der Liste lapply(d$A, tblfy), ersetzt werden, die

ist
[[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 

Natürlich könnte ich in diesem einfachen Fall einfach eine einfache Neuzuordnung vornehmen. Der Punkt ist jedoch, dass ich dies programmatisch, idealerweise mit dplyr, in einer allgemein anwendbaren Weise tun möchte, die mit einer beliebigen Anzahl von Listenspalten umgehen könnte.

Hier ist, wo ich stolpern: Wenn ich versuche, die Listen-Spalten-Daten-Frame-Spalten zu konvertieren mit dem folgenden Anwendungs ​​

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

ich eine Fehlermeldung bekommen, dass ich weiß nicht, wie zu interpretieren:

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

Warum mutate_if() scheitern? Wie kann ich es richtig anwenden, um das gewünschte Ergebnis zu erhalten?

Bemerkung

Ein Kommentator hat darauf hingewiesen, dass die Funktion tblfy() vektorisiert werden soll. Das ist ein vernünftiger Vorschlag. Aber - wenn ich nicht falsch vektorisiert habe - scheint das nicht an der Wurzel des Problems zu liegen. Anstecken einer vektorisierte Version von tblfy(),

tblfy_vec <- Vectorize(tblfy) 

in mutate_if() schlägt mit dem Fehler

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

aktualisiert

Nach etwas Erfahrung mit purrr gewinnen, finde ich jetzt die folgende Vorgehensweise natürlich, wenn etwas langatmig:

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

Dies ist mehr oder weniger identisch mit @ alistaires Lösung, aber verwendet map_if() resp. map(), anstelle von mutate_if() resp. Vectorize().

+2

Also was genau ist die erwartete Ausgabe? Sie möchten A von einer Liste von Listen in eine Liste von tibbles ändern? – MrFlick

+1

Ihre Funktion ist nicht vektorisiert, sie akzeptiert nur eine Liste. Sieh dir 'tblfy (d $ A)' an. Es gibt einen Fehler, weil es zwei Listen in 'd $ A' gibt. Sie vergleichen Äpfel nicht mit Äpfeln. In Ihrem 'lapply (d $ A, tblfy)' geben Sie Ihrer Funktion eine Liste zu einer Zeit, deshalb funktioniert es. 'tblfy (d $ A [[1]])' und 'tblfy (d $ A [[2]])'. In Ihrer dplyr-Funktion liefern Sie zwei Listen. Ändern Sie 'tblfy', um mehr als eine Liste zu akzeptieren, oder ändern Sie den dplyr-Anruf. Oder, wie Herr Flick fragt, denken Sie breiter darüber nach, was Sie bauen. –

+0

@MrFlick Ich habe die Frage bearbeitet, um die gewünschte Ausgabe explizit zu machen. Ist es jetzt klar? – egnha

Antwort

5

Die Original für mich tblfy Funktion Fehler aus (auch wenn seine Elemente werden direkt verkettet), so machen wir es ein wenig umbauen, Vektorisierung Zugabe als auch, was lässt uns eine ansonsten notwendige vor rowwise() Aufruf vermeiden:

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

Jetzt können wir mutate_if gut verwenden:

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 

... und wenn wir UNNEST, um zu sehen, was da ist,

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 

Ein paar Anmerkungen:

  • map_df(identity) scheint effizienter zu sein bei einer tibble als jede der alternativen Formulierungen zu bauen. Ich weiß, dass der Anruf identity unnötig scheint, aber fast alles andere bricht.
  • Ich bin nicht sicher, wie weit nützlich tblfy wird, da es etwas abhängig von der Struktur der Listen in der Liste Spalte ist, die enorm variieren kann. Wenn Sie viel mit einer ähnlichen Struktur haben, ist es wahrscheinlich nützlich.
  • Es kann einen Weg geben, dies mit pmap anstelle von Vectorize zu tun, aber ich kann es nicht mit einigen oberflächlichen Versuchen arbeiten.
+1

Danke, das macht es! Ihre Version von 'tblfy()' mit 'map_df()' ist knapper als meine. Hätte nicht so gedacht. In der Tat erklärt der Quellcode von 'map_df()', warum Ihre Lösung funktioniert, und vor allem, warum 'list()' notwendig ist (was mich anfangs verwirrt hatte): weil 'map_df' eigentlich' map' ist, gefolgt von 'bind_rows', wobei' list() 'weggelassen wird, würde zu einem Datenrahmen der Dimension 4 x 2 führen. – egnha

6

In-Place-Konvertierung ohne Kopieren:

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

Beantwortet nicht die Originalfrage zu' mutate_if', sondern eine sehr schöne Alternativlösung zum Grundproblem. data.table ist neu für mich. Vielen Dank! – egnha