行ごとにリストに変換したい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をリストに集められた1行の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でさらに使用するのに役立ちます
今日、これに取り組んでいたのは、数百万の観測値と35列のdata.frame(実際にはdata.table)です。私の目標は、それぞれが1行のdata.frames(data.tables)のリストを返すことでした。つまり、各行を個別のdata.frameに分割し、これらをリストに保存したかったのです。
以下に、そのデータセットのsplit(dat, seq_len(nrow(dat)))
よりも約3倍速い2つの方法を思い付きました。以下では、7500行、5列のデータセット(irisが50回繰り返される)で3つのメソッドをベンチマークします。
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
前回のテストほど差は大きくありませんが、max(setDF)<min(split)およびsetDF
メソッドを使用すると、実行の分布のすべてのレベルで直線のattr
メソッドが大幅に高速になります通常は2倍以上の速度です。
4番目の方法は極端なチャンピオンで、単純なネストされたlapply
で、ネストされたリストを返します。このメソッドは、リストからdata.frameを構築するコストを例示しています。さらに、私がdata.frame
関数で試したすべての方法は、data.table
手法よりもおよそ1桁遅くなりました。
データ
dat <- vector("list", 50)
for(i in 1:50) dat[[i]] <- iris
dat <- setDF(rbindlist(dat))
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;
}
purrr
でcaompare:
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
より現代的なソリューションは、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
私にとって最善の方法は次のとおりです。
サンプルデータ:
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)
library(purrr)
を使用する別の代替手段(大規模なdata.framesの方が少し速いようです)
flatten(by_row(xy.df, ..f = function(x) flatten_chr(x), .labels = FALSE))
purrrlyr
パッケージのby_row
関数がこれを行います。
この例は示します
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
からの戻り値は、.out
というdfの新しい リスト列 に入れられます。上記のステートメントの最後にある$.out
は、すぐにこの列を選択し、リストのリストを返します。
@flodelが書いたように:これは、データフレームを、データフレーム内の行数と同じ数の要素を持つリストに変換します。
NewList <- split(df, f = seq(nrow(df)))
リストの各要素のNA以外の列のみを選択に関数を追加できます:
NewList2 <- lapply(NewList, function(x) x[,!is.na(x)])