Convertir una lista en un marco de datos

513

Tengo una lista anidada de datos. Su longitud es 132 y cada elemento es una lista de longitud 20. ¿Hay un rápido manera de convertir esta estructura en un marco de datos que tenga 132 filas y 20 columnas de datos?

Aquí hay algunos datos de muestra para trabajar:

l <- replicate(
  132,
  list(sample(letters, 20)),
  simplify = FALSE
)
Btibert3
fuente
Entonces, ¿desea que cada elemento de la lista sea una fila de datos en su data.frame?
Joshua Ulrich
2
@RichieCotton No es un buen ejemplo. "cada elemento es una lista de longitud 20" y usted obtuvo que cada elemento es una lista de elementos de un vector de longitud 20.
Marek
1
Llegó tarde a la fiesta, pero no vi a nadie mencionar esto , lo que pensé que era muy útil (por lo que estaba buscando hacer).
mflo-ByeSE

Respuestas:

390

Asumiendo que su lista de listas se llama l:

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

Lo anterior convertirá todas las columnas de caracteres en factores, para evitar esto, puede agregar un parámetro a la llamada data.frame ():

df <- data.frame(matrix(unlist(l), nrow=132, byrow=T),stringsAsFactors=FALSE)
nico
fuente
109
Tenga cuidado aquí si sus datos no son todos del mismo tipo. Pasar a través de una matriz significa que todos los datos se convertirán en un tipo común. Es decir, si tiene una columna de datos de caracteres y una columna de datos numéricos, los datos numéricos se coaccionarán a una cadena por matriz () y luego a ambos por factorizar por data.frame ().
Ian Sudbery
¿Cuál es la mejor manera de hacer esto cuando la lista tiene valores faltantes o incluir NA en el marco de datos?
Dave
1
@Dave: funciona para mí ... mira aquí r-fiddle.org/#/fiddle?id=y8DW7lqL&version=3
nico
44
También tenga cuidado si tiene un tipo de datos de caracteres: data.frame lo convertirá en factores.
Alex Brown
44
@nico ¿Hay alguna manera de mantener los nombres de los elementos de la lista como colnames o rownames en el df?
N.Varela
472

Con rbind

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

Editar: Versión anterior de retorno data.framede list's en lugar de vectores (como @IanSudbery señaló en los comentarios).

Marek
fuente
55
¿Por qué funciona esto pero rbind(your_list)devuelve una matriz de lista 1x32?
eykanal
26
@eykanal do.callpasa elementos de your_listcomo argumentos a rbind. Es equivalente rbind(your_list[[1]], your_list[[2]], your_list[[3]], ....., your_list[[length of your_list]]).
Marek
2
Este método sufre de la situación nula.
Frank Wang
3
@FrankWANG Pero este método no está diseñado para una situación nula. Se requiere que your_listcontenga vectores de igual tamaño. NULLtiene longitud 0, por lo que debería fallar.
Marek
12
Este método parece devolver el objeto correcto, pero al inspeccionar el objeto, encontrará que las columnas son listas en lugar de vectores, lo que puede generar problemas en el futuro si no lo espera.
Ian Sudbery
135

Puedes usar el plyrpaquete. Por ejemplo, una lista anidada del formulario

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

ahora tiene una longitud de 4 y cada lista lcontiene otra lista de la longitud 3. Ahora puede ejecutar

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

y debería obtener el mismo resultado que en la respuesta @Marek y @nico.

mropa
fuente
8
Gran respuesta. ¿Podría explicarme un poco cómo funciona? ¿Simplemente devuelve un marco de datos para cada entrada de la lista?
Michael Barton
13
Imho la MEJOR respuesta. Devuelve un marco de datos honesto. Todos los tipos de datos (caracteres, numéricos, etc.) se transforman correctamente. Si la lista tiene diferentes tipos de datos, todos se transformarán en caracteres con matrixenfoque.
Roah
1
La muestra proporcionada aquí no es la proporcionada por la pregunta. El resultado de esta respuesta en el conjunto de datos original es incorrecto.
MySchizoBuddy
¡Funciona muy bien para mí! ¡Y se establecen los nombres de las columnas en el Marco de datos resultante! Tx
BAN
¿Plyr es multinúcleo? ¿O hay una versión lapply para usar con mclapply?
Garglesoap
103

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

sapplylo convierte en una matriz. data.frameConvierte la matriz en un marco de datos.

Alex Brown
fuente
19
mejor respuesta por lejos! Ninguna de las otras soluciones obtiene los tipos / nombres de columna correctos. ¡GRACIAS!
d_a_c321
1
¿Qué papel tiene la intención cde jugar aquí, una instancia de los datos de la lista? Oh, espera, c para la función concatenada ¿verdad? Confundirse con el uso de @ mnel de c. También estoy de acuerdo con @dchandler, obtener los nombres de columna correctos fue una necesidad valiosa en mi caso de uso. Solución brillante
jxramos
ese derecho - función c estándar; desde ?c:Combine Values into a Vector or List
Alex Brown
1
no funciona con los datos de muestra proporcionados en la pregunta
MySchizoBuddy
3
¿No genera esto un data.frame de listas?
Carl
69

asume que tu lista se llama L,

data.frame(Reduce(rbind, L))
jdeng
fuente
2
¡Buena esa! Hay una diferencia con la solución de @Alex Brown en comparación con la suya, ir por su ruta arrojó el siguiente mensaje de advertencia por alguna razón: `Mensaje de advertencia: en data.row.names (row.names, rowsi, i): algunos row.names duplicados : 3,4 -> fila.nombres NO utilizados '
jxramos
¡¡Muy bien!! Trabajó para mí aquí: stackoverflow.com/questions/32996321/…
Anastasia Pupynina
2
Funciona bien a menos que la lista tenga solo un elemento: data.frame(Reduce(rbind, list(c('col1','col2'))))produce un marco de datos con 2 filas, 1 columna (esperaba 1 fila 2 columnas)
The Red Pea
61

El paquete data.tabletiene la función rbindlistque es una implementación súper rápida dedo.call(rbind, list(...)) .

Puede tomar una lista de lists, data.frameso data.tables como entrada.

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)

Esto devuelve una data.tableherencia dedata.frame .

Si realmente desea volver a convertir a un data.frame useas.data.frame(DT)

mnel
fuente
En cuanto a la última línea, setDFahora permite volver a data.frame por referencia.
Frank
1
Para mi lista con 30k artículos, rbindlist funcionó mucho más rápido que ldply
tallharish
35

El tibblepaquete tiene una función enframe()que resuelve este problema mediante la coerción de listobjetos anidados a objetos anidados tibble(marco de datos "ordenado"). Aquí hay un breve ejemplo de R para 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]>

Como tiene varios nidos en su lista, lpuede usar elunlist(recursive = FALSE) para eliminar el anidamiento innecesario para obtener una sola lista jerárquica y luego pasar a enframe(). Utilizo tidyr::unnest()para descomponer la salida en un marco de datos "ordenado" de un solo nivel, que tiene sus dos columnas (una para el grupo namey otra para las observaciones con los grupos value). Si desea columnas que se amplíen, puede agregar una columna add_column()que simplemente repita el orden de los valores 132 veces. Entonces solo spread()los valores.


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>
Matt Dancho
fuente
Citando el OP: "¿Hay una manera rápida de convertir esta estructura en un marco de datos que tenga 132 filas y 20 columnas de datos?" Entonces tal vez necesites un paso extendido o algo así.
Frank
1
Ah sí, solo tiene que haber una columna de índice que se pueda extender. Voy a actualizar en breve.
Matt Dancho
17

Dependiendo de la estructura de sus listas, hay algunas tidyverseopciones que funcionan bien con listas de longitud desigual:

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

También puede mezclar vectores y marcos de datos:

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
sbha
fuente
Esta función dplyr :: bind_rows funciona bien, incluso con listas difíciles de trabajar que se originan como JSON. Desde JSON hasta un marco de datos sorprendentemente limpio. Agradable.
GGAnderson
@sbha Intenté usar df <- purrr :: map_df (l, ~ .x) pero parece que no funciona, el mensaje de error que tengo es Error: la columna X2no se puede convertir de entero a carácter
Jolin
16

Reshape2 produce el mismo resultado que el ejemplo de plyr anterior:

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)

rendimientos:

  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

Si estaba casi sin píxeles, podría hacer esto todo en 1 línea con recast ().

Jack Ryan
fuente
12

Este método utiliza un tidyversepaquete ( ronroneo ).

La lista:

x <- as.list(mtcars)

Convirtiéndolo en un marco de datos ( tibblemás específicamente):

library(purrr)
map_df(x, ~.x)
Guardado por Jesús
fuente
10

Ampliando la respuesta de @ Marek: si desea evitar que las cadenas se conviertan en factores y la eficiencia no es una preocupación, intente

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

Para el caso general de listas profundamente anidadas con 3 o más niveles como los obtenidos de un JSON anidado:

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

considere el enfoque de melt()convertir la lista anidada a un formato alto primero:

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

seguido por dcast()entonces para ampliarse nuevamente en un conjunto de datos ordenado donde cada variable forma una columna y cada observación forma una fila:

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
RubenLaguna
fuente
9

Más respuestas, junto con tiempos en la respuesta a esta pregunta: ¿Cuál es la forma más eficiente de emitir una lista como un marco de datos?

La forma más rápida, que no produce un marco de datos con listas en lugar de vectores para columnas parece ser (de la respuesta de Martin Morgan):

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]])))
Ian Sudbery
fuente
8

Algunas veces sus datos pueden ser una lista de listas de vectores de la misma longitud.

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

(Los vectores internos también podrían ser listas, pero estoy simplificando para que sea más fácil de leer).

Entonces puedes hacer la siguiente modificación. Recuerde que puede anular la lista de un nivel a la vez:

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

Ahora use su método favorito mencionado en las otras respuestas:

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
usuario36302
fuente
4

Esto es lo que finalmente funcionó para mí:

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

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

Para una solución en paralelo (multinúcleo, multisesión, etc.) que utiliza una purrrfamilia de soluciones, use:

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

Donde lesta la lista

Para comparar el más eficiente plan()que puede usar:

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

El siguiente comando simple funcionó para mí:

myDf <- as.data.frame(myList)

Referencia ( respuesta de Quora )

> 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"

Pero esto fallará si no es obvio cómo convertir la lista a un marco de datos:

> 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

Nota : La respuesta es hacia el título de la pregunta y puede omitir algunos detalles de la pregunta.

Ahmad
fuente
Una nota de que en la entrada de la pregunta esto solo funciona. OP solicita 132 filas y 20 columnas, pero esto da 20 filas y 132 columnas.
Gregor Thomas el
Para su ejemplo con entrada de longitud diferente donde falla, no está claro cuál sería el resultado deseado ...
Gregor Thomas
@ Gregor Verdadero, pero el título de la pregunta es "Lista R al marco de datos". Muchos visitantes de la pregunta y aquellos que la votaron no tienen el problema exacto de OP. Según el título de la pregunta, solo buscan una forma de convertir la lista en un marco de datos. Yo mismo tuve el mismo problema y la solución que publiqué resolvió mi problema
Ahmad
Sí, solo notando. No voto negativo. Puede ser bueno notar en la respuesta que hace algo similar, pero claramente diferente de, prácticamente todas las otras respuestas.
Gregor Thomas el
1

Una forma corta (pero quizás no la más rápida) de hacerlo sería utilizar la base r, ya que un marco de datos es solo una lista de vectores de igual longitud . Por lo tanto, la conversión entre su lista de entrada y un marco de datos de 30 x 132 sería:

df <- data.frame(l)

Desde allí, podemos transponerlo a una matriz de 132 x 30 y convertirlo nuevamente en un marco de datos:

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

Como una línea:

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

Los nombres de las filas serán bastante molestos de ver, pero siempre puede cambiar el nombre de aquellos con

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

Will C
fuente
2
¿Por qué se rechazó esto? Me gustaría saber para no seguir difundiendo información errónea.
Will C
Definitivamente he hecho esto antes, usando una combinación de data.frame y t! Creo que las personas que votaron negativamente sienten que hay mejores formas, particularmente aquellas que no confunden los nombres.
Arthur Yip
1
Ese es un buen punto, supongo que esto también es incorrecto si desea conservar los nombres en su lista.
Will C
0

¿Qué hay de usar map_ función junto con un forbucle? Aquí está mi solución:

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

donde map_dfrconvertir cada elemento de la lista en un data.frame y luego rbindunirlos por completo.

En su caso, supongo que sería:

converted_list <- list_to_df(l)
Bảo Trần
fuente