Converti un elenco in un frame di dati


513

Ho un elenco nidificato di dati. La sua lunghezza è 132 e ogni elemento è un elenco di lunghezza 20. Esiste un modo rapido per convertire questa struttura in un frame di dati che ha 132 righe e 20 colonne di dati?

Ecco alcuni dati di esempio con cui lavorare:

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

Quindi vuoi ogni elemento della lista come una riga di dati nel tuo data.frame?
Joshua Ulrich,

2
@RichieCotton Non è l'esempio giusto. "ogni articolo è un elenco di lunghezza 20" e ogni articolo è un elenco di un elemento di vettore di lunghezza 20.
Marek,

1
In ritardo alla festa, ma io non ho visto nessuno menzione questo , che mi è sembrato molto utile (per quello che stavo cercando di fare).
mflo-ByeSE,


Risposte:


390

Supponendo che il tuo elenco di elenchi sia chiamato l:

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

Quanto sopra converte tutte le colonne di caratteri in fattori, per evitare ciò è possibile aggiungere un parametro alla chiamata data.frame ():

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

109
Fai attenzione qui se i tuoi dati non sono tutti dello stesso tipo. Passare attraverso una matrice significa che tutti i dati saranno costretti in un tipo comune. Vale a dire se hai una colonna di dati carattere e una colonna di dati numerici, i dati numerici saranno costretti a stringa per matrice () e poi entrambi a fattore per data.frame ().
Ian Sudbery,

Qual è il modo migliore per farlo in cui l'elenco ha valori mancanti o per includere NA nel frame di dati?
Dave,

1
@Dave: Funziona per me ... vedi qui r-fiddle.org/#/fiddle?id=y8DW7lqL&version=3
nico

4
Fai anche attenzione se hai un tipo di dati carattere - data.frame lo convertirà in fattori.
Alex Brown,

4
@nico C'è un modo per mantenere i nomi degli elementi della lista come nomi secondari o rownames nel df?
N.Varela,

472

Con rbind

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

Edit: Versione precedente il ritorno data.framedi list's invece di vettori (come @IanSudbery sottolineato nei commenti).


5
Perché funziona ma rbind(your_list)restituisce una matrice elenco 1x32?
eykanal,

26
@eykanal do.callpassa elementi di your_listcome argomenti a rbind. È equivalente a rbind(your_list[[1]], your_list[[2]], your_list[[3]], ....., your_list[[length of your_list]]).
Marek,

2
Questo metodo soffre della situazione nulla.
Frank Wang,

3
@FrankWANG Ma questo metodo non è progettato per una situazione nulla. È necessario che your_listcontengano vettori di dimensioni uguali. NULLha lunghezza 0 quindi dovrebbe fallire.
Marek,

12
Questo metodo sembra restituire l'oggetto corretto, ma esaminando l'oggetto, scoprirai che le colonne sono elenchi anziché vettori, il che può portare a problemi lungo la linea se non te lo aspetti.
Ian Sudbery,

135

Puoi usare il plyrpacchetto. Ad esempio un elenco nidificato del modulo

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

ora ha una lunghezza di 4 e ogni elenco in lcontiene un altro elenco della lunghezza 3. Ora puoi eseguire

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

e dovrebbe ottenere lo stesso risultato della risposta @Marek e @nico.


8
Bella risposta. Potresti spiegare un po 'come funziona? Restituisce semplicemente un frame di dati per ogni voce dell'elenco?
Michael Barton,

13
Imho la risposta MIGLIORE. Restituisce un data.frame onesto. Tutti i tipi di dati (carattere, numerico, ecc.) Sono trasformati correttamente. Se l'elenco ha diversi tipi di dati, questi saranno tutti trasformati in carattere con matrixapproccio.
Roah,

1
l'esempio fornito qui non è quello fornito dalla domanda. il risultato di questa risposta sul set di dati originale non è corretto.
MySchizoBuddy,

Funziona benissimo per me! E vengono impostati i nomi delle colonne nel Data Frame risultante! Tx
ban

Plyr è multicore? Oppure esiste una versione lapply da utilizzare con mclapply?
Garglesoap,

103

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

sapplylo converte in una matrice. data.frameconverte la matrice in un frame di dati.


19
la migliore risposta di gran lunga! Nessuna delle altre soluzioni ottiene i nomi di tipi / colonne corretti. GRAZIE!
d_a_c321

1
Che ruolo intendi csvolgere qui, un'istanza dei dati dell'elenco? Oh aspetta, c per la funzione concatenata giusto? Essere confusi con l'uso di @ mnel di c. Concordo anche con @dchandler, ottenere correttamente i nomi delle colonne era una necessità preziosa nel mio caso d'uso. Soluzione brillante.
jxramos,

quel diritto - funzione c standard; da ?c:Combine Values into a Vector or List
Alex Brown,

1
non funziona con i dati di esempio forniti nella domanda
MySchizoBuddy,

3
Questo non genera un data.frame di liste?
Carl,

69

supponiamo che il tuo elenco sia chiamato L,

data.frame(Reduce(rbind, L))

2
Ben fatto! C'è una differenza con la soluzione di @Alex Brown rispetto alla tua, andare lungo il tuo percorso ha prodotto il seguente messaggio di avviso per qualche motivo: `Messaggio di avviso: in data.row.names (row.names, rowi, i): alcuni row.names duplicati : 3,4 -> row.names NON utilizzato '
jxramos

Molto bene!! Ha lavorato per me qui: stackoverflow.com/questions/32996321/...
Anastasia Pupynina

2
Funziona bene a meno che l'elenco non contenga un solo elemento: data.frame(Reduce(rbind, list(c('col1','col2'))))produce un frame di dati con 2 righe, 1 colonna (mi aspettavo 1 riga 2 colonne)
The Red Pea,

61

Il pacchetto data.tableha la funzione rbindlistche è un'implementazione superveloce di do.call(rbind, list(...)).

Si può prendere un elenco di lists, data.frameso data.tables come input.

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)

Questo restituisce un data.tableereditario da data.frame.

Se vuoi davvero riconvertire in un data.frame usaas.data.frame(DT)


Per quanto riguarda l'ultima riga, setDFora consente di tornare a data.frame per riferimento.
Frank,

1
Per la mia lista con 30k articoli, rbindlist ha funzionato molto più velocemente di ldply
tallharish

35

Il tibblepacchetto ha una funzione enframe()che risolve questo problema forzando listoggetti nidificati in oggetti nidificati tibble("ordinati"). Ecco un breve esempio di R per 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]>

Dato che hai diversi nidi nell'elenco, lpuoi utilizzare il unlist(recursive = FALSE)per rimuovere l'annidamento non necessario per ottenere un solo elenco gerarchico e poi passare a enframe(). Uso tidyr::unnest()per annotare l'output in un frame di dati "ordinato" a livello singolo, che ha le tue due colonne (una per il gruppo namee una per le osservazioni con i gruppi value). Se si desidera che le colonne si allarghino, è possibile aggiungere una colonna usando add_column()che ripete semplicemente l'ordine dei valori 132 volte. Quindi solo spread()i valori.


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>

Citando l'OP: "Esiste un modo rapido per convertire questa struttura in un frame di dati che ha 132 righe e 20 colonne di dati?" Quindi forse hai bisogno di un passaggio diffuso o qualcosa del genere.
Frank,

1
Ah sì, ci deve solo essere una colonna di indice che può essere diffusa. Aggiornerò a breve.
Matt Dancho,

17

A seconda della struttura dei tuoi elenchi ci sono alcune tidyverseopzioni che funzionano bene con elenchi di lunghezza diversa:

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

Puoi anche mescolare vettori e frame di dati:

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

Questa funzione di dplyr :: bind_rows funziona bene, anche se è difficile lavorare con elenchi che hanno origine come JSON. Da JSON a un frame di dati sorprendentemente pulito. Bello.
GGAnderson,

@sbha Ho provato a usare df <- purrr :: map_df (l, ~ .x) ma sembra che non funzioni, il messaggio di errore che ho è Errore: la colonna X2non può essere convertita da numero intero a carattere
Jolin

16

Reshape2 produce lo stesso output dell'esempio plyr sopra:

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)

rendimenti:

  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

Se tu fossi quasi fuori di pixel che si potrebbe fare tutto questo in 1 linea w / rifusione ().


12

Questo metodo utilizza un tidyversepacchetto ( purrr ).

La lista:

x <- as.list(mtcars)

Convertendolo in un frame di dati (uno tibblepiù specifico):

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

10

Estendendo la risposta di @ Marek: se vuoi evitare che le stringhe vengano trasformate in fattori e l'efficienza non è un problema, prova

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

10

Per il caso generale di elenchi profondamente nidificati con 3 o più livelli come quelli ottenuti da un JSON nidificato:

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

considerare prima l'approccio di melt()convertire l'elenco nidificato in un formato alto:

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

seguito da dcast()poi allargare di nuovo in un set di dati ordinato in cui ogni variabile forma una colonna e ogni osservazione forma una riga:

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

Altre risposte, insieme ai tempi nella risposta a questa domanda: Qual è il modo più efficiente per trasmettere un elenco come frame di dati?

Il modo più rapido, che non produce un frame di dati con elenchi anziché vettori per colonne sembra essere (dalla risposta di 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]])))

8

A volte i tuoi dati possono essere un elenco di elenchi di vettori della stessa lunghezza.

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

(I vettori interni potrebbero anche essere elenchi, ma sto semplificando per renderlo più facile da leggere).

Quindi è possibile apportare la seguente modifica. Ricorda che puoi deselezionare un livello alla volta:

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

Ora usa il tuo metodo preferito menzionato nelle altre risposte:

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

Questo è ciò che finalmente ha funzionato per me:

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

Per una soluzione parallela (multicore, multisessione, ecc.) Che utilizza una purrrfamiglia di soluzioni, utilizzare:

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

Dov'è lla lista.

Per valutare il più efficiente plan()puoi usare:

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

3

Il seguente semplice comando ha funzionato per me:

myDf <- as.data.frame(myList)

Riferimento ( risposta 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"

Ma questo fallirà se non è ovvio come convertire l'elenco in un frame di dati:

> 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 risposta è rivolta al titolo della domanda e potrebbe saltare alcuni dettagli della domanda


Una nota che sull'input dalla domanda questo solo tipo di lavoro. OP richiede 132 righe e 20 colonne, ma ciò fornisce 20 righe e 132 colonne.
Gregor Thomas,

Per il tuo esempio con input di diversa lunghezza in cui non riesce, non è chiaro quale sarebbe il risultato desiderato ...
Gregor Thomas

@Gregor True, ma il titolo della domanda è "R - list to data frame". Molti visitatori della domanda e coloro che l'hanno votata non hanno il problema esatto di OP. In base al titolo della domanda, cercano solo un modo per convertire l'elenco in frame di dati. Io stesso ho avuto lo stesso problema e la soluzione che ho pubblicato ha risolto il mio problema
Ahmad,

Sì, sto solo notando. Non sottovalutare. Potrebbe essere bello notare nella risposta che fa qualcosa di simile - ma nettamente diverso da - praticamente tutte le altre risposte.
Gregor Thomas,

1

Un modo breve (ma forse non il più veloce) per farlo sarebbe usare la base r, poiché un frame di dati è solo un elenco di vettori di uguale lunghezza . Pertanto la conversione tra l'elenco di input e un data.frame 30 x 132 sarebbe:

df <- data.frame(l)

Da lì possiamo trasporlo in una matrice 132 x 30 e riconvertirlo in un frame di dati:

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

Come una linea:

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

I rownames saranno piuttosto fastidiosi da guardare, ma potresti sempre rinominarli

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


2
Perché questo è stato downvoted? Mi piacerebbe saperlo, quindi non continuo a diffondere disinformazione.
Will C

L'ho già fatto prima, usando una combinazione di data.frame et! Immagino che le persone che hanno effettuato il downvoting sentano che ci sono modi migliori, in particolare quelli che non sbagliano i nomi.
Arthur Yip

1
Questo è un buon punto, immagino che sia errato anche se vuoi preservare i nomi nella tua lista.
Will C

0

Che ne dici di usare map_ funzione insieme ad un forloop? Ecco la mia soluzione:

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

dove map_dfrconverte ciascuno degli elementi dell'elenco in un data.frame e poi rbindli unisce del tutto.

Nel tuo caso, immagino che sarebbe:

converted_list <- list_to_df(l)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.