1
votes

Comment convertir une grande liste avec des vecteurs de longueur différente en dataframe?

J'ai une grande liste de plus de 30000 éléments. Il existe des vecteurs de longueurs différentes et je souhaite convertir la liste en un dataframe, où chaque vecteur représente une ligne et ses valeurs sont réparties sur plusieurs colonnes. Voici un exemple simulé de la liste:

#  [,1]  [,2] [,3] [,4] [,5] [,6]
#a    1    2    3    4    5    6
#b    7    8    9   NA   NA   NA
#c   10   11   NA   NA   NA   NA

Ma sortie souhaitée ressemble à ceci:

lst <- list(a = c(1,2,4,5,6), c = c(7,8,9), c = c(10,11))


3 commentaires

C'est bien de voir un exemple court et précis reproductible avec la sortie souhaitée!


J'ai l'impression que vous voulez vraiment une matrice plutôt qu'un data.frame - difficile à dire avec certitude sans en savoir plus sur ce que vous faites, mais gardez à l'esprit que, même dans R, les données tabulaires ne doivent pas nécessairement être dans des données frame s'il n'est pas orienté colonne.


J'ai ajouté des horaires


4 Réponses :


1
votes

L'astuce consiste à créer des vecteurs de longueur égale. Il semble également que vous souhaitiez disposer d'une matrice en sortie.

lst <- list(a = c(1,2,4,5,6), c = c(7,8,9), c = c(10,11))

convert <-function(lst){
  Reduce(function(x,y){
    n <- max(length(x), length(y))
    length(x) <- n
    length(y) <- n
    rbind(x,y,deparse.level = 0)
  },
  lst)
}

convert2 <- function(lst){
  t(sapply(lst, "length<-", max(lengths(lst))))
}

convert3 <- function(lst){
t(as.data.frame(lapply(lst, "length<-", max(lengths(lst)))))
}

microbenchmark::microbenchmark(convert(lst),
                               convert2(lst),
                               convert3(lst))

#Unit: microseconds
#          expr     min       lq      mean   median      uq      max neval
#  convert(lst)  41.962  50.0725 106.47314  62.2375  68.408 4392.895   100
# convert2(lst)  28.209  33.6755  69.93855  40.7280  45.136 2298.002   100
# convert3(lst) 292.673 306.6005 381.59504 319.1180 333.399 2887.929   100

Sortie

# [,1] [,2] [,3] [,4] [,5]
# [1,]    1    2    4    5    6
# [2,]    7    8    9   NA   NA
# [3,]   10   11   NA   NA   NA

Vous pouvez réinitialiser les noms des lignes à ce stade.

MISE À JOUR Horaires pour ceux qui sont intéressés:

Reduce(function(x,y){
  n <- max(length(x), length(y))
  length(x) <- n
  length(y) <- n
  rbind(x,y,deparse.level = 0)
},
       list(a = c(1,2,4,5,6), c = c(7,8,9), c = c(10,11)))


0 commentaires

2
votes

Vous pouvez faire:

t(sapply(lst, "length<-", max(lengths(lst))))

#  [,1] [,2] [,3] [,4] [,5]
#a    1    2    4    5    6
#c    7    8    9   NA   NA
#c   10   11   NA   NA   NA

Ou comme @Andrew l'a souligné, vous pouvez faire:

t(as.data.frame(lapply(lst, "length<-", max(lengths(lst)))))

#    [,1] [,2] [,3] [,4] [,5]
#a      1    2    4    5    6
#c      7    8    9   NA   NA
#c.1   10   11   NA   NA   NA


2 commentaires

Pas besoin d'envelopper as.data.frame si vous le transposez après. Vous pouvez utiliser sapply en considérant que t se convertira de toute façon en matrice.


renvoie l'erreur: Erreur dans (function (..., row.names = NULL, check.rows = FALSE, check.names = TRUE,: les arguments impliquent un nombre de lignes différent: 1, 0



2
votes

Voici une option R de base:

# Create a vector for number of times an NA needs to be padded
na_nums <- max(lengths(lst)) - lengths(lst)

# Transpose results after patting NA's using mapply
t(mapply(c, lst, sapply(na_nums, rep, x = NA)))
  [,1] [,2] [,3] [,4] [,5]
a    1    2    4    5    6
c    7    8    9   NA   NA
c   10   11   NA   NA   NA


0 commentaires

2
votes

C'était ma première impulsion.

lst <- list(a = c(1,2,4,5,6), c = c(7,8,9), c = c(10,11))
# build out a list of 30,000 elements.
lst <- lst[sample(1:3, 30000, replace = TRUE)]

library(microbenchmark)
microbenchmark(
  benjamin = {
    max_len <- max(vapply(lst, 
                          FUN = length, 
                          FUN.VALUE = numeric(1)))

    lst <- lapply(lst, 
                  function(x, max_len) c(x, rep(NA, max_len - length(x))), 
                  max_len)

    # Form a matrix
    do.call("rbind", lst)
  }, 
  slava = {
    Reduce(function(x,y){
      n <- max(length(x), length(y))
      length(x) <- n
      length(y) <- n
      rbind(x,y,deparse.level = 0)
    },
    lst)
  }, 
  andrew = {
    na_nums <- max(lengths(lst)) - lengths(lst)

    # Transpose results after patting NA's using mapply
    t(mapply(c, lst, sapply(na_nums, rep, x = NA)))
  }, 
  matt = {
    t(as.data.frame(lapply(lst, "length<-", max(lengths(lst)))))
  }
)

Unit: milliseconds
     expr         min          lq       mean      median          uq        max neval
 benjamin    77.08337    91.42793   117.9376   106.97656   122.53898   191.6612     5
    slava 32383.10840 32962.57589 32976.6662 33071.40314 33180.70634 33285.5372     5
   andrew    60.91803    66.74401    87.1645    71.92043    77.78805   158.4520     5
     matt  1685.09158  1702.19796  1759.2741  1737.01949  1760.86237  1911.1993     5

C'est un peu verbeux, et certaines des autres solutions sont plutôt élégantes. Puisque vous dites que votre liste contient plus de 30 000 éléments, j'étais curieux de savoir comment ils fonctionneraient sur une liste de 30 000 éléments.

Si c'est quelque chose que vous devez faire souvent, vous voudrez peut-être adopter l'approche d'Andrew .

max_len <- max(vapply(lst, 
                      FUN = length, 
                      FUN.VALUE = numeric(1)))

lst <- lapply(lst, 
              function(x, max_len) c(x, rep(NA, max_len - length(x))), 
              max_len)

# Form a matrix
do.call("rbind", lst)


1 commentaires

Merci pour les horaires avec un plus grand ensemble de données.