do.call(rbind, list) 用于奇数列
Posted
技术标签:
【中文标题】do.call(rbind, list) 用于奇数列【英文标题】:do.call(rbind, list) for uneven number of column 【发布时间】:2013-06-22 21:53:38 【问题描述】:我有一个列表,每个元素都是一个字符向量,长度不同 我想将数据绑定为行,以便列名“对齐”,如果有额外数据,则创建列,如果缺少数据,则创建 NAs
下面是我正在使用的数据的模拟示例
x <- list()
x[[1]] <- letters[seq(2,20,by=2)]
names(x[[1]]) <- LETTERS[c(1:length(x[[1]]))]
x[[2]] <- letters[seq(3,20, by=3)]
names(x[[2]]) <- LETTERS[seq(3,20, by=3)]
x[[3]] <- letters[seq(4,20, by=4)]
names(x[[3]]) <- LETTERS[seq(4,20, by=4)]
如果我确定每个元素的格式相同,我通常会执行以下操作...
do.call(rbind,x)
我希望有人想出一个不错的小解决方案,匹配列名并用NA
s 填充空白,同时在绑定过程中找到新列时添加新列...
【问题讨论】:
plyr:::rbind.fill
: rbind 使用 NA 填充缺失列的数据框列表。
plyr:::rbind.fill(lapply(x,function(y)as.data.frame(t(y))))
converts all the characters into factors...无论如何要摆脱它?
在节日后做。转置您的变量会无意中将其更改为矩阵。一旦将其强制返回到 data.frame,字符就会被编码为因子。
居然搞定了...rbind.fill(lapply(x,function(y)as.data.frame(t(y),stringsAsFactors=FALSE)))
@h.l.m,当您在每个列表元素上调用 as.data.frame
时,效率会非常低。我不认为这是“最好/最快”的解决方案。
【参考方案1】:
rbind.fill
是一个很棒的函数,在 data.frames 列表中表现非常出色。但是恕我直言,对于这种情况,当列表仅包含(命名的)向量时,它可以做得更快。
rbind.fill
方式
require(plyr)
rbind.fill(lapply(x,function(y)as.data.frame(t(y),stringsAsFactors=FALSE)))
一种更直接的方法(至少在这种情况下有效):
rbind.named.fill <- function(x)
nam <- sapply(x, names)
unam <- unique(unlist(nam))
len <- sapply(x, length)
out <- vector("list", length(len))
for (i in seq_along(len))
out[[i]] <- unname(x[[i]])[match(unam, nam[[i]])]
setNames(as.data.frame(do.call(rbind, out), stringsAsFactors=FALSE), unam)
基本上,我们得到总的唯一名称来形成最终 data.frame 的列。然后,我们创建一个长度 = 输入的列表,并用NA
填充其余值。这可能是“最棘手”的部分,因为我们必须在填写 NA 时匹配名称。然后,我们最终为列设置名称一次(如果需要,也可以使用 data.table
包中的 setnames
引用设置)。
现在进行一些基准测试:
数据:
# generate some huge random data:
set.seed(45)
sample.fun <- function()
nam <- sample(LETTERS, sample(5:15))
val <- sample(letters, length(nam))
setNames(val, nam)
ll <- replicate(1e4, sample.fun())
功能:
# plyr's rbind.fill version:
rbind.fill.plyr <- function(x)
rbind.fill(lapply(x,function(y)as.data.frame(t(y),stringsAsFactors=FALSE)))
rbind.named.fill <- function(x)
nam <- sapply(x, names)
unam <- unique(unlist(nam))
len <- sapply(x, length)
out <- vector("list", length(len))
for (i in seq_along(len))
out[[i]] <- unname(x[[i]])[match(unam, nam[[i]])]
setNames(as.data.frame(do.call(rbind, out), stringsAsFactors=FALSE), unam)
更新(也添加了GSee的功能):
foo <- function (...)
dargs <- list(...)
all.names <- unique(names(unlist(dargs)))
out <- do.call(rbind, lapply(dargs, `[`, all.names))
colnames(out) <- all.names
as.data.frame(out, stringsAsFactors=FALSE)
基准测试:
require(microbenchmark)
microbenchmark(t1 <- rbind.named.fill(ll),
t2 <- rbind.fill.plyr(ll),
t3 <- do.call(foo, ll), times=10)
identical(t1, t2) # TRUE
identical(t1, t3) # TRUE
Unit: milliseconds
expr min lq median uq max neval
t1 <- rbind.named.fill(ll) 243.0754 258.4653 307.2575 359.4332 385.6287 10
t2 <- rbind.fill.plyr(ll) 16808.3334 17139.3068 17648.1882 17890.9384 18220.2534 10
t3 <- do.call(foo, ll) 188.5139 204.2514 229.0074 339.6309 359.4995 10
【讨论】:
+1 用于基准测试。如果您删除了参数检查(即带有 vapply 的 if 语句),我认为我的甚至可能会提前一点。 (但它们的速度非常接近;参数检查可能是值得的) @GSee,公平点。我今天打算改变它。现在我做到了。是的,确实,它更快。无论如何,我喜欢你的代码紧凑性和想法。 +1。我会在rbind.named.fill()
中使用 lapply()
而不是 sapply()
,因为 sapply 将 nam 简化为矩阵,而 unique()
在矩阵和列表上的工作方式不同。【参考方案2】:
如果你希望结果是一个矩阵...
我最近为一位想将向量绑定到矩阵中的同事编写了这个函数。
foo <- function (...)
dargs <- list(...)
if (!all(vapply(dargs, is.vector, TRUE)))
stop("all inputs must be vectors")
if (!all(vapply(dargs, function(x) !is.null(names(x)), TRUE)))
stop("all input vectors must be named.")
all.names <- unique(names(unlist(dargs)))
out <- do.call(rbind, lapply(dargs, `[`, all.names))
colnames(out) <- all.names
out
R > do.call(foo, x)
A B C D E F G H I J L O R P T
[1,] "b" "d" "f" "h" "j" "l" "n" "p" "r" "t" NA NA NA NA NA
[2,] NA NA "c" NA NA "f" NA NA "i" NA "l" "o" "r" NA NA
[3,] NA NA NA "d" NA NA NA "h" NA NA "l" NA NA "p" "t"
【讨论】:
【参考方案3】:这是一个使用包data.table
的版本,对于非常大的数据来说要快一些。
它使用函数rbindlist
及其参数fill=TRUE
传递给函数do.call
。
library(data.table)
x <- list()
x[[1]] <- letters[seq(2,20,by=2)]
names(x[[1]]) <- LETTERS[c(1:length(x[[1]]))]
x[[2]] <- letters[seq(3,20, by=3)]
names(x[[2]]) <- LETTERS[seq(3,20, by=3)]
x[[3]] <- letters[seq(4,20, by=4)]
names(x[[3]]) <- LETTERS[seq(4,20, by=4)]
x2 <- lapply(x, as.list)
rbindlist(x2, fill=TRUE)
#> A B C D E F G H I J L O R P T
#> 1: b d f h j l n p r t <NA> <NA> <NA> <NA> <NA>
#> 2: <NA> <NA> c <NA> <NA> f <NA> <NA> i <NA> l o r <NA> <NA>
#> 3: <NA> <NA> <NA> d <NA> <NA> <NA> h <NA> <NA> l <NA> <NA> p t
它增加了一点开销,因为它需要用as.list
转换字符向量。这段话还可以增加流程的时间,具体取决于数据的生成方式。
另一方面,它似乎在大型数据集上执行得更快。
它返回一个data.table
。
我重写了@Arun 和@GSee 的示例以生成更大的样本。
数据
# generate some huge random data:
set.seed(45)
sample.fun <- function()
nam <- sample(LETTERS, sample(5:15))
val <- sample(letters, length(nam))
setNames(val, nam)
l1 <- replicate(1e6, sample.fun()) # Arun's data, just bigger
l2 <- lapply(l1, as.list) # same data converted with as.list
功能
library(microbenchmark)
library(data.table)
# Arun's function
rbind.named.fill <- function(x)
nam <- sapply(x, names)
unam <- unique(unlist(nam))
len <- sapply(x, length)
out <- vector("list", length(len))
for (i in seq_along(len))
out[[i]] <- unname(x[[i]])[match(unam, nam[[i]])]
setNames(as.data.frame(do.call(rbind, out), stringsAsFactors=FALSE), unam)
# GSee's function
foo <- function (...)
dargs <- list(...)
all.names <- unique(names(unlist(dargs)))
out <- do.call(rbind, lapply(dargs, `[`, all.names))
colnames(out) <- all.names
as.data.frame(out, stringsAsFactors=FALSE)
基准测试
microbenchmark(t1 <- rbind.named.fill(l1),
t2 <- rbindlist(l2, fill=TRUE),
t3 <- do.call(foo, l1),
times=10)
#> Unit: seconds
#> expr min lq mean median uq max neval
#> t1 <- rbind.named.fill(l1) 6.536782 7.545538 9.118771 9.304844 10.505814 11.28260 10
#> t2 <- rbindlist(l2, fill=TRUE) 5.250387 5.787712 6.910340 6.226065 7.579503 10.40524 10
#> t3 <- do.call(foo, l1) 9.590615 11.043557 13.504694 12.550535 15.364464 19.95877 10
identical(t1, data.frame(t2))
#> [1] TRUE
identical(t3, data.frame(t2))
#> [1] TRUE
由reprex package (v0.3.0) 于 2019-08-01 创建
【讨论】:
【参考方案4】:将名称向量转换为单个数据框后,您可以使用dplyr::bind_rows
dplyr::bind_rows(lapply(x,function(y) as.data.frame(t(y),stringsAsFactors=FALSE)))
# A B C D E F G H I J L O R P T
#1 b d f h j l n p r t <NA> <NA> <NA> <NA> <NA>
#2 <NA> <NA> c <NA> <NA> f <NA> <NA> i <NA> l o r <NA> <NA>
#3 <NA> <NA> <NA> d <NA> <NA> <NA> h <NA> <NA> l <NA> <NA> p t
在这种情况下我们也可以使用purrr::map_df
/purrr::map_dfr
purrr::map_df(x, ~as.data.frame(t(.x),stringsAsFactors = FALSE))
这将提供与上述相同的输出。
【讨论】:
以上是关于do.call(rbind, list) 用于奇数列的主要内容,如果未能解决你的问题,请参考以下文章