web-dev-qa-db-fra.com

data.frame rangs à une liste

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

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

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,]
}
92
Roman Luštrik

Comme ça:

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

Et si vous voulez que les noms de domaine de xy.df soient les noms de la liste de sortie, vous pouvez faire:

xy.list <- setNames(split(xy.df, seq(nrow(xy.df))), rownames(xy.df))
122
flodel

Eureka!

xy.list <- as.list(as.data.frame(t(xy.df)))
44
Roman Luštrik

Si vous voulez abuser complètement du nom data.frame (comme je le fais) et que vous souhaitez conserver la fonctionnalité $, vous pouvez par exemple diviser data.frame en une seule ligne data.frames regroupé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, il permet également de "transformer" le data.frame en une liste de ses lignes, en conservant 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)

12
Qiou Bi

Je travaillais sur cela 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), chacune avec une seule ligne. C'est-à-dire que je souhaitais scinder chaque ligne dans un data.frame distinct et les stocker dans une liste.

Voici deux méthodes que j'ai trouvées et 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, 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 retourne

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 grandes que dans mon test précédent, la méthode straight setDF est nettement plus rapide à tous les niveaux de la distribution des exécutions avec max (setDF) <min (split) et la méthode attr est généralement plus de deux fois plus rapide.

Une quatrième méthode est le champion extrême, qui consiste en une simple lapply imbriquée, 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 fonction data.frame étaient à peu près d'un ordre de grandeur plus lent que les techniques data.table.

Les données

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

Semble qu'une version actuelle du paquet purrr (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 aussi 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
6
Artem Klevtsov

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
2
Mike Stanley

Une autre alternative en utilisant library(purrr) (qui semble être un peu plus rapide sur les grandes données.)

flatten(by_row(xy.df, ..f = function(x) flatten_chr(x), .labels = FALSE))
2
MrHopko

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 bibliothèque BBmisc

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) 
2
Cro-Magnon

Une autre méthode consiste à convertir le fichier df en une matrice, puis à appliquer la liste apply lappy function: ldf <- lapply(as.matrix(myDF), function(x)x).

1
user3553260

La fonction by_row du package purrrlyr 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 de myfn est placée dans une nouvelle colonne list dans le fichier df appelé .out. Le $.out à la fin de l'instruction ci-dessus sélectionne immédiatement cette colonne et renvoie une liste de listes.

0
RobinL

Comme @flodel a écrit: Ceci 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 également ajouter une fonction à 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)])
0
michal