Строки data.frame в список

У меня есть data.frame, который я хотел бы преобразовать в список по строкам, что означает, что каждая строка будет соответствовать его собственным элементам списка. Другими словами, я бы хотел, чтобы список был до тех пор, пока data.frame имеет строки.

До сих пор я решил эту проблему следующим образом, но мне было интересно, есть ли лучший способ приблизиться к этому.

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

Ответ 1

Вот так:

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

И если вы хотите, чтобы имена ролей xy.df были именами выходного списка, вы можете сделать:

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

Ответ 2

Эврика!

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

Ответ 3

Если вы хотите полностью злоупотреблять data.frame(как и я) и хотите сохранить функциональность $, одним из способов является разделение данных data.frame на однострочные data.frames, собранные в списке:

> 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

Это не только интеллектуальная мастурбация, но и позволяет "трансформировать" data.frame в список его строк, сохраняя индексирование $, которое может быть полезно для дальнейшего использования с lapply (при условии, что функция, которую вы передаете в lapply, использует это $indexation)

Ответ 4

Кажется, что текущая версия пакета purrr (0.2.2) - это самое быстрое решение:

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

Сравним наиболее интересные решения:

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

Также мы можем получить тот же результат с помощью 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;
}

Теперь запомните purrr:

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

Результаты:

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

Ответ 5

Я работал над этим сегодня для data.frame (действительно data.table) с миллионами наблюдений и 35 столбцов. Моя цель состояла в том, чтобы вернуть список data.frames(data.tables) каждый с одной строкой. То есть, я хотел разбить каждую строку на отдельный файл data.frame и сохранить их в списке.

Вот два метода, которые я придумал, примерно в 3 раза быстрее, чем split(dat, seq_len(nrow(dat))) для этого набора данных. Ниже я сравниваю три метода в наборе 7500 строк, 5 столбцов (диафрагма повторяется 50 раз).

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
) 

Это возвращает

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

Хотя различия не такие большие, как в моем предыдущем тесте, прямой метод setDF значительно быстрее на всех уровнях распределения прогонов с max (setDF) <min (split), а метод attr обычно более чем в два раза быстрее.

Четвертый метод является крайним чемпионом, который является простым вложенным lapply, возвращая вложенный список. Этот метод иллюстрирует стоимость построения кадра данных из списка. Более того, все методы, которые я пробовал с data.frame функции data.frame были примерно на порядок медленнее, чем методы data.table.

данные

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

Ответ 6

Другая альтернатива, использующая library(purrr) (которая, по-видимому, немного быстрее на больших кадрах данных)

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

Ответ 7

Лучший способ для меня был:

Пример данных:

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

Мы называем библиотеку BBmisc

library(BBmisc)

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

И результат будет:

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) 

Ответ 8

Альтернативный способ - преобразовать df в матрицу, а затем применить к нему функцию lappy: ldf <- lapply(as.matrix(myDF), function(x)x)

Ответ 9

Функция by_row из пакета purrrlyr сделает это за вас.

В этом примере демонстрируется

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

По умолчанию возвращаемое значение из myfn помещается в новый столбец в df, называемый .out. $.out в конце вышеприведенного оператора немедленно выбирает этот столбец, возвращая список списков.

Ответ 10

Как @flodel писал (а): Это преобразует ваш dataframe в список с таким же количеством элементов, как количество строк в dataframe:

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

Вы можете дополнительно добавить функцию в выбрать только те столбцы, которые не являются NA в каждом элементе списка:

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

Ответ 11

Более современное решение использует только 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