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,]
}
237745 次浏览

找到了!

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

如果你想完全滥用 data.frame (就像我一样) ,并且想保留 $功能,一种方法是将 data.frame 分割成一行 data.frame,收集在一个列表中:

> 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“转换”成它的行列表,保留 $indexation,这可以用于 lapplication 的进一步使用(假设你传递给 lapplication 的函数使用了这个 $indexation)

像这样:

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

如果希望 xy.df的行名作为输出列表的名称,可以这样做:

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

另一种方法是将 df 转换为一个矩阵,然后应用列表对其应用 lappy函数: ldf <- lapply(as.matrix(myDF), function(x)x)

另一种使用 library(purrr)的方法(在大型 data.frame 上似乎更快一些)

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

看起来当前版本的 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相比:

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

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返回的值放入 df 中名为 .out的新 列表栏中。以上语句末尾的 $.out立即选择此列,并返回列表列表。

对我来说最好的办法是:

示例数据:

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)

就像@flodel 写的: 这会将您的数据框转换为一个拥有与数据框中的行数相同数量的元素的列表:

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

我今天正在为一个 data.frame (实际上是一个 data.table)做这个工作,它有数百万个观察数据和35个列。我的目标是返回一个包含每一行的 data.frame (data.tables)列表。也就是说,我想将每一行分割成一个单独的 data.frame 并将它们存储在一个列表中。

下面是我想出的两个方法,对于这个数据集,它们的速度大约是 split(dat, seq_len(nrow(dat)))的3倍。下面,我对一个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

虽然差异不像我以前的测试那样大,但是在 max (setDF) < min (split)的运行分布的所有级别上,直接 setDF方法明显更快,而 attr方法通常快两倍以上。

第四种方法是极限冠军,它是一个简单的嵌套 lapply,返回一个嵌套列表。这种方法举例说明了从一个列表构造一个 data.frame 的成本。此外,我尝试的所有使用 data.frame功能的方法都比使用 data.table技术的方法大致慢一个数量级。

资料

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

还有几个选择:

asplit

asplit(xy.df, 1)
#[[1]]
#     x      y
#0.1137 0.6936


#[[2]]
#     x      y
#0.6223 0.5450


#[[3]]
#     x      y
#0.6093 0.2827
#....

splitrow

split(xy.df, row(xy.df)[, 1])


#$`1`
#       x      y
#1 0.1137 0.6936


#$`2`
#       x     y
#2 0.6223 0.545


#$`3`
#       x      y
#3 0.6093 0.2827
#....

资料

set.seed(1234)
xy.df <- data.frame(x = runif(10),  y = runif(10))