合并(cbind)不同长度的向量

37

我有几个长度不相等的向量,想要将它们 cbind 起来。我把这些向量放进了一个列表中,并尝试使用 do.call(cbind, ...) 来组合它们:

nm <- list(1:8, 3:8, 1:5)
do.call(cbind, nm)

#      [,1] [,2] [,3]
# [1,]    1    3    1
# [2,]    2    4    2
# [3,]    3    5    3
# [4,]    4    6    4
# [5,]    5    7    5
# [6,]    6    8    1
# [7,]    7    3    2
# [8,]    8    4    3
# Warning message:
#   In (function (..., deparse.level = 1)  :
#         number of rows of result is not a multiple of vector length (arg 2)

如预期所料,结果矩阵的行数是最长向量的长度,并且较短向量的值会被循环使用来补足长度。

相反,我想用NA值填充短向量,以获得与最长向量相同的长度。我希望矩阵看起来像这样:

#      [,1] [,2] [,3]
# [1,]    1    3    1
# [2,]    2    4    2
# [3,]    3    5    3
# [4,]    4    6    4
# [5,]    5    7    5
# [6,]    6    8    NA
# [7,]    7    NA   NA
# [8,]    8    NA   NA

我该如何着手做到这一点?


1
灵光一闪:nm <- cbind(z1, c(z2, rep(NA,length(z1)-length(z2)))) - Nick
6个回答

35

如果你对一个对象使用索引,而索引值超出了该对象的大小,则会返回NA。这适用于使用foo定义任意数量行的对象:

nm <- list(1:8,3:8,1:5)

foo <- 8

sapply(nm, '[', 1:foo)

编辑:

或者将最大向量作为行数,写成一行:

sapply(nm, '[', seq(max(sapply(nm,length))))

R 3.2.0 版本开始,您可以使用 lengths("获取列表中每个元素的长度")代替 sapply(nm, length)

sapply(nm, '[', seq(max(lengths(nm))))

'[' 是索引操作符 [ 的名称,您可以在索引中使用它(例如 foo[1:10])。另请参见 ?'[' - Sacha Epskamp
如果第一列比其他两列要短,则单行解决方案会失败。 - bshor
唯一保留列名的答案来自@Ronak Shah,使用rowr包。是否有使用基本R的替代方法可以保留列名? - SeGa

8
在调用do.call之前,您应该用NA填充向量。
nm <- list(1:8,3:8,1:5)

max_length <- max(unlist(lapply(nm,length)))
nm_filled <- lapply(nm,function(x) {ans <- rep(NA,length=max_length);
                                    ans[1:length(x)]<- x;
                                    return(ans)})
do.call(cbind,nm_filled)

3
这是Wojciech解决方案的简化版。
nm <- list(1:8,3:8,1:5)
max_length <- max(sapply(nm,length))
sapply(nm, function(x){
    c(x, rep(NA, max_length - length(x)))
})

2
使用vapply总比使用sapply更好,因为这样可以确保您获得预期的输出类型。 - hadley
@hadley,您能详细解释一下您的评论吗?我不明白在这个问题中vapply和sapply之间的区别。 - guerda
1
sapply在编程中是很危险的,因为它不是类型稳定的 - 根据“nm”的长度,你会得到不同的类型。 - hadley

3

下面是一种使用 stringi 包中的 stri_list2matrix 选项的方法。

library(stringi)
out <- stri_list2matrix(nm)
class(out) <- 'numeric'
out
#      [,1] [,2] [,3]
#[1,]    1    3    1
#[2,]    2    4    2
#[3,]    3    5    3
#[4,]    4    6    4
#[5,]    5    7    5
#[6,]    6    8   NA
#[7,]    7   NA   NA
#[8,]    8   NA   NA

2

虽然已经有些晚了,但是你可以使用 rowr 包中的 cbind.fill 函数,并设置参数 fill = NA

library(rowr)
do.call(cbind.fill, c(nm, fill = NA))

#  object object object
#1      1      3      1
#2      2      4      2
#3      3      5      3
#4      4      6      4
#5      5      7      5
#6      6      8     NA
#7      7     NA     NA
#8      8     NA     NA

如果你有一个命名的list,并且希望保留标题,你可以使用setNames

nm <- list(a = 1:8, b = 3:8, c = 1:5)
setNames(do.call(cbind.fill, c(nm, fill = NA)), names(nm))

#  a  b  c
#1 1  3  1
#2 2  4  2
#3 3  5  3
#4 4  6  4
#5 5  7  5
#6 6  8 NA
#7 7 NA NA
#8 8 NA NA

1

你需要使用length<-将所有列表元素的长度变为相同,然后可以使用cbind来获取一个矩阵。

nm <- list(1:8, 3:8, 1:5)

do.call(cbind, lapply(nm, `length<-`, max(lengths(nm))))
#     [,1] [,2] [,3]
#[1,]    1    3    1
#[2,]    2    4    2
#[3,]    3    5    3
#[4,]    4    6    4
#[5,]    5    7    5
#[6,]    6    8   NA
#[7,]    7   NA   NA
#[8,]    8   NA   NA

基准测试

nm <- list(1:8, 3:8, 1:5)

bench::mark(
"[" = sapply(nm, '[', seq(max(lengths(nm)))),
"length<-" = do.call(cbind, lapply(nm, `length<-`, max(lengths(nm)))) )
#  express…¹     min  median itr/s…² mem_a…³ gc/se…⁴ n_itr  n_gc total…⁵ result  
#  <bch:exp> <bch:t> <bch:t>   <dbl> <bch:b>   <dbl> <int> <dbl> <bch:t> <list>  
#1 [         36.19µs 40.56µs  24412.      0B    12.2  9995     5 409.4ms <int[…]>
#2 length<-   8.63µs  9.88µs 100367.      0B    20.1  9998     2  99.6ms <int[…]>

在这种情况下,使用length<-[快大约4倍。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接