Ответ 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))
У меня есть 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,]
}
Вот так:
xy.list <- split(xy.df, seq(nrow(xy.df)))
И если вы хотите, чтобы имена ролей xy.df
были именами выходного списка, вы можете сделать:
xy.list <- setNames(split(xy.df, seq(nrow(xy.df))), rownames(xy.df))
Эврика!
xy.list <- as.list(as.data.frame(t(xy.df)))
Если вы хотите полностью злоупотреблять 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)
Кажется, что текущая версия пакета 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
Я работал над этим сегодня для 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))
Другая альтернатива, использующая library(purrr)
(которая, по-видимому, немного быстрее на больших кадрах данных)
flatten(by_row(xy.df, ..f = function(x) flatten_chr(x), .labels = FALSE))
Лучший способ для меня был:
Пример данных:
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)
Альтернативный способ - преобразовать df в матрицу, а затем применить к нему функцию lappy
: ldf <- lapply(as.matrix(myDF), function(x)x)
Функция 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
в конце вышеприведенного оператора немедленно выбирает этот столбец, возвращая список списков.
Как @flodel писал (а): Это преобразует ваш dataframe в список с таким же количеством элементов, как количество строк в dataframe:
NewList <- split(df, f = seq(nrow(df)))
Вы можете дополнительно добавить функцию в выбрать только те столбцы, которые не являются NA в каждом элементе списка:
NewList2 <- lapply(NewList, function(x) x[,!is.na(x)])
Более современное решение использует только 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