web-dev-qa-db-ger.com

daten.Frame-Zeilen zu einer Liste

Ich habe ein data.frame, das ich nach Zeilen in eine Liste konvertieren möchte, was bedeutet, dass jede Zeile ihren eigenen Listenelementen entsprechen würde. Mit anderen Worten, ich möchte eine Liste, die so lang ist, wie der data.frame Zeilen hat.

Bisher habe ich dieses Problem auf folgende Weise angegangen, aber ich habe mich gefragt, ob es einen besseren Weg gibt, dieses Problem anzugehen.

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

So was:

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

Und wenn Sie möchten, dass die Namen von xy.df die Namen der Ausgabeliste sind, können Sie Folgendes tun:

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

Wenn Sie data.frame (wie ich) vollständig missbrauchen möchten und die $ -Funktion beibehalten möchten, können Sie data.frame in einzeilige data.frames aufteilen, die in einer Liste zusammengefasst sind:

> 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

Es ist nicht nur eine intellektuelle Masturbation, sondern erlaubt es, den data.frame in eine Liste seiner Zeilen 'umzuwandeln', wobei die $ indexierung beibehalten wird, was für die weitere Verwendung mit lapply nützlich sein kann (vorausgesetzt, die Funktion, die Sie an lapply übergeben, verwendet diese $ indexierung)

12
Qiou Bi

Daran habe ich heute für einen data.frame (eigentlich eine data.table) mit Millionen von Beobachtungen und 35 Spalten gearbeitet. Mein Ziel war es, eine Liste von data.frames (data.tables) mit jeweils einer einzelnen Zeile zurückzugeben. Das heißt, ich wollte jede Zeile in einen separaten data.frame aufteilen und diese in einer Liste speichern.

Hier sind zwei Methoden, die ich mir ausgedacht hatte. Sie waren ungefähr drei Mal schneller als split(dat, seq_len(nrow(dat))) für diesen Datensatz. Nachfolgend stelle ich die drei Methoden für einen Datensatz mit 7500 Zeilen und 5 Spalten fest (iris, der 50-mal wiederholt wird).

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
) 

Das kehrt zurück

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

Während die Unterschiede nicht so groß sind wie in meinem vorherigen Test, ist die gerade setDF-Methode auf allen Ebenen der Verteilung von Durchläufen mit max (setDF) <min (split) signifikant schneller und die attr-Methode ist normalerweise mehr als doppelt so schnell.

Eine vierte Methode ist der extreme Champion, eine einfache verschachtelte lapply, die eine verschachtelte Liste zurückgibt. Diese Methode zeigt beispielhaft die Kosten für das Erstellen eines data.frame aus einer Liste. Darüber hinaus waren alle Methoden, die ich mit der data.frame-Funktion ausprobierte, ungefähr eine Größenordnung langsamer als die data.table-Techniken.

Daten

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

Scheint eine aktuelle Version des purrr (0.2.2) Pakets ist die schnellste Lösung:

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

Vergleichen wir die interessantesten Lösungen:

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
)

Ergebnisse:

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

Auch mit Rcpp können wir dasselbe Ergebnis erzielen:

#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;
}

Jetzt mit purrr vergleichen:

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

Ergebnisse:

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

Eine modernere Lösung verwendet nur 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

Eine andere Alternative mit library(purrr) (die bei großen Daten.Frames etwas schneller zu sein scheint)

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

Der beste Weg für mich war:

Beispieldaten:

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

Wir nennen die Bibliothek BBmisc

library(BBmisc)

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

Und das Ergebnis wird sein:

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

Eine Alternative ist, die Datenbank in eine Matrix umzuwandeln und dann die Liste lappy-Funktion darauf anzuwenden: ldf <- lapply(as.matrix(myDF), function(x)x)

1
user3553260

Die by_row-Funktion aus dem purrrlyr-Paket übernimmt dies für Sie. 

Dieses Beispiel demonstriert

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

Standardmäßig wird der von myfn zurückgegebene Wert in eine neue Listenspalte in der Datenbank mit dem Namen .out eingefügt. Der $.out am Ende der obigen Anweisung wählt diese Spalte sofort aus und gibt eine Liste mit Listen zurück.

0
RobinL

Wie @flodel schrieb: Dies konvertiert Ihren Datenrahmen in eine Liste, die die gleiche Anzahl von Elementen wie die Anzahl von Zeilen in Datenrahmen hat:

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

Sie können auch eine Funktion hinzufügen, um nur die Spalten auszuwählen, die nicht NA in jedem Element der Liste sind:

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