2010-08-16 17 views
68

Ho un data.frame che vorrei convertire in un elenco per righe, nel senso che ogni riga corrisponderebbe ai propri elementi di lista. In altre parole, mi piacerebbe un elenco che sia lungo come data.frame ha le righe.data.frame righe a un elenco

Finora, ho affrontato questo problema nel modo seguente, ma mi chiedevo se c'è un modo migliore per avvicinarsi a questo.

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

risposta

87

Come questo:

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

E se si desidera che i rownames di xy.df di essere i nomi della lista di output, si può fare:

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

Eureka!

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

Picchiami ;-). Tuttavia, se desideri semplicemente ricorrere a questi valori, usa meglio applica. – mbq

+1

Prestare attenzione a dimostrare come utilizzare applicare? –

+2

'unlist (applica (xy.df, 1, lista), recursive = FALSE)'. Tuttavia la soluzione di flodel è la più efficiente dell'uso di 'apply' o' t'. – Arun

11

Se si vuole abusare completamente il data.frame (come faccio io) e, come per mantenere la funzionalità $, in un modo è quello di dividere voi data.frame in una sola riga data.frames raccolti in una lista :

> 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 la masturbazione intellettuale, ma permette di 'trasformare' la data.frame in un elenco delle sue linee, mantenendo il $ indicizzazione che può essere utile per un ulteriore uso con lapply (supponendo che la funzione passare a lapply utilizza questa indicizzazione $)

+0

Come li rimettiamo insieme? Trasforma un elenco di 'data.frame's in un singolo' data.frame'? –

+3

@AaronMcDaid È possibile utilizzare do.call e rbind: df == do.call ("rbind", ldf) –

+0

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

1

Un modo alternativo è quello di convertire il df ad una matrice poi applicare la lista applicare lappy funzione su di esso:

2

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

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

'by_row()' è ora spostato in 'library (purrrlyr)' – MrHopko

5

Sembra una versione attuale della (0.2.2) del pacchetto purrr è la soluzione più veloce:

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

Mettiamo a confronto 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 caompare 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 
+0

benchmarking su un piccolo set di dati di 150 righe non ha molto senso poiché nessuno noterà alcuna differenza in microsecondi e non scala –

+0

You ' Hai ragione. Risposta aggiornata –

+2

'by_row()' è ora spostato in 'library (purrrlyr)' – MrHopko

0

La funzione by_row dal pacchetto purrrlyr farà questo per voi.

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 myfn viene messo in un nuovo list column nel DF chiamato .out. Lo $.out alla fine dell'istruzione precedente seleziona immediatamente questa colonna, restituendo un elenco di elenchi.

1

Il modo migliore per me era: i dati

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 

noi chiamiamo il BBmisc biblioteca

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) 
0

L ike @flodel ha scritto: Questo trasforma il vostro dataframe in un elenco che ha lo stesso numero di elementi come il numero di righe in dataframe:

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

È possibile additionaly aggiungere una funzione per selezionare solo le colonne che non sono NA in ogni elemento della lista:

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

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 
Problemi correlati