Converter uma lista em um quadro de dados

513

Eu tenho uma lista aninhada de dados. Seu comprimento é 132 e cada item é uma lista de comprimento 20. Existe uma maneira rápida de converter essa estrutura em um quadro de dados com 132 linhas e 20 colunas de dados?

Aqui estão alguns dados de amostra para trabalhar:

l <- replicate(
  132,
  list(sample(letters, 20)),
  simplify = FALSE
)
Btibert3
fonte
Então você quer que cada elemento da lista seja uma linha de dados no seu data.frame?
19372 Joshua Ulrich
2
@RichieCotton Não é o exemplo certo. "cada item é uma lista de comprimento 20" e você tem cada item é uma lista um elemento do vector de comprimento 20.
Marek
1
Chegou atrasado à festa, mas não vi ninguém mencionar isso , o que achei muito útil (para o que estava procurando fazer).
Mflo-ByeSE

Respostas:

390

Supondo que sua lista de listas seja chamada l:

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

O acima irá converter todas as colunas de caracteres em fatores, para evitar isso, você pode adicionar um parâmetro à chamada data.frame ():

df <- data.frame(matrix(unlist(l), nrow=132, byrow=T),stringsAsFactors=FALSE)
nico
fonte
109
Cuidado aqui se seus dados não forem do mesmo tipo. Passar por uma matriz significa que todos os dados serão coagidos em um tipo comum. Ou seja, se você tiver uma coluna de dados de caracteres e uma coluna de dados numéricos, os dados numéricos serão coagidos a string por matriz () e ambos a fatorar por data.frame ().
Ian Sudbery
Qual é a melhor maneira de fazer isso onde a lista tem valores ausentes ou incluir NA no quadro de dados?
25413 Dave
1
@ Dave: funciona para mim ... ver aqui r-fiddle.org/#/fiddle?id=y8DW7lqL&version=3
nico
4
Tome cuidado também se você tiver um tipo de dado de caractere - data.frame o converterá em fatores.
Alex
4
@nico Existe uma maneira de manter os nomes dos elementos da lista como nomes de nomes ou nomes de domínio no df?
N.Varela
472

Com rbind

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

Edit: retorno data.frameda versão anterior de list's em vez de vetores (como @IanSudbery apontou nos comentários).

Marek
fonte
5
Por que isso funciona, mas rbind(your_list)retorna uma matriz de lista 1x32?
eykanal
26
@eykanal do.callpassa elementos de your_listas argumentos para rbind. É equivalente a rbind(your_list[[1]], your_list[[2]], your_list[[3]], ....., your_list[[length of your_list]]).
Marek
2
Este método sofre com a situação nula.
Frank Wang
3
@FrankWANG Mas esse método não foi projetado para uma situação nula. É necessário que your_listcontenha vetores de tamanhos iguais. NULLtem comprimento 0, portanto deve falhar.
Marek
12
Esse método parece retornar o objeto correto, mas, ao inspecionar o objeto, você verá que as colunas são listas e não vetores, o que pode levar a problemas na linha se você não o estiver esperando.
Ian Sudbery
135

Você pode usar o plyrpacote. Por exemplo, uma lista aninhada do formulário

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

agora tem um comprimento de 4 e cada lista lcontém outra lista do comprimento 3. Agora você pode executar

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

e deve obter o mesmo resultado que nas respostas @Marek e @nico.

mropa
fonte
8
Ótima resposta. Eu poderia explicar um pouco como isso funciona? Ele simplesmente retorna um quadro de dados para cada entrada da lista?
Michael Barton
13
Imho a melhor resposta. Retorna um data.frame honesto. Todos os tipos de dados (caracteres, numéricos, etc) são transformados corretamente. Se a lista tiver tipos de dados diferentes, todos serão transformados em caracteres com matrixabordagem.
Roah
1
a amostra fornecida aqui não é a fornecida pela pergunta. o resultado desta resposta no conjunto de dados original está incorreto.
MySchizoBuddy
Funciona muito bem para mim! E os nomes das colunas no Data Frame resultante são definidos! Tx
bAN
O plyr é multicore? Ou existe uma versão lapply para uso com o mclapply?
Garglesoap
103

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

sapplyconverte-o em uma matriz. data.frameconverte a matriz em um quadro de dados.

Alex Brown
fonte
19
melhor resposta de longe! Nenhuma das outras soluções obtém os tipos / nomes de colunas corretos. OBRIGADO!
d_a_c321 11/01
1
Que papel você pretende cdesempenhar aqui, uma instância dos dados da lista? Ah, espere, c pela função concatenar, certo? Confundindo-se com o uso de c. Também concordo com o @dchandler, acertar os nomes das colunas era uma necessidade valiosa no meu caso de uso. Solução brilhante.
jxramos
que direito - função c padrão; a partir de ?c:Combine Values into a Vector or List
Alex Brown
1
não funciona com os dados de amostra fornecidos na questão
MySchizoBuddy
3
Isso não gera um data.frame de listas?
2626 Carl
69

suponha que sua lista seja chamada L,

data.frame(Reduce(rbind, L))
jdeng
fonte
2
Agradável! Há uma diferença com a solução do @Alex Brown em comparação com a sua, seguir seu caminho por algum motivo: `Mensagem de aviso: Em data.row.names (row.names, linhasi, i): alguns row.names duplicados : 3,4 -> row.names NOT used '
jxramos
Muito bom!! Trabalhou para mim aqui: stackoverflow.com/questions/32996321/…
Anastasia Pupynina
2
Funciona bem, a menos que a lista tenha apenas um elemento: data.frame(Reduce(rbind, list(c('col1','col2'))))produz um quadro de dados com 2 linhas, 1 coluna (eu esperava 1 linha 2 colunas)
The Red Pea
61

O pacote data.tabletem a função rbindlistque é uma implementação super rápida do.call(rbind, list(...)).

Pode levar uma lista de lists, data.framesou 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)

Isso retorna um data.tableherda de data.frame.

Se você realmente deseja converter novamente em data.frame, useas.data.frame(DT)

mnel
fonte
Em relação à última linha, setDFagora permite retornar ao data.frame por referência.
Frank
1
Para minha lista com 30k itens, rbindlist trabalhou muito mais rápido do ldply
tallharish
35

O tibblepacote possui uma função enframe()que resolve esse problema coagindo listobjetos aninhados a objetos aninhados tibble("arrumar o quadro de dados"). Aqui está um breve exemplo do 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]>

Como você tem vários ninhos em sua lista, lvocê pode usar o unlist(recursive = FALSE)para remover o aninhamento desnecessário para obter apenas uma única lista hierárquica e depois passar para enframe(). Eu uso tidyr::unnest()para desnaturar a saída em um único quadro de dados "organizado", com duas colunas (uma para o grupo namee outra para as observações dos grupos value). Se você deseja colunas que ampliam, é possível adicionar uma coluna add_column()que apenas repete a ordem dos valores 132 vezes. Então apenas spread()os 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
fonte
Citando o OP: "Existe uma maneira rápida de converter essa estrutura em um quadro de dados com 132 linhas e 20 colunas de dados?" Então, talvez você precise de uma etapa de expansão ou algo assim.
Frank
1
Ah, sim, só precisa haver uma coluna de índice que possa ser espalhada. Vou atualizar em breve.
precisa saber é o seguinte
17

Dependendo da estrutura de suas listas, existem algumas tidyverseopções que funcionam bem com listas de tamanhos diferentes:

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

Você também pode misturar vetores e quadros de dados:

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
fonte
Essa função dplyr :: bind_rows funciona bem, mesmo com trabalho difícil com listas originadas como JSON. De JSON a um quadro de dados surpreendentemente limpo. Agradável.
GGAnderson
@sbha Eu tentei usar o df <- purrr :: map_df (l, ~ .x), mas parece que não está funcionando, a mensagem de erro que tenho é: Erro: a coluna X2não pode ser convertida de número inteiro para caractere
Jolin
16

Remodelar2 produz a mesma saída que o exemplo plyr acima:

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)

rendimentos:

  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 você estivesse quase sem pixels, poderá fazer tudo isso em 1 linha com reformulação ().

Jack Ryan
fonte
12

Este método usa um tidyversepacote ( purrr ).

A lista:

x <- as.list(mtcars)

Convertendo-o em um quadro de dados ( tibblemais especificamente):

library(purrr)
map_df(x, ~.x)
SavedByJESUS
fonte
10

Ampliando a resposta da @ Marek: se você deseja evitar que as strings sejam transformadas em fatores e a eficiência não é uma preocupação, tente

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

Para o caso geral de listas profundamente aninhadas com 3 ou mais níveis, como os obtidos de um JSON aninhado:

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

considere a abordagem de melt()converter a lista aninhada em um formato alto primeiro:

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()então para ampliar novamente em um conjunto de dados organizado, onde cada variável forma uma coluna e cada observação forma uma linha:

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
fonte
9

Mais respostas, além de tempos na resposta a esta pergunta: Qual é a maneira mais eficiente de transmitir uma lista como um quadro de dados?

A maneira mais rápida, que não produz um quadro de dados com listas, em vez de vetores para colunas, parece ser (da resposta 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
fonte
8

Às vezes, seus dados podem ser uma lista de listas de vetores do mesmo comprimento.

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

(Os vetores internos também podem ser listas, mas estou simplificando para facilitar a leitura).

Então você pode fazer a seguinte modificação. Lembre-se de que você pode desassistir um nível de cada 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

Agora use seu método favorito mencionado nas outras respostas:

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
user36302
fonte
4

Isto é o que finalmente funcionou para mim:

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

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

Para uma solução paralela (multicore, multisessão, etc) usando a purrrfamília de soluções, use:

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

Onde lestá a lista?

Para comparar o mais eficiente, plan()você pode usar:

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

O seguinte comando simples funcionou para mim:

myDf <- as.data.frame(myList)

Referência ( resposta do 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"

Mas isso falhará se não for óbvio como converter a lista em um quadro de dados:

> 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 : A resposta é referente ao título da pergunta e pode pular alguns detalhes da pergunta

Ahmad
fonte
Observe que, na entrada da pergunta, isso apenas funciona. O OP solicita 132 linhas e 20 colunas, mas isso fornece 20 linhas e 132 colunas.
Gregor Thomas
Para o seu exemplo, com entrada diferente de comprimento, onde ele falhar, não está claro o que o resultado desejado seria ...
Gregor Thomas
@ Gregor True, mas o título da pergunta é "R - listar no quadro de dados". Muitos visitantes da pergunta e aqueles que votaram nela não têm o problema exato do OP. Com base no título da pergunta, eles apenas procuram uma maneira de converter a lista em quadro de dados. Eu mesmo tive o mesmo problema e da solução que eu postei resolveu o meu problema
Ahmad
Sim, apenas observando. Não com voto negativo. Pode ser bom notar na resposta que ele faz algo semelhante - mas distintamente diferente de - praticamente todas as outras respostas.
Gregor Thomas
1

Uma maneira curta (mas talvez não a mais rápida) de fazer isso seria usar a base r, pois um quadro de dados é apenas uma lista de vetores de comprimento igual . Assim, a conversão entre sua lista de entrada e um data.frame de 30 x 132 seria:

df <- data.frame(l)

A partir daí, podemos transpor para uma matriz de 132 x 30 e convertê-la novamente em um dataframe:

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

Como uma linha:

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

Os nomes de usuário serão muito irritantes de se olhar, mas você sempre pode renomear aqueles com

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

Will C
fonte
2
Por que isso foi prejudicado? Eu gostaria de saber para não continuar divulgando informações erradas.
C #
Definitivamente, eu já fiz isso antes, usando uma combinação de data.frame et! Eu acho que as pessoas que votaram mal acreditam que existem maneiras melhores, principalmente aquelas que não estragam os nomes.
Arthur Yip
1
Esse é um bom ponto, acho que isso também está incorreto se você deseja preservar nomes em sua lista.
Will C
0

Que tal usar a map_função junto com um forloop? Aqui está a minha solução:

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

onde map_dfrconverta cada elemento da lista em data.frame e depois os une rbind.

No seu caso, acho que seria:

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