data.frame righe in un elenco


123

Ho un data.frame che vorrei convertire in un elenco per righe, il che significa che ogni riga corrisponderebbe ai propri elementi dell'elenco. In altre parole, vorrei una lista lunga quanto data.frame ha righe.

Finora ho affrontato questo problema nel modo seguente, ma mi chiedevo se ci fosse un modo migliore per affrontarlo.

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,]
}

Risposte:


164

Come questo:

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

E se vuoi che i rownames di xy.dfsiano i nomi dell'elenco di output, puoi fare:

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

4
Nota che, dopo aver usato splitogni elemento, ha il tipo data.frame with 1 rows and N columnsinvece dilist of length N
Karol Daniluk

Vorrei solo aggiungere che se usi splitprobabilmente dovresti fare drop=Taltrimenti i tuoi livelli originali per i fattori non scenderanno
Denis

51

Eureka!

xy.list <- as.list(as.data.frame(t(xy.df)))

1
Ti interessa dimostrare come usare applica?
Roman Luštrik

3
unlist(apply(xy.df, 1, list), recursive = FALSE). Tuttavia la soluzione di flodel è la più efficiente rispetto all'utilizzo di applyo t.
Arun

11
Il problema qui è che tconverte il data.famein a in matrixmodo che gli elementi nella tua lista siano vettori atomici, non lista come l'OP richiesto. Di solito non è un problema fino a quando non xy.dfcontiene tipi misti ...
Calimo

2
Se vuoi scorrere i valori, non lo consiglio apply. In realtà è solo un ciclo for implementato in R. lapplyesegue il loop in C, che è significativamente più veloce. Questo formato di elenco di righe è in realtà preferibile se stai eseguendo molti cicli.
Liz Sander

1
Aggiungendo un altro commento dal futuro, una applyversione è.mapply(data.frame, xy.df, NULL)
alexis_laz

15

Se vuoi abusare completamente di data.frame (come faccio io) e ti piace mantenere la funzionalità $, un modo è dividere il tuo data.frame in data.frame di una riga raccolti in un elenco:

> 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

Non è solo masturbazione intellettuale, ma permette di 'trasformare' il data.frame in un elenco delle sue righe, mantenendo l'indicizzazione $ che può essere utile per un ulteriore utilizzo con lapply (supponendo che la funzione che si passa a lapply utilizzi questa $ indexation)


Come li rimettiamo insieme? Trasformare un elenco di messaggi di posta data.frameelettronica in un unico data.frame?
Aaron McDaid

4
@AaronMcDaid Puoi usare do.call e rbind: df == do.call ("rbind", ldf)
random_forest_fanatic

@AaronMcDaid Oppure data.table :: rbindlist (). Se il frame di dati originale era grande, i guadagni di velocità saranno significativi.
Empiromancer

8

Una soluzione più moderna utilizza solo 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

8

Ci stavo lavorando oggi per un data.frame (in realtà un data.table) con milioni di osservazioni e 35 colonne. Il mio obiettivo era restituire un elenco di data.frames (data.tables) ciascuno con una singola riga. Cioè, volevo dividere ogni riga in un data.frame separato e memorizzarli in un elenco.

Ecco due metodi che ho escogitato che erano circa 3 volte più veloci rispetto split(dat, seq_len(nrow(dat)))a quel set di dati. Di seguito, ho confrontato i tre metodi su un set di dati di 7500 righe e 5 colonne ( iris ripetuto 50 volte).

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
) 

Questo ritorna

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

Sebbene le differenze non siano così grandi come nel mio test precedente, il setDFmetodo diretto è significativamente più veloce a tutti i livelli della distribuzione delle corse con max (setDF) <min (split) e il attrmetodo è in genere più del doppio più veloce.

Un quarto metodo è il campione estremo, che è un semplice nidificato lapply, che restituisce un elenco nidificato. Questo metodo esemplifica il costo di costruzione di un data.frame da un elenco. Inoltre, tutti i metodi che ho provato con la data.framefunzione erano all'incirca un ordine di grandezza più lenti delle data.tabletecniche.

dati

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

6

Sembra che una versione corrente del purrrpacchetto (0.2.2) sia la soluzione più veloce:

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

Confrontiamo le soluzioni più interessanti:

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
)

Rsults:

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

Inoltre possiamo ottenere lo stesso risultato con 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;
}

Ora confronta con purrr:

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

risultati:

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

il benchmarking su un minuscolo set di dati di 150 righe non ha molto senso in quanto nessuno noterà alcuna differenza nei microsecondi e non si ridimensiona
David Arenburg

4
by_row()si è ora trasferito alibrary(purrrlyr)
MrHopko

E oltre ad essere in purrrlyr, sta per essere deprecato. Ora ci sono altri metodi che combinano tidyr :: nest, dplyr :: mutate purrr :: map per ottenere lo stesso risultato
Mike Stanley

3

Un paio di altre opzioni:

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

Con spliterow

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

dati

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

2

Il modo migliore per me era:

Dati di esempio:

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

Chiamiamo la BBmiscbiblioteca

library(BBmisc)

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

E il risultato sarà:

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) 

1

Un modo alternativo è convertire il df in una matrice, quindi applicare la lappyfunzione di applicazione dell'elenco su di esso:ldf <- lapply(as.matrix(myDF), function(x)x)


1

Un'altra alternativa utilizzando library(purrr)(che sembra essere un po 'più veloce su data.frames di grandi dimensioni)

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

3
`by_row ()` è stato ora spostato in `library (purrrlyr)`
MrHopko

1

Come ha scritto @flodel: questo converte il tuo dataframe in un elenco che ha lo stesso numero di elementi del numero di righe nel dataframe:

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

È inoltre possibile aggiungere una funzione per selezionare solo quelle colonne che non sono NA in ogni elemento dell'elenco:

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

0

La by_rowfunzione del purrrlyrpacchetto lo farà per te.

Questo esempio dimostra

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

Per impostazione predefinita, il valore restituito da myfnviene inserito in una nuova colonna di elenco nel df chiamato .out. Alla $.outfine della dichiarazione precedente seleziona immediatamente questa colonna, restituendo un elenco di elenchi.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.