data.frame Zeilen zu einer Liste

123

Ich habe einen data.frame, den ich zeilenweise in eine Liste konvertieren möchte, was bedeutet, dass jede Zeile ihren eigenen Listenelementen entspricht. Mit anderen Worten, ich möchte eine Liste, die so lang ist, wie der data.frame Zeilen enthält.

Bisher habe ich dieses Problem folgendermaßen angegangen, aber ich habe mich gefragt, ob es einen besseren Weg gibt, dies zu erreichen.

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
quelle

Antworten:

163

So was:

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

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

xy.list <- setNames(split(xy.df, seq(nrow(xy.df))), rownames(xy.df))
flodel
quelle
4
Beachten Sie, dass nach der Verwendung splitjedes Elements Typ data.frame with 1 rows and N columnsanstelle vonlist of length N
Karol Daniluk
Ich würde nur hinzufügen, dass wenn Sie verwenden, splitSie wahrscheinlich tun sollten, drop=Tsonst werden Ihre ursprünglichen Werte für Faktoren nicht fallen
Denis
51

Eureka!

xy.list <- as.list(as.data.frame(t(xy.df)))
Roman Luštrik
quelle
1
Möchten Sie zeigen, wie man sich bewirbt?
Roman Luštrik
3
unlist(apply(xy.df, 1, list), recursive = FALSE). Die Lösung von flodel ist jedoch effizienter als die Verwendung von applyoder t.
Arun
11
Das Problem hierbei ist, dass tdas data.famein a konvertiert wird , matrixsodass die Elemente in Ihrer Liste Atomvektoren sind und nicht wie vom OP angefordert aufgelistet werden. Es ist normalerweise kein Problem, bis Sie xy.dfgemischte Typen enthalten ...
Calimo
2
Wenn Sie die Werte durchlaufen möchten, empfehle ich nicht apply. Es ist eigentlich nur eine in R implementierte for-Schleife, lapplydie die Schleife in C ausführt, was erheblich schneller ist. Dieses Zeilenlistenformat ist eigentlich vorzuziehen, wenn Sie viel schleifen.
Liz Sander
1
Ein weiterer Kommentar aus der Zukunft, eine applyVersion ist.mapply(data.frame, xy.df, NULL)
alexis_laz
15

Wenn Sie den data.frame (wie ich) vollständig missbrauchen und die $ -Funktionalität beibehalten möchten, können Sie Ihren 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 intellektuelle Masturbation, sondern ermöglicht es auch, den data.frame in eine Liste seiner Zeilen zu "transformieren", wobei die $ indexation beibehalten wird, die für die weitere Verwendung mit lapply nützlich sein kann (vorausgesetzt, die Funktion, die Sie an lapply übergeben, verwendet diese $ indexation).

Qiou Bi
quelle
Wie setzen wir sie wieder zusammen? Eine Liste von data.frames in eine einzige data.frameverwandeln?
Aaron McDaid
4
@AaronMcDaid Sie können do.call und rbind verwenden: df == do.call ("rbind", ldf)
random_forest_fanatic
@AaronMcDaid Oder data.table :: rbindlist (). Wenn Ihr ursprünglicher Datenrahmen groß war, sind die Geschwindigkeitsgewinne erheblich.
Empiromancer
8

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
Mike Stanley
quelle
8

Ich habe heute daran für einen data.frame (wirklich 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 habe und die ungefähr dreimal schneller waren als split(dat, seq_len(nrow(dat)))für diesen Datensatz. Im Folgenden vergleiche ich die drei Methoden mit einem Datensatz mit 7500 Zeilen und 5 Spalten ( Iris 50-mal wiederholt).

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
) 

Dies 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 setDFMethode auf allen Ebenen der Verteilung von Läufen mit max (setDF) <min (split) signifikant schneller und die attrMethode ist normalerweise mehr als doppelt so schnell.

Eine vierte Methode ist der extreme Champion, der einfach verschachtelt lapplyist und eine verschachtelte Liste zurückgibt. Diese Methode veranschaulicht die Kosten für die Erstellung eines data.frame aus einer Liste. Außerdem waren alle Methoden, die ich mit der data.frameFunktion ausprobierte, ungefähr eine Größenordnung langsamer als die data.tableTechniken.

Daten

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

Eine aktuelle Version des purrrPakets (0.2.2) scheint die schnellste Lösung zu sein:

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

Das gleiche Ergebnis erzielen wir auch mit 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;
}

Vergleichen Sie jetzt mit purrr:

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
Artem Klevtsov
quelle
Das Benchmarking eines winzigen Datensatzes mit 150 Zeilen macht wenig Sinn, da niemand einen Unterschied in Mikrosekunden bemerkt und es nicht skaliert
David Arenburg
4
by_row()ist jetzt umgezogen zulibrary(purrrlyr)
MrHopko
Und zusätzlich zu Purrrlyr ist es im Begriff, veraltet zu sein. Es gibt jetzt andere Methoden, die tidyr :: nest, dplyr :: mutate purrr :: map kombinieren, um das gleiche Ergebnis zu erzielen
Mike Stanley
3

Ein paar weitere Optionen:

Mit 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 
#....

Mit splitundrow

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
#....

Daten

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

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 rufen die BBmiscBibliothek an

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) 
Cro-Magnon
quelle
1

Eine alternative Möglichkeit besteht darin, den df in eine Matrix umzuwandeln und dann die Listenanwendungsfunktion darauf anzuwenden lappy:ldf <- lapply(as.matrix(myDF), function(x)x)

user3553260
quelle
1

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

flatten(by_row(xy.df, ..f = function(x) flatten_chr(x), .labels = FALSE))
MrHopko
quelle
3
`by_row ()` ist jetzt in `library (purrrlyr)`
umgezogen
1

Wie @flodel schrieb: Dies konvertiert Ihren Datenrahmen in eine Liste, die die gleiche Anzahl von Elementen wie die Anzahl der Zeilen im Datenrahmen enthält:

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

Sie können zusätzlich 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)])
michal
quelle
0

Die by_rowFunktion aus dem purrrlyrPaket erledigt dies für Sie.

Dieses Beispiel zeigt

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 zurückgegebene Wert von myfnin eine neue Listenspalte in der aufgerufenen df eingefügt .out. Am $.outEnde der obigen Anweisung wird diese Spalte sofort ausgewählt und eine Liste mit Listen zurückgegeben.

RobinL
quelle