Binding list of vectors of different lengths in R

80 Views Asked by At

Consider a list of vectors with different lenghts, for example lV defined as

set.seed(123)
v1 = rnorm(n = 5)
v2 = rnorm(n = 4)
v3 = rnorm(n = 3)
lV = list(v1, v2, v3)

How can I bind the vectors in a compact manner similarly to

do.call("cbind", lV)

but obtaining the output of

cbind(v1[3 : 5], v2[2 : 4], v3)

? Preferably without using slow functions.

If it wasn't clear, I am trying to retain the last n elements of each vector, where

n = min(sapply(X = lV, FUN = length))
print(n)
3

There are 3 best solutions below

0
ThomasIsCoding On BEST ANSWER

A short code option

> sapply(lV, tail, min(lengths(lV)))
           [,1]       [,2]       [,3]
[1,] 1.55870831  0.4609162 -0.4456620
[2,] 0.07050839 -1.2650612  1.2240818
[3,] 0.12928774 -0.6868529  0.3598138
0
Gregor Thomas On
n = min(lengths(lV))
do.call(cbind, lapply(lV, tail, n))
#            [,1]       [,2]       [,3]
# [1,] 1.55870831  0.4609162 -0.4456620
# [2,] 0.07050839 -1.2650612  1.2240818
# [3,] 0.12928774 -0.6868529  0.3598138
2
jblood94 On

A vectorized approach using sequence and cumsum:

n <- min(lengths(lV))
matrix(unlist(lV)[sequence(rep(n, length(lV)), cumsum(lengths(lV)) - n + 1L)], n)
#>            [,1]       [,2]       [,3]
#> [1,] 1.55870831  0.4609162 -0.4456620
#> [2,] 0.07050839 -1.2650612  1.2240818
#> [3,] 0.12928774 -0.6868529  0.3598138

Depending on the sizes of the vectors in lV, this can be much more performant than a loop or apply-family solution:

lV <- lapply(sample(100, 100, 1), runif)
n <- min(lengths(lV))

microbenchmark::microbenchmark(
  do.call = do.call(cbind, lapply(lV, tail, n)),
  sequence = matrix(unlist(lV)[sequence(rep(n, length(lV)), cumsum(lengths(lV)) - n + 1L)], n),
  check = "identical"
)
#> Unit: microseconds
#>      expr   min     lq    mean median     uq    max neval
#>   do.call 459.2 562.25 594.879 588.25 604.40 1985.1   100
#>  sequence  21.6  28.70  33.822  29.95  34.15   81.7   100