Frage Mutieren von Spalten eines Datenrahmens basierend auf einer Prädikatfunktion (dplyr :: mutate_if)


Ich möchte dplyr's benutzen mutate_if() Funktion, um Listenspalten in Datenrahmenspalten zu konvertieren, aber wenn ich versuche, einen rätselhaften Fehler zu bekommen. Ich benutze dplyr 0.5.0, purrr 0.2.2, R 3.3.0.

Das grundlegende Setup sieht so aus: Ich habe einen Datenrahmen dEinige Spalten sind 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 der Listen konvertieren (in diesem Fall d$A) zu einer Spalte von Datenrahmen unter Verwendung der folgenden Funktion:

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

Das heißt, ich hätte gerne die Listenspalte d$A durch die Liste ersetzt werden lapply(d$A, tblfy), welches 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 stolpere: Wenn ich versuche, die List-Spalten in Datenrahmen-Spalten mit der folgenden Anwendung zu konvertieren

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

Ich erhalte eine Fehlermeldung, die ich nicht interpretieren kann:

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

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

Anmerkung

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

tblfy_vec <- Vectorize(tblfy)

in mutate_if() scheitert mit dem Fehler

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

Aktualisieren

Nachdem ich etwas Erfahrung mit purrr gesammelt habe, finde ich jetzt den folgenden Ansatz natürlich, wenn auch etwas langatmig:

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

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


5
2017-07-07 18:08


Ursprung


Antworten:


Das Original tblfy Funktion Fehler für mich aus (auch wenn seine Elemente direkt verkettet sind), so dass wir es ein wenig neu erstellen, Hinzufügen von Vektorisierung als auch, die uns einen ansonsten notwendigen Prior vermeiden können rowwise() Anruf:

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

Jetzt können wir verwenden mutate_if schön:

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 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 Notizen:

  • map_df(identity) scheint effizienter zu sein, als jede alternative Formulierung. Ich kenne das identity Anruf scheint unnötig, aber fast alles andere bricht.
  • Ich bin mir nicht sicher, wie nützlich es ist tblfy Dies hängt von der Struktur der Listen in der Listenspalte ab, die sehr unterschiedlich sein können. Wenn Sie viel mit einer ähnlichen Struktur haben, ist es wahrscheinlich nützlich.
  • Es kann einen Weg geben, dies zu tun pmap Anstatt von Vectorize, aber ich kann es mit ein paar oberflächlichen Versuchen nicht zum Laufen bringen.

6
2017-07-07 20:03



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

7
2017-07-07 20:00