Convertir une liste en un bloc de données

513

J'ai une liste imbriquée de données. Sa longueur est de 132 et chaque élément est une liste de longueur 20. Existe-t-il un moyen rapide de convertir cette structure en un bloc de données qui comprend 132 lignes et 20 colonnes de données?

Voici quelques exemples de données avec lesquelles travailler:

l <- replicate(
  132,
  list(sample(letters, 20)),
  simplify = FALSE
)
Btibert3
la source
Vous voulez donc que chaque élément de la liste soit une ligne de données dans votre data.frame?
Joshua Ulrich
2
@RichieCotton Ce n'est pas un bon exemple. "chaque élément est une liste de longueur 20" et vous avez obtenu chaque élément est une liste d'un élément de vecteur de longueur 20.
Marek
1
Tard dans la soirée, mais je n'ai vu personne mentionner cela , ce que j'ai trouvé très pratique (pour ce que je cherchais à faire).
mflo-ByeSE
1
mentionné dans r-bloggers.com/converting-a-list-to-a-data-frame
千 木 郷

Réponses:

390

En supposant que votre liste de listes s'appelle l:

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

Ce qui précède convertira toutes les colonnes de caractères en facteurs, pour éviter cela, vous pouvez ajouter un paramètre à l'appel data.frame ():

df <- data.frame(matrix(unlist(l), nrow=132, byrow=T),stringsAsFactors=FALSE)
Nico
la source
109
Attention ici si vos données ne sont pas toutes du même type. Le passage à travers une matrice signifie que toutes les données seront contraintes dans un type commun. C'est-à-dire que si vous avez une colonne de données de caractères et une colonne de données numériques, les données numériques seront contraintes à la chaîne par matrice (), puis les deux à factoriser par data.frame ().
Ian Sudbery
Quelle est la meilleure façon de procéder lorsque la liste contient des valeurs manquantes ou d'inclure NA dans le bloc de données?
Dave
1
@Dave: Fonctionne pour moi ... voir ici r-fiddle.org/#/fiddle?id=y8DW7lqL&version=3
nico
4
Faites également attention si vous avez un type de données de caractère - data.frame le convertira en facteurs.
Alex Brown
4
@nico Existe-t-il un moyen de conserver les noms des éléments de la liste sous forme de noms de colonnes ou de noms de domaine dans le df?
N.Varela
472

Avec rbind

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

Edit: retour data.framede listla version précédente de 's au lieu de vecteurs (comme l'a souligné @IanSudbery dans les commentaires).

Marek
la source
5
Pourquoi cela fonctionne-t-il mais rbind(your_list)renvoie une matrice de liste 1x32?
eykanal
26
@eykanal do.callpasse des éléments de your_listcomme arguments à rbind. C'est l'équivalent de rbind(your_list[[1]], your_list[[2]], your_list[[3]], ....., your_list[[length of your_list]]).
Marek
2
Cette méthode souffre de la situation nulle.
Frank Wang
3
@FrankWANG Mais cette méthode n'est pas conçue pour une situation nulle. Il doit your_listcontenir des vecteurs de taille égale. NULLa une longueur de 0, il devrait donc échouer.
Marek
12
Cette méthode semble renvoyer l'objet correct, mais en inspectant l'objet, vous constaterez que les colonnes sont des listes plutôt que des vecteurs, ce qui peut entraîner des problèmes sur toute la ligne si vous ne vous y attendez pas.
Ian Sudbery
135

Vous pouvez utiliser le plyrpackage. Par exemple, une liste imbriquée du formulaire

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

a maintenant une longueur de 4 et chaque liste lcontient une autre liste de la longueur 3. Maintenant, vous pouvez exécuter

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

et devrait obtenir le même résultat que dans la réponse @Marek et @nico.

mropa
la source
8
Très bonne réponse. Je pourrais vous expliquer un peu comment ça marche? Il renvoie simplement un bloc de données pour chaque entrée de liste?
Michael Barton
13
À mon humble avis la meilleure réponse. Il renvoie un honnête data.frame. Tous les types de données (caractère, numérique, etc.) sont correctement transformés. Si la liste a différents types de données, ils seront tous transformés en caractère avec matrixapproche.
Roah
1
l'échantillon fourni ici n'est pas celui fourni par la question. le résultat de cette réponse sur l'ensemble de données d'origine est incorrect.
MySchizoBuddy
Fonctionne très bien pour moi! Et les noms des colonnes dans la trame de données résultante sont définis! Tx
bAN
Est plyr multicœur? Ou existe-t-il une version lapply à utiliser avec mclapply?
Garglesoap
103

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

sapplyle convertit en matrice. data.frameconvertit la matrice en une trame de données.

Alex Brown
la source
19
meilleure réponse de loin! Aucune des autres solutions n'obtient les types / noms de colonne corrects. MERCI!
d_a_c321
1
Quel rôle avez-vous l'intention cde jouer ici, une instance des données de la liste? Oh attendez, c pour la fonction de concaténation non? Se confondre avec l'utilisation par @ mnel de c. Je suis également d'accord avec @dchandler, obtenir les bons noms de colonne était un besoin précieux dans mon cas d'utilisation. Solution brillante.
jxramos
ce droit - fonction c standard; de ?c:Combine Values into a Vector or List
Alex Brown
1
ne fonctionne pas avec les exemples de données fournis dans la question
MySchizoBuddy
3
Cela ne génère-t-il pas un data.frame de listes?
Carl
69

supposons que votre liste est appelée L,

data.frame(Reduce(rbind, L))
jdeng
la source
2
Joli! Il y a une différence avec la solution de @Alex Brown par rapport à la vôtre, votre itinéraire a généré le message d'avertissement suivant pour une raison quelconque: `Message d'avertissement: dans data.row.names (row.names, rowi, i): certains row.names dupliqués : 3,4 -> row.names NOT used '
jxramos
Très bien!! A travaillé pour moi ici: stackoverflow.com/questions/32996321/…
Anastasia Pupynina
2
Fonctionne bien sauf si la liste ne contient qu'un seul élément: data.frame(Reduce(rbind, list(c('col1','col2'))))produit un bloc de données avec 2 lignes, 1 colonne (je m'attendais à 1 ligne 2 colonnes)
The Red Pea
61

Le paquet data.tablea la fonction rbindlistqui est une implémentation ultra-rapide de do.call(rbind, list(...)).

Il peut prendre une liste de lists, data.framesou data.tables en entrée.

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)

Cela renvoie un data.tablehérite de data.frame.

Si vous voulez vraiment revenir à une utilisation data.frameas.data.frame(DT)

mnel
la source
Concernant la dernière ligne, setDFpermet désormais de revenir à data.frame par référence.
Frank
1
Pour ma liste avec 30k articles, rbindlist a fonctionné beaucoup plus rapidement que ldply
tallharish
35

Le tibblepackage a une fonction enframe()qui résout ce problème en contraignant des listobjets imbriqués à des objets imbriqués tibble(trame de données "bien rangée"). Voici un bref exemple de R pour 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]>

Étant donné que vous avez plusieurs nids dans votre liste, lvous pouvez utiliser le unlist(recursive = FALSE)pour supprimer l'imbrication inutile pour obtenir une seule liste hiérarchique, puis passer à enframe(). J'utilise tidyr::unnest()pour déloger la sortie dans un bloc de données "rangé" à un seul niveau, qui a vos deux colonnes (une pour le groupe nameet une pour les observations avec les groupes value). Si vous voulez des colonnes qui s'étendent, vous pouvez ajouter une colonne en utilisant add_column()qui répète simplement l'ordre des valeurs 132 fois. Ensuite, juste spread()les valeurs.


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
la source
Citant l'OP: "Existe-t-il un moyen rapide de convertir cette structure en un bloc de données qui a 132 lignes et 20 colonnes de données?" Alors peut-être que vous avez besoin d'une étape de propagation ou quelque chose.
Frank
1
Ah oui, il doit juste y avoir une colonne d'index qui peut être répartie. Je mettrai à jour sous peu.
Matt Dancho
17

Selon la structure de vos listes, certaines tidyverseoptions fonctionnent bien avec des listes de longueurs inégales:

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

Vous pouvez également mélanger des vecteurs et des trames de données:

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
la source
Cette fonction dplyr :: bind_rows fonctionne bien, même avec du mal à travailler avec des listes provenant de JSON. De JSON à une trame de données étonnamment propre. Agréable.
GGAnderson
@sbha J'ai essayé d'utiliser df <- purrr :: map_df (l, ~ .x) mais il semble que cela ne fonctionne pas, le message d'erreur que j'ai est Erreur: la colonne X2ne peut pas être convertie d'un entier en caractère
Jolin
16

Reshape2 donne le même résultat que l'exemple plyr ci-dessus:

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)

rendements:

  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 vous n'aviez presque plus de pixels, vous pouvez tout faire en 1 ligne avec refonte ().

Jack Ryan
la source
12

Cette méthode utilise un tidyversepackage ( purrr ).

La liste:

x <- as.list(mtcars)

Le convertir en un bloc de données (un tibbleplus spécifiquement):

library(purrr)
map_df(x, ~.x)
SavedByJESUS
la source
10

Étendre la réponse de @ Marek: si vous voulez éviter que les chaînes soient transformées en facteurs et que l'efficacité ne soit pas un problème, essayez

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

Pour le cas général des listes profondément imbriquées avec 3 niveaux ou plus comme ceux obtenus à partir d'un JSON imbriqué:

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

considérez d'abord l'approche de melt()convertir la liste imbriquée dans un format grand:

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

suivi par dcast()puis de nouveau large dans un ensemble de données bien rangé où chaque variable forme une colonne et chaque observation forme une ligne:

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
la source
9

Plus de réponses, ainsi que des délais dans la réponse à cette question: Quelle est la façon la plus efficace de caster une liste en tant que bloc de données?

Le moyen le plus rapide, qui ne produit pas une trame de données avec des listes plutôt que des vecteurs pour les colonnes semble être (d'après la réponse 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
la source
8

Parfois, vos données peuvent être une liste de listes de vecteurs de même longueur.

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

(Les vecteurs internes pourraient également être des listes, mais je simplifie pour faciliter la lecture).

Ensuite, vous pouvez apporter la modification suivante. N'oubliez pas que vous pouvez annuler la liste d'un niveau à la fois:

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

Utilisez maintenant votre méthode préférée mentionnée dans les autres réponses:

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
la source
4

C'est ce qui a finalement fonctionné pour moi:

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

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

Pour une solution parallèle (multicœur, multisession, etc.) utilisant une purrrfamille de solutions, utilisez:

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

l est la liste.

Pour comparer les plus efficaces, plan()vous pouvez utiliser:

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

La commande simple suivante a fonctionné pour moi:

myDf <- as.data.frame(myList)

Référence ( réponse 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"

Mais cela échouera s'il n'est pas évident de convertir la liste en un bloc de données:

> 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

Remarque : La réponse est vers le titre de la question et peut ignorer certains détails de la question

Ahmad
la source
A noter que sur l'apport de la question cette seule sorte de travaux. OP demande 132 lignes et 20 colonnes, mais cela donne 20 lignes et 132 colonnes.
Gregor Thomas
Pour votre exemple avec une entrée de longueur différente où elle échoue, ce n'est pas clair quel serait le résultat souhaité ...
Gregor Thomas
@Gregor True, mais le titre de la question est "R - list to data frame". De nombreux visiteurs de la question et ceux qui l'ont votée n'ont pas exactement le problème de l'OP. Sur la base du titre de la question, ils recherchent simplement un moyen de convertir la liste en bloc de données. J'ai moi-même eu le même problème et la solution que j'ai publiée a résolu mon problème
Ahmad
Ouais, juste en notant. Pas de vote en aval. Il pourrait être intéressant de noter dans la réponse qu'il fait quelque chose de similaire - mais distinctement différent de - à peu près toutes les autres réponses.
Gregor Thomas
1

Un moyen court (mais peut-être pas le plus rapide) de le faire serait d'utiliser la base r, car une trame de données n'est qu'une liste de vecteurs de longueur égale . Ainsi, la conversion entre votre liste d'entrées et un data.frame 30 x 132 serait:

df <- data.frame(l)

De là, nous pouvons le transposer dans une matrice de 132 x 30, et le reconvertir en une trame de données:

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

En une ligne:

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

Les noms d'utilisateur seront assez ennuyeux à regarder, mais vous pouvez toujours renommer ceux avec

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

Will C
la source
2
Pourquoi cela a-t-il été rejeté? Je voudrais savoir pour ne pas continuer à diffuser de la désinformation.
Will C
Je l'ai définitivement fait auparavant, en utilisant une combinaison de data.frame et t! Je suppose que les personnes qui ont voté en contrebas ont le sentiment qu'il existe de meilleures façons, en particulier celles qui ne gâchent pas les noms.
Arthur Yip
1
C'est un bon point, je suppose que c'est également incorrect si vous souhaitez conserver les noms dans votre liste.
Will C
0

Que diriez-vous d'utiliser la map_fonction avec une forboucle? Voici ma solution:

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

map_dfrconvertir chacun des éléments de la liste en un data.frame puisrbind complètement.

Dans votre cas, je suppose que ce serait:

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