данные.фрейм строк в список
У меня есть данные.фрейм, который я хотел бы преобразовать в список по строкам, то есть каждая строка будет соответствовать своим собственным элементам списка. Другими словами, Мне бы хотелось, чтобы список был таким же длинным, как и данные.рамка имеет ряды.
До сих пор я рассматривал эту проблему следующим образом, но мне было интересно, есть ли лучший способ подойти к этому.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,]
}
11 ответов:
Вот так:
xy.list <- split(xy.df, seq(nrow(xy.df)))
И если вы хотите, чтобы имена строк
xy.df
были именами выходного списка, вы можете сделать:xy.list <- setNames(split(xy.df, seq(nrow(xy.df))), rownames(xy.df))
Если вы хотите полностью злоупотребить данными.фрейм (как и я) и хотел бы сохранить функциональность$, один из способов-разделить ваши данные.кадр в однострочные данные.кадры собраны в список:
Это не только интеллектуальная мастурбация, но и позволяет "трансформировать" данные.кадр в список его строк, сохраняя $ индексацию , которая может быть полезна для дальнейшего использования с lapply (предполагая, что функция, которую вы передаете lapply, использует эту $ индексацию)> 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
Кажется, что текущая версия пакета
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 )
Результаты:
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; }
Теперь caompare с
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
Я работал над этим сегодня для данных.фрейм (действительно данные.таблица) с миллионами наблюдений и 35 столбцами. Моей целью было вернуть список данных.фреймы (данные.таблицы) каждая с одной строкой. То есть я хотел разбить каждую строку на отдельные данные.обрамляйте и храните их в списке.
Вот два метода, которые я придумал, которые были примерно в 3 раза быстрее, чемsplit(dat, seq_len(nrow(dat)))
для этого набора данных. Ниже я сравниваю три метода на наборе данных 7500 строк, 5 столбцов (iris повторяется 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) attr обычно более чем в два раза быстрее. Четвертый метод-это экстремальный чемпион, который является простым вложеннымlapply
, возвращающим вложенный список. Этот метод иллюстрирует стоимость построения данных.кадр из список. Более того, все методы, которые я пробовал с функцией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 в матрицу, а затем применение над ней функции list apply
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
в конце приведенного выше утверждения немедленно выбирает этот столбец, возвращая список списков.
Более современное решение использует только
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
Как написал @flodel: Это преобразует ваш фрейм данных в список, который имеет то же количество элементов, что и количество строк в фрейме данных:
NewList <- split(df, f = seq(nrow(df)))
Вы можете дополнительно добавить функцию к выбрать только те столбцы, которые не являются NA в каждом элементе списка:
NewList2 <- lapply(NewList, function(x) x[,!is.na(x)])