Konvertieren Sie eine Liste in einen Datenrahmen


513

Ich habe eine verschachtelte Liste von Daten. Seine Länge beträgt 132 und jedes Element ist eine Liste der Länge 20. Gibt es eine schnelle Möglichkeit, diese Struktur in einen Datenrahmen mit 132 Zeilen und 20 Datenspalten zu konvertieren?

Hier sind einige Beispieldaten, mit denen Sie arbeiten können:

l <- replicate(
  132,
  list(sample(letters, 20)),
  simplify = FALSE
)

Sie möchten also jedes Listenelement als Datenzeile in Ihrem data.frame?
Joshua Ulrich

2
@RichieCotton Es ist nicht das richtige Beispiel. "Jedes Element ist eine Liste der Länge 20" und Sie haben jedes Element ist eine Ein-Element-Liste des Vektors der Länge 20.
Marek

1
Spät zur Party, aber ich habe niemanden erwähnt nicht dies , was ich dachte , war sehr praktisch (für das, was ich zu tun , war auf der Suche).
mflo-ByeSE


Antworten:


390

Angenommen, Ihre Liste der Listen heißt l:

df <- data.frame(matrix(unlist(l), nrow=length(l), byrow=T))

Mit dem obigen Befehl werden alle Zeichenspalten in Faktoren konvertiert. Um dies zu vermeiden, können Sie dem Aufruf von data.frame () einen Parameter hinzufügen:

df <- data.frame(matrix(unlist(l), nrow=132, byrow=T),stringsAsFactors=FALSE)

109
Seien Sie hier vorsichtig, wenn Ihre Daten nicht alle vom gleichen Typ sind. Das Durchlaufen einer Matrix bedeutet, dass alle Daten zu einem gemeinsamen Typ zusammengeführt werden. Das heißt, wenn Sie eine Spalte mit Zeichendaten und eine Spalte mit numerischen Daten haben, werden die numerischen Daten durch Matrix () zum String und dann beide durch data.frame () zum Faktor gezwungen.
Ian Sudbery

Was ist der beste Weg, um dies zu tun, wenn die Liste fehlende Werte enthält, oder um NA in den Datenrahmen aufzunehmen?
Dave

1
@ Dave: Funktioniert für mich ... siehe hier r-fiddle.org/#/fiddle?id=y8DW7lqL&version=3
nico

4
Achten Sie auch darauf, ob Sie einen Zeichendatentyp haben - data.frame konvertiert ihn in Faktoren.
Alex Brown

4
@nico Gibt es eine Möglichkeit, die Namen der Listenelemente als Spaltennamen oder Rownamen im df beizubehalten?
N. Varela

472

Mit rbind

do.call(rbind.data.frame, your_list)

Bearbeiten: Vorherige Version Rückgabe data.framevon list's anstelle von Vektoren (wie @IanSudbery in Kommentaren hervorhob).


5
Warum funktioniert dies, rbind(your_list)gibt aber eine 1x32-Listenmatrix zurück?
Eykanal

26
@eykanal do.callElemente your_listals Argumente an übergeben rbind. Es ist gleichbedeutend mit rbind(your_list[[1]], your_list[[2]], your_list[[3]], ....., your_list[[length of your_list]]).
Marek

2
Diese Methode leidet unter der Nullsituation.
Frank Wang

3
@FrankWANG Diese Methode ist jedoch nicht für die Nullsituation ausgelegt. Es ist erforderlich, dass your_listgleich große Vektoren enthalten sind. NULLhat die Länge 0, sollte also fehlschlagen.
Marek

12
Diese Methode scheint das richtige Objekt zurückzugeben, aber wenn Sie das Objekt untersuchen, werden Sie feststellen, dass es sich bei den Spalten eher um Listen als um Vektoren handelt, was später zu Problemen führen kann, wenn Sie es nicht erwarten.
Ian Sudbery

135

Sie können das plyrPaket verwenden. Zum Beispiel eine verschachtelte Liste des Formulars

l <- list(a = list(var.1 = 1, var.2 = 2, var.3 = 3)
      , b = list(var.1 = 4, var.2 = 5, var.3 = 6)
      , c = list(var.1 = 7, var.2 = 8, var.3 = 9)
      , d = list(var.1 = 10, var.2 = 11, var.3 = 12)
      )

hat jetzt eine Länge von 4 und jede Liste in lenthält eine andere Liste der Länge 3. Jetzt können Sie ausführen

  library (plyr)
  df <- ldply (l, data.frame)

und sollte das gleiche Ergebnis wie in der Antwort @Marek und @nico erhalten.


8
Gute Antwort. Ich könnte Ihnen ein wenig erklären, wie das funktioniert? Es wird einfach ein Datenrahmen für jeden Listeneintrag zurückgegeben?
Michael Barton

13
Imho die beste Antwort. Es gibt einen ehrlichen data.frame zurück. Alle Datentypen (Zeichen, Zahlen usw.) werden korrekt transformiert. Wenn die Liste unterschiedliche Datentypen hat, werden alle mit matrixAnnäherung in Zeichen umgewandelt .
Roah

1
Das hier bereitgestellte Beispiel ist nicht das von der Frage bereitgestellte. Das Ergebnis dieser Antwort auf den Originaldatensatz ist falsch.
MySchizoBuddy

Funktioniert super für mich! Und die Namen der Spalten im resultierenden Datenrahmen werden festgelegt! Tx
BAN

Ist Plyr Multicore? Oder gibt es eine lapply-Version zur Verwendung mit mclapply?
Garglesoap

103

data.frame(t(sapply(mylistlist,c)))

sapplykonvertiert es in eine Matrix. data.framekonvertiert die Matrix in einen Datenrahmen.


19
beste Antwort bei weitem! Bei keiner der anderen Lösungen werden die Typen / Spaltennamen korrekt angegeben. VIELEN DANK!
d_a_c321

1
Welche Rolle wollen Sie chier spielen, eine Instanz der Listendaten? Oh warte, c auf die Verkettungsfunktion, oder? Verwechseln mit der Verwendung von c durch @ mnel. Ich stimme auch @dchandler zu, die richtigen Spaltennamen zu finden, war in meinem Anwendungsfall ein wertvolles Bedürfnis. Geniale Lösung.
Jxramos

das Recht - Standard c Funktion; von ?c:Combine Values into a Vector or List
Alex Brown

1
funktioniert nicht mit den Beispieldaten in der Frage
MySchizoBuddy

3
Erzeugt dies nicht einen data.frame von Listen?
Carl

69

Angenommen, Ihre Liste heißt L,

data.frame(Reduce(rbind, L))

2
Schön! Es gibt einen Unterschied zwischen der Lösung von @Alex Brown und Ihrer. Wenn Sie Ihre Route wählen, wird aus irgendeinem Grund die folgende Warnmeldung ausgegeben: `Warnmeldung: In data.row.names (row.names, rowsi, i): Einige row.names wurden dupliziert : 3,4 -> row.names NICHT verwendet '
jxramos

Sehr gut!! Arbeitete für mich hier: stackoverflow.com/questions/32996321/…
Anastasia Pupynina

2
Funktioniert gut, es sei denn, die Liste enthält nur ein Element: data.frame(Reduce(rbind, list(c('col1','col2'))))Erzeugt einen Datenrahmen mit 2 Zeilen, 1 Spalte (ich habe 1 Zeile 2 Spalten erwartet)
The Red Pea

61

Das Paket data.tablehat die Funktion, rbindlistdie eine superschnelle Implementierung von ist do.call(rbind, list(...)).

Es kann dauern eine Liste lists, data.framesoder data.tables als Eingabe.

library(data.table)
ll <- list(a = list(var.1 = 1, var.2 = 2, var.3 = 3)
  , b = list(var.1 = 4, var.2 = 5, var.3 = 6)
  , c = list(var.1 = 7, var.2 = 8, var.3 = 9)
  , d = list(var.1 = 10, var.2 = 11, var.3 = 12)
  )

DT <- rbindlist(ll)

Dies gibt ein data.tableErbe von zurück data.frame.

Wenn Sie wirklich wieder in einen data.frame konvertieren möchten, verwenden Sieas.data.frame(DT)


In Bezug auf die letzte Zeile können Sie setDFjetzt als Referenz zu data.frame zurückkehren.
Frank

1
Für meine Liste mit 30.000 Artikeln funktionierte rbindlist viel schneller als ldply
tallharish

35

Das tibblePaket verfügt über eine Funktion enframe(), die dieses Problem löst, indem verschachtelte listObjekte zu verschachtelten Objekten tibble("aufgeräumten" Datenrahmen) gezwungen werden. Hier ist ein kurzes Beispiel von R for Data Science :

x <- list(
    a = 1:5,
    b = 3:4, 
    c = 5:6
) 

df <- enframe(x)
df
#> # A tibble: 3 × 2
#>    name     value
#>   <chr>    <list>
#>    1     a <int [5]>
#>    2     b <int [2]>
#>    3     c <int [2]>

Da Ihre Liste mehrere Nester enthält, lkönnen Sie mithilfe der unlist(recursive = FALSE)Option unnötige Verschachtelungen entfernen, um nur eine einzige hierarchische Liste abzurufen und anschließend an zu übergeben enframe(). Ich verwende tidyr::unnest(), um die Ausgabe in einen einstufigen "aufgeräumten" Datenrahmen zu entstören, der Ihre zwei Spalten enthält (eine für die Gruppe nameund eine für die Beobachtungen mit den Gruppen value). Wenn Sie Spalten wünschen, die breit werden, können Sie eine Spalte hinzufügen, indem Sie add_column()die Reihenfolge der Werte 132 Mal wiederholen. Dann nur noch spread()die Werte.


library(tidyverse)

l <- replicate(
    132,
    list(sample(letters, 20)),
    simplify = FALSE
)

l_tib <- l %>% 
    unlist(recursive = FALSE) %>% 
    enframe() %>% 
    unnest()
l_tib
#> # A tibble: 2,640 x 2
#>     name value
#>    <int> <chr>
#> 1      1     d
#> 2      1     z
#> 3      1     l
#> 4      1     b
#> 5      1     i
#> 6      1     j
#> 7      1     g
#> 8      1     w
#> 9      1     r
#> 10     1     p
#> # ... with 2,630 more rows

l_tib_spread <- l_tib %>%
    add_column(index = rep(1:20, 132)) %>%
    spread(key = index, value = value)
l_tib_spread
#> # A tibble: 132 x 21
#>     name   `1`   `2`   `3`   `4`   `5`   `6`   `7`   `8`   `9`  `10`  `11`
#> *  <int> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr>
#> 1      1     d     z     l     b     i     j     g     w     r     p     y
#> 2      2     w     s     h     r     i     k     d     u     a     f     j
#> 3      3     r     v     q     s     m     u     j     p     f     a     i
#> 4      4     o     y     x     n     p     i     f     m     h     l     t
#> 5      5     p     w     v     d     k     a     l     r     j     q     n
#> 6      6     i     k     w     o     c     n     m     b     v     e     q
#> 7      7     c     d     m     i     u     o     e     z     v     g     p
#> 8      8     f     s     e     o     p     n     k     x     c     z     h
#> 9      9     d     g     o     h     x     i     c     y     t     f     j
#> 10    10     y     r     f     k     d     o     b     u     i     x     s
#> # ... with 122 more rows, and 9 more variables: `12` <chr>, `13` <chr>,
#> #   `14` <chr>, `15` <chr>, `16` <chr>, `17` <chr>, `18` <chr>,
#> #   `19` <chr>, `20` <chr>

Zitat des OP: "Gibt es eine schnelle Möglichkeit, diese Struktur in einen Datenrahmen mit 132 Zeilen und 20 Datenspalten zu konvertieren?" Vielleicht brauchen Sie einen Ausbreitungsschritt oder so.
Frank

1
Ah ja, es muss nur eine Indexspalte geben, die verteilt werden kann. Ich werde in Kürze aktualisieren.
Matt Dancho

17

Abhängig von der Struktur Ihrer Listen gibt es einige tidyverseOptionen, die bei Listen mit ungleicher Länge gut funktionieren:

l <- list(a = list(var.1 = 1, var.2 = 2, var.3 = 3)
        , b = list(var.1 = 4, var.2 = 5)
        , c = list(var.1 = 7, var.3 = 9)
        , d = list(var.1 = 10, var.2 = 11, var.3 = NA))

df <- dplyr::bind_rows(l)
df <- purrr::map_df(l, dplyr::bind_rows)
df <- purrr::map_df(l, ~.x)

# all create the same data frame:
# A tibble: 4 x 3
  var.1 var.2 var.3
  <dbl> <dbl> <dbl>
1     1     2     3
2     4     5    NA
3     7    NA     9
4    10    11    NA

Sie können auch Vektoren und Datenrahmen mischen:

library(dplyr)
bind_rows(
  list(a = 1, b = 2),
  data_frame(a = 3:4, b = 5:6),
  c(a = 7)
)

# A tibble: 4 x 2
      a     b
  <dbl> <dbl>
1     1     2
2     3     5
3     4     6
4     7    NA

Diese Funktion dplyr :: bind_rows funktioniert gut, auch wenn es schwierig ist, mit Listen zu arbeiten, die ihren Ursprung in JSON haben. Von JSON zu einem überraschend sauberen Datenrahmen. Nett.
GGAnderson

@sbha Ich habe versucht, df <- purrr :: map_df (l, ~ .x) zu verwenden, aber es scheint nicht zu funktionieren. Die Fehlermeldung lautet Fehler: Spalte X2kann nicht von Ganzzahl in Zeichen konvertiert werden
Jolin

16

Reshape2 liefert die gleiche Ausgabe wie das obige Plyr-Beispiel:

library(reshape2)
l <- list(a = list(var.1 = 1, var.2 = 2, var.3 = 3)
          , b = list(var.1 = 4, var.2 = 5, var.3 = 6)
          , c = list(var.1 = 7, var.2 = 8, var.3 = 9)
          , d = list(var.1 = 10, var.2 = 11, var.3 = 12)
)
l <- melt(l)
dcast(l, L1 ~ L2)

Ausbeuten:

  L1 var.1 var.2 var.3
1  a     1     2     3
2  b     4     5     6
3  c     7     8     9
4  d    10    11    12

Wenn Sie fast keine Pixel mehr haben, können Sie dies alles in einer Zeile mit recast () tun.


12

Diese Methode verwendet ein tidyversePaket ( purrr ).

Die Liste:

x <- as.list(mtcars)

Konvertieren in einen Datenrahmen ( tibblegenauer gesagt):

library(purrr)
map_df(x, ~.x)

10

Erweitern Sie die Antwort von @ Marek: Wenn Sie vermeiden möchten, dass Zeichenfolgen in Faktoren umgewandelt werden, ist Effizienz kein Problem

do.call(rbind, lapply(your_list, data.frame, stringsAsFactors=FALSE))

10

Für den allgemeinen Fall tief verschachtelter Listen mit 3 oder mehr Ebenen, wie sie von einem verschachtelten JSON erhalten wurden:

{
"2015": {
  "spain": {"population": 43, "GNP": 9},
  "sweden": {"population": 7, "GNP": 6}},
"2016": {
  "spain": {"population": 45, "GNP": 10},
  "sweden": {"population": 9, "GNP": 8}}
}

Betrachten Sie zunächst den Ansatz melt(), die verschachtelte Liste in ein großes Format zu konvertieren:

myjson <- jsonlite:fromJSON(file("test.json"))
tall <- reshape2::melt(myjson)[, c("L1", "L2", "L3", "value")]
    L1     L2         L3 value
1 2015  spain population    43
2 2015  spain        GNP     9
3 2015 sweden population     7
4 2015 sweden        GNP     6
5 2016  spain population    45
6 2016  spain        GNP    10
7 2016 sweden population     9
8 2016 sweden        GNP     8

gefolgt von dcast()dann zu breit in einen ordentlichen Datensatz, in dem jede Variable eine Spalte und jede Beobachtung eine Zeile bildet:

wide <- reshape2::dcast(tall, L1+L2~L3) 
# left side of the formula defines the rows/observations and the 
# right side defines the variables/measurements
    L1     L2 GNP population
1 2015  spain   9         43
2 2015 sweden   6          7
3 2016  spain  10         45
4 2016 sweden   8          9

9

Weitere Antworten sowie Zeitangaben in der Antwort auf diese Frage: Wie lässt sich eine Liste am effizientesten als Datenrahmen umwandeln?

Der schnellste Weg, der keinen Datenrahmen mit Listen anstelle von Vektoren für Spalten erzeugt, scheint zu sein (aus Martin Morgans Antwort):

l <- list(list(col1="a",col2=1),list(col1="b",col2=2))
f = function(x) function(i) unlist(lapply(x, `[[`, i), use.names=FALSE)
as.data.frame(Map(f(l), names(l[[1]])))

8

Manchmal können Ihre Daten eine Liste von Listen von Vektoren gleicher Länge sein.

lolov = list(list(c(1,2,3),c(4,5,6)), list(c(7,8,9),c(10,11,12),c(13,14,15)) )

(Die inneren Vektoren könnten auch Listen sein, aber ich vereinfache, um dies leichter lesbar zu machen).

Dann können Sie die folgende Änderung vornehmen. Denken Sie daran, dass Sie jeweils eine Ebene aufheben können:

lov = unlist(lolov, recursive = FALSE )
> lov
[[1]]
[1] 1 2 3

[[2]]
[1] 4 5 6

[[3]]
[1] 7 8 9

[[4]]
[1] 10 11 12

[[5]]
[1] 13 14 15

Verwenden Sie nun Ihre in den anderen Antworten erwähnte Lieblingsmethode:

library(plyr)
>ldply(lov)
  V1 V2 V3
1  1  2  3
2  4  5  6
3  7  8  9
4 10 11 12
5 13 14 15

4

Das hat endlich bei mir funktioniert:

do.call("rbind", lapply(S1, as.data.frame))


4
l <- replicate(10,list(sample(letters, 20)))
a <-lapply(l[1:10],data.frame)
do.call("cbind", a)

3

Verwenden Sie für eine parallele Lösung (Multicore, Multisession usw.) unter Verwendung purrreiner Lösungsfamilie:

library (furrr)
plan(multisession) # see below to see which other plan() is the more efficient
myTibble <- future_map_dfc(l, ~.x)

Wo list die Liste?

Um das effizienteste Benchmarking durchzuführen plan(), können Sie Folgendes verwenden:

library(tictoc)
plan(sequential) # reference time
# plan(multisession) # benchamark plan() goes here. See ?plan().
tic()
myTibble <- future_map_dfc(l, ~.x)
toc()

3

Der folgende einfache Befehl hat bei mir funktioniert:

myDf <- as.data.frame(myList)

Referenz ( Quora Antwort )

> myList <- list(a = c(1, 2, 3), b = c(4, 5, 6))
> myList
$a
[1] 1 2 3

$b
[1] 4 5 6

> myDf <- as.data.frame(myList)
  a b
1 1 4
2 2 5
3 3 6
> class(myDf)
[1] "data.frame"

Dies schlägt jedoch fehl, wenn nicht klar ist, wie die Liste in einen Datenrahmen konvertiert werden soll:

> myList <- list(a = c(1, 2, 3), b = c(4, 5, 6, 7))
> myDf <- as.data.frame(myList)
Error in (function (..., row.names = NULL, check.rows = FALSE, check.names = TRUE,  : 
  arguments imply differing number of rows: 3, 4

Hinweis : Die Antwort bezieht sich auf den Titel der Frage und überspringt möglicherweise einige Details der Frage


Ein Hinweis, dass bei der Eingabe aus der Frage dies nur funktioniert. OP fordert 132 Zeilen und 20 Spalten an, dies ergibt jedoch 20 Zeilen und 132 Spalten.
Gregor Thomas

Für Ihr Beispiel mit einer Eingabe unterschiedlicher Länge, bei der es fehlschlägt, ist nicht klar, was das gewünschte Ergebnis sein würde ...
Gregor Thomas

@ Gregor True, aber der Fragentitel lautet "R - Liste zum Datenrahmen". Viele Besucher der Frage und diejenigen, die sie gewählt haben, haben nicht das genaue Problem der OP. Basierend auf dem Fragentitel suchen sie nur nach einer Möglichkeit, die Liste in einen Datenrahmen zu konvertieren. Ich selbst hatte das gleiche Problem und die Lösung, die ich gepostet habe, hat mein Problem gelöst
Ahmad

Ja, nur zur Kenntnis nehmen. Nicht downvoting. Es könnte schön sein, in der Antwort zu vermerken, dass es etwas Ähnliches tut - aber deutlich anders als - so ziemlich alle anderen Antworten.
Gregor Thomas

1

Ein kurzer (aber vielleicht nicht der schnellste) Weg, dies zu tun, wäre die Verwendung der Basis r, da ein Datenrahmen nur eine Liste von Vektoren gleicher Länge ist . Die Konvertierung zwischen Ihrer Eingabeliste und einem 30 x 132-Datenrahmen wäre also:

df <- data.frame(l)

Von dort aus können wir es in eine 132 x 30-Matrix transponieren und wieder in einen Datenrahmen konvertieren:

new_df <- data.frame(t(df))

Als Einzeiler:

new_df <- data.frame(t(data.frame(l)))

Die Rownamen sind ziemlich nervig anzusehen, aber Sie können sie jederzeit mit umbenennen

rownames(new_df) <- 1:nrow(new_df)


2
Warum wurde das abgelehnt? Ich würde es gerne wissen, damit ich keine Fehlinformationen weiter verbreite.
Will C

Ich habe dies definitiv schon einmal mit einer Kombination aus data.frame und t gemacht! Ich denke, die Leute, die herabgestimmt haben, glauben, dass es bessere Wege gibt, besonders diejenigen, die die Namen nicht durcheinander bringen.
Arthur Yip

1
Das ist ein guter Punkt, ich denke, das ist auch falsch, wenn Sie Namen in Ihrer Liste beibehalten möchten.
Will C

0

Wie wäre es mit der map_Funktion zusammen mit einer forSchleife? Hier ist meine Lösung:

list_to_df <- function(list_to_convert) {
  tmp_data_frame <- data.frame()
  for (i in 1:length(list_to_convert)) {
    tmp <- map_dfr(list_to_convert[[i]], data.frame)
    tmp_data_frame <- rbind(tmp_data_frame, tmp)
  }
  print(tmp_data_frame)
}

Dabei map_dfrkonvertieren Sie jedes Listenelement in einen data.frame und vereinen Sie sie dann rbindinsgesamt.

In Ihrem Fall wäre es wohl:

converted_list <- list_to_df(l)
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.