data.frame lignes dans une liste

123

J'ai un data.frame que je voudrais convertir en une liste par lignes, ce qui signifie que chaque ligne correspondrait à ses propres éléments de liste. En d'autres termes, je voudrais une liste qui est aussi longue que le data.frame a des lignes.

Jusqu'à présent, j'ai abordé ce problème de la manière suivante, mais je me demandais s'il y avait une meilleure façon d'aborder cela.

xy.df <- data.frame(x = runif(10),  y = runif(10))

# pre-allocate a list and fill it with a loop
xy.list <- vector("list", nrow(xy.df))
for (i in 1:nrow(xy.df)) {
    xy.list[[i]] <- xy.df[i,]
}
Roman Luštrik
la source

Réponses:

164

Comme ça:

xy.list <- split(xy.df, seq(nrow(xy.df)))

Et si vous voulez que les noms de fichiers xy.dfsoient les noms de la liste de sortie, vous pouvez faire:

xy.list <- setNames(split(xy.df, seq(nrow(xy.df))), rownames(xy.df))
flodel
la source
4
Notez qu'après avoir utilisé splitchaque élément a un type data.frame with 1 rows and N columnsau lieu delist of length N
Karol Daniluk
J'ajouterais seulement que si vous utilisez, splitvous devriez probablement le faire drop=Tsinon vos niveaux d'origine pour les facteurs ne baisseront pas
Denis
51

Eureka!

xy.list <- as.list(as.data.frame(t(xy.df)))
Roman Luštrik
la source
1
Voulez-vous montrer comment utiliser appliquer?
Roman Luštrik
3
unlist(apply(xy.df, 1, list), recursive = FALSE). Cependant, la solution de flodel est la plus efficace que d'utiliser applyou t.
Arun
11
Le problème ici est que tconvertit le data.fameen a de matrixsorte que les éléments de votre liste soient des vecteurs atomiques, et non une liste comme l'OP demandé. Ce n'est généralement pas un problème jusqu'à ce que votre xy.dfcontienne des types mixtes ...
Calimo
2
Si vous voulez faire une boucle sur les valeurs, je ne recommande pas apply. C'est en fait juste une boucle for implémentée dans R. lapplyeffectue la boucle en C, ce qui est beaucoup plus rapide. Ce format de liste de lignes est en fait préférable si vous faites beaucoup de boucles.
Liz Sander
1
Ajout d'un autre commentaire du futur, une applyversion est.mapply(data.frame, xy.df, NULL)
alexis_laz
15

Si vous voulez abuser complètement du data.frame (comme je le fais) et que vous aimez garder la fonctionnalité $, une façon est de diviser votre data.frame en data.frames sur une ligne rassemblés dans une liste:

> df = data.frame(x=c('a','b','c'), y=3:1)
> df
  x y
1 a 3
2 b 2
3 c 1

# 'convert' into a list of data.frames
ldf = lapply(as.list(1:dim(df)[1]), function(x) df[x[1],])

> ldf
[[1]]
x y
1 a 3    
[[2]]
x y
2 b 2
[[3]]
x y
3 c 1

# and the 'coolest'
> ldf[[2]]$y
[1] 2

Ce n'est pas seulement de la masturbation intellectuelle, mais permet de `` transformer '' le data.frame en une liste de ses lignes, en gardant l'indexation $ qui peut être utile pour une utilisation ultérieure avec lapply (en supposant que la fonction que vous passez à lapply utilise cette $ indexation)

Qiou Bi
la source
Comment les reconstituer? Transformer une liste de data.frames en un seul data.frame?
Aaron McDaid
4
@AaronMcDaid Vous pouvez utiliser do.call et rbind: df == do.call ("rbind", ldf)
random_forest_fanatic
@AaronMcDaid Ou data.table :: rbindlist (). Si votre trame de données d'origine était volumineuse, les gains de vitesse seront significatifs.
Empiromancer
8

Une solution plus moderne utilise uniquement purrr::transpose:

library(purrr)
iris[1:2,] %>% purrr::transpose()
#> [[1]]
#> [[1]]$Sepal.Length
#> [1] 5.1
#> 
#> [[1]]$Sepal.Width
#> [1] 3.5
#> 
#> [[1]]$Petal.Length
#> [1] 1.4
#> 
#> [[1]]$Petal.Width
#> [1] 0.2
#> 
#> [[1]]$Species
#> [1] 1
#> 
#> 
#> [[2]]
#> [[2]]$Sepal.Length
#> [1] 4.9
#> 
#> [[2]]$Sepal.Width
#> [1] 3
#> 
#> [[2]]$Petal.Length
#> [1] 1.4
#> 
#> [[2]]$Petal.Width
#> [1] 0.2
#> 
#> [[2]]$Species
#> [1] 1
Mike Stanley
la source
8

Je travaillais là-dessus aujourd'hui pour un data.frame (vraiment un data.table) avec des millions d'observations et 35 colonnes. Mon objectif était de renvoyer une liste de data.frames (data.tables) chacun avec une seule ligne. Autrement dit, je voulais diviser chaque ligne en un data.frame séparé et les stocker dans une liste.

Voici deux méthodes que j'ai proposées qui étaient environ 3 fois plus rapides que split(dat, seq_len(nrow(dat)))pour cet ensemble de données. Ci-dessous, je compare les trois méthodes sur un ensemble de données de 7500 lignes et 5 colonnes ( iris répété 50 fois).

library(data.table)
library(microbenchmark)

microbenchmark(
split={dat1 <- split(dat, seq_len(nrow(dat)))},
setDF={dat2 <- lapply(seq_len(nrow(dat)),
                  function(i) setDF(lapply(dat, "[", i)))},
attrDT={dat3 <- lapply(seq_len(nrow(dat)),
           function(i) {
             tmp <- lapply(dat, "[", i)
             attr(tmp, "class") <- c("data.table", "data.frame")
             setDF(tmp)
           })},
datList = {datL <- lapply(seq_len(nrow(dat)),
                          function(i) lapply(dat, "[", i))},
times=20
) 

Cela renvoie

Unit: milliseconds
       expr      min       lq     mean   median        uq       max neval
      split 861.8126 889.1849 973.5294 943.2288 1041.7206 1250.6150    20
      setDF 459.0577 466.3432 511.2656 482.1943  500.6958  750.6635    20
     attrDT 399.1999 409.6316 461.6454 422.5436  490.5620  717.6355    20
    datList 192.1175 201.9896 241.4726 208.4535  246.4299  411.2097    20

Bien que les différences ne soient pas aussi importantes que dans mon test précédent, la setDFméthode directe est nettement plus rapide à tous les niveaux de la distribution des exécutions avec max (setDF) <min (split) et la attrméthode est généralement plus de deux fois plus rapide.

Une quatrième méthode est le champion extrême, qui est un simple imbriqué lapply, renvoyant une liste imbriquée. Cette méthode illustre le coût de construction d'un data.frame à partir d'une liste. De plus, toutes les méthodes que j'ai essayées avec la data.framefonction étaient à peu près un ordre de grandeur plus lentes que les data.tabletechniques.

Les données

dat <- vector("list", 50)
for(i in 1:50) dat[[i]] <- iris
dat <- setDF(rbindlist(dat))
lmo
la source
6

Semble qu'une version actuelle du purrrpackage (0.2.2) est la solution la plus rapide:

by_row(x, function(v) list(v)[[1L]], .collate = "list")$.out

Comparons les solutions les plus intéressantes:

data("Batting", package = "Lahman")
x <- Batting[1:10000, 1:10]
library(benchr)
library(purrr)
benchmark(
    split = split(x, seq_len(.row_names_info(x, 2L))),
    mapply = .mapply(function(...) structure(list(...), class = "data.frame", row.names = 1L), x, NULL),
    purrr = by_row(x, function(v) list(v)[[1L]], .collate = "list")$.out
)

Résultats:

Benchmark summary:
Time units : milliseconds 
  expr n.eval   min  lw.qu median   mean  up.qu  max  total relative
 split    100 983.0 1060.0 1130.0 1130.0 1180.0 1450 113000     34.3
mapply    100 826.0  894.0  963.0  972.0 1030.0 1320  97200     29.3
 purrr    100  24.1   28.6   32.9   44.9   40.5  183   4490      1.0

Nous pouvons également obtenir le même résultat avec Rcpp:

#include <Rcpp.h>
using namespace Rcpp;

// [[Rcpp::export]]
List df2list(const DataFrame& x) {
    std::size_t nrows = x.rows();
    std::size_t ncols = x.cols();
    CharacterVector nms = x.names();
    List res(no_init(nrows));
    for (std::size_t i = 0; i < nrows; ++i) {
        List tmp(no_init(ncols));
        for (std::size_t j = 0; j < ncols; ++j) {
            switch(TYPEOF(x[j])) {
                case INTSXP: {
                    if (Rf_isFactor(x[j])) {
                        IntegerVector t = as<IntegerVector>(x[j]);
                        RObject t2 = wrap(t[i]);
                        t2.attr("class") = "factor";
                        t2.attr("levels") = t.attr("levels");
                        tmp[j] = t2;
                    } else {
                        tmp[j] = as<IntegerVector>(x[j])[i];
                    }
                    break;
                }
                case LGLSXP: {
                    tmp[j] = as<LogicalVector>(x[j])[i];
                    break;
                }
                case CPLXSXP: {
                    tmp[j] = as<ComplexVector>(x[j])[i];
                    break;
                }
                case REALSXP: {
                    tmp[j] = as<NumericVector>(x[j])[i];
                    break;
                }
                case STRSXP: {
                    tmp[j] = as<std::string>(as<CharacterVector>(x[j])[i]);
                    break;
                }
                default: stop("Unsupported type '%s'.", type2name(x));
            }
        }
        tmp.attr("class") = "data.frame";
        tmp.attr("row.names") = 1;
        tmp.attr("names") = nms;
        res[i] = tmp;
    }
    res.attr("names") = x.attr("row.names");
    return res;
}

Maintenant, comparez avec purrr:

benchmark(
    purrr = by_row(x, function(v) list(v)[[1L]], .collate = "list")$.out,
    rcpp = df2list(x)
)

Résultats:

Benchmark summary:
Time units : milliseconds 
 expr n.eval  min lw.qu median mean up.qu   max total relative
purrr    100 25.2  29.8   37.5 43.4  44.2 159.0  4340      1.1
 rcpp    100 19.0  27.9   34.3 35.8  37.2  93.8  3580      1.0
Artem Klevtsov
la source
l'analyse comparative sur un minuscule ensemble de données de 150 lignes n'a pas beaucoup de sens car personne ne remarquera de différence en microsecondes et cela ne se met pas à l'échelle
David Arenburg
4
by_row()est maintenant passé àlibrary(purrrlyr)
MrHopko
Et en plus d'être dans purrrlyr, il est sur le point d'être obsolète. Il existe maintenant d'autres méthodes combinant tidyr :: nest, dplyr :: mutate purrr :: map pour obtenir le même résultat
Mike Stanley
3

Quelques options supplémentaires:

Avec asplit

asplit(xy.df, 1)
#[[1]]
#     x      y 
#0.1137 0.6936 

#[[2]]
#     x      y 
#0.6223 0.5450 

#[[3]]
#     x      y 
#0.6093 0.2827 
#....

Avec splitetrow

split(xy.df, row(xy.df)[, 1])

#$`1`
#       x      y
#1 0.1137 0.6936

#$`2`
#       x     y
#2 0.6223 0.545

#$`3`
#       x      y
#3 0.6093 0.2827
#....

Les données

set.seed(1234)
xy.df <- data.frame(x = runif(10),  y = runif(10))
Ronak Shah
la source
2

Le meilleur moyen pour moi était:

Exemple de données:

Var1<-c("X1",X2","X3")
Var2<-c("X1",X2","X3")
Var3<-c("X1",X2","X3")

Data<-cbind(Var1,Var2,Var3)

ID    Var1   Var2  Var3 
1      X1     X2    X3
2      X4     X5    X6
3      X7     X8    X9

Nous appelons la BBmiscbibliothèque

library(BBmisc)

data$lists<-convertRowsToList(data[,2:4])

Et le résultat sera:

ID    Var1   Var2  Var3  lists
1      X1     X2    X3   list("X1", "X2", X3") 
2      X4     X5    X6   list("X4","X5", "X6") 
3      X7     X8    X9   list("X7,"X8,"X9) 
Cro-Magnon
la source
1

Une autre manière est de convertir le df en matrice, puis d'appliquer la lappyfonction list apply dessus:ldf <- lapply(as.matrix(myDF), function(x)x)

user3553260
la source
1

Une autre alternative utilisant library(purrr)(qui semble être un peu plus rapide sur les gros data.frames)

flatten(by_row(xy.df, ..f = function(x) flatten_chr(x), .labels = FALSE))
MrHopko
la source
3
`by_row ()` est maintenant déplacé vers `library (purrrlyr)`
MrHopko
1

Comme @flodel l'a écrit: Cela convertit votre dataframe en une liste qui a le même nombre d'éléments que le nombre de lignes dans dataframe:

NewList <- split(df, f = seq(nrow(df)))

Vous pouvez en outre ajouter une fonction pour sélectionner uniquement les colonnes qui ne sont pas NA dans chaque élément de la liste:

NewList2 <- lapply(NewList, function(x) x[,!is.na(x)])
michal
la source
0

La by_rowfonction du purrrlyrpackage le fera pour vous.

Cet exemple montre

myfn <- function(row) {
  #row is a tibble with one row, and the same number of columns as the original df
  l <- as.list(row)
  return(l)
}

list_of_lists <- purrrlyr::by_row(df, myfn, .labels=FALSE)$.out

Par défaut, la valeur renvoyée par myfnest placée dans une nouvelle colonne de liste dans le df appelé .out. Le $.outà la fin de l'instruction ci-dessus sélectionne immédiatement cette colonne, renvoyant une liste de listes.

RobinL
la source