Yesterday’s request for help got some great replies on twitter. I’ve collected them in this post and benchmarked them.
No one (besides myself) had the sheer audacity to inflict a for
loop upon the world.
Huge thanks to the following people for their ideas! (in order of appearance)
- Stuart Lee
- Gabe Becker
- Kara Woo
- Michael Sumner
- Jake Westfall
- Brodie Gaslam
- Brendan Knapp
- Edward Visel
- David Mas-Ponte
Interleaving a row-vector with a matrix of the same width
I have a vector a row-vector and matrix of equal width
vec <- c(101, 102, 103)
mat <- matrix(c( 1, 2, 3,
4, 5, 6,
7, 8, 9,
10, 11, 12), nrow = 4, byrow = TRUE)
Expected output after interleaving by column
[,1] [,2] [,3] [,4] [,5] [,6]
[1,] 1 101 2 102 3 103
[2,] 4 101 5 102 6 103
[3,] 7 101 8 102 9 103
[4,] 10 101 11 102 12 103
My solution
This was my initial solution. I was concerned about whether the for
loop
could be done away with.
I did try a couple of solutions with an explicit rep()
to try and avoid the for
loop, but found the overhead of the extra memory allocation slowed things down.
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#' Interleave a matrix and a row-vector of the same width
#'
#' * Create an empty matrix of double the width
#' * Copy over the given matrix
#' * Copy over the vector (using a for loop. quelle horreur!)
#'
#' @param m NxM matrix
#' @param v vector of length M
#'
#' @return N x M*2 matrix
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
interleave_coolbutuseless <- function(mat, vec) {
res <- matrix(0, ncol = 2L * ncol(mat), nrow = nrow(mat))
res[,c(T, F)] <- mat
for (i in seq_along(vec)) {
res[,2L * i] <- vec[i]
}
res
}
interleave_coolbutuseless(mat, vec)
[,1] [,2] [,3] [,4] [,5] [,6]
[1,] 1 101 2 102 3 103
[2,] 4 101 5 102 6 103
[3,] 7 101 8 102 9 103
[4,] 10 101 11 102 12 103
Solution by Stuart Lee
interleave_stuartlee <- function(mat, vec) {
res <- matrix(0, ncol = 2L * ncol(mat), nrow = nrow(mat))
res[, c(TRUE, FALSE)] <- mat
res[, c(FALSE, TRUE)] <- sort(rep(vec, nrow(mat)))
res
}
interleave_stuartlee(mat, vec)
[,1] [,2] [,3] [,4] [,5] [,6]
[1,] 1 101 2 102 3 103
[2,] 4 101 5 102 6 103
[3,] 7 101 8 102 9 103
[4,] 10 101 11 102 12 103
Solution by Gabe Becker
interleave_groundwalkgmb <- function(mat, vec) {
vm <- matrix(rep(vec, nrow(mat)), ncol = ncol(mat), byrow=TRUE)
res <- rbind(mat, vm)
dim(res) <- c(nrow(mat), 2L * ncol(mat))
res
}
interleave_groundwalkgmb(mat, vec)
[,1] [,2] [,3] [,4] [,5] [,6]
[1,] 1 101 2 102 3 103
[2,] 4 101 5 102 6 103
[3,] 7 101 8 102 9 103
[4,] 10 101 11 102 12 103
Solutions by Kara Woo
interleave_kara_woo_1 <- function(mat, vec) {
t(apply(mat, 1, function(x) unlist(purrr::map2(x, vec, c))))
}
interleave_kara_woo_1(mat, vec)
[,1] [,2] [,3] [,4] [,5] [,6]
[1,] 1 101 2 102 3 103
[2,] 4 101 5 102 6 103
[3,] 7 101 8 102 9 103
[4,] 10 101 11 102 12 103
interleave_kara_woo_2 <- function(mat, vec) {
t(apply(mat, 1, function(x) unlist(mapply(c, x, vec))))
}
interleave_kara_woo_2(mat, vec)
[,1] [,2] [,3] [,4] [,5] [,6]
[1,] 1 101 2 102 3 103
[2,] 4 101 5 102 6 103
[3,] 7 101 8 102 9 103
[4,] 10 101 11 102 12 103
Solution by Michael Sumner
interleave_mdsumner <- function(mat, vec) {
matrix(
rbind(
mat,
matrix(rep(vec, each = nrow(mat)), ncol = ncol(mat))
),
nrow = nrow(mat)
)
}
interleave_mdsumner(mat, vec)
[,1] [,2] [,3] [,4] [,5] [,6]
[1,] 1 101 2 102 3 103
[2,] 4 101 5 102 6 103
[3,] 7 101 8 102 9 103
[4,] 10 101 11 102 12 103
Solution by Jake Westfall
interleave_CookieSci <- function(mat, vec) {
Reduce(cbind, vec, mat)[,c(rbind(seq(vec), seq(vec)+length(vec)))]
}
interleave_CookieSci(mat, vec)
[,1] [,2] [,3] [,4] [,5] [,6]
[1,] 1 101 2 102 3 103
[2,] 4 101 5 102 6 103
[3,] 7 101 8 102 9 103
[4,] 10 101 11 102 12 103
Solution by Brodie Gaslam
interleave_BrodieGaslam <- function(mat, vec) {
matrix( aperm( array(c(mat, rep(vec, each=nrow(mat))), dim=c(dim(mat), 2)), c(1, 3, 2) ), nrow(mat) )
}
interleave_BrodieGaslam(mat, vec)
[,1] [,2] [,3] [,4] [,5] [,6]
[1,] 1 101 2 102 3 103
[2,] 4 101 5 102 6 103
[3,] 7 101 8 102 9 103
[4,] 10 101 11 102 12 103
Solution by Brendan Knapp
interleave_knapply <- function(mat, vec) {
matrix(rbind(mat, matrix(vec, nrow = nrow(mat), ncol = ncol(mat), byrow = TRUE)), nrow = nrow(mat))
}
interleave_knapply(mat, vec)
[,1] [,2] [,3] [,4] [,5] [,6]
[1,] 1 101 2 102 3 103
[2,] 4 101 5 102 6 103
[3,] 7 101 8 102 9 103
[4,] 10 101 11 102 12 103
Solutions by Edward Visel
interleave_alistaire_1 <- function(mat, vec) {
res <- array(dim = dim(mat) * 1:2)
abind::afill(res, T, c(T, F)) <- mat
abind::afill(res, T, c(F, T)) <- t(array(vec, rev(dim(mat))))
res
}
interleave_alistaire_1(mat, vec)
[,1] [,2] [,3] [,4] [,5] [,6]
[1,] 1 101 2 102 3 103
[2,] 4 101 5 102 6 103
[3,] 7 101 8 102 9 103
[4,] 10 101 11 102 12 103
interleave_alistaire_2 <- function(mat, vec) {
res <- array(dim = dim(mat) * 1:2)
res[, c(T, F)] <- mat
res[, c(F, T)] <- t(array(vec, rev(dim(mat))))
res
}
interleave_alistaire_2(mat, vec)
[,1] [,2] [,3] [,4] [,5] [,6]
[1,] 1 101 2 102 3 103
[2,] 4 101 5 102 6 103
[3,] 7 101 8 102 9 103
[4,] 10 101 11 102 12 103
Solution by David Mas-Ponte
interleave_davidmaasp <- function(mat, vec) {
new_idx <- (1:ncol(mat)*2)-1
inter_idx <- (1:ncol(mat)*2)
res <- matrix(0, ncol = 2L * ncol(mat), nrow = nrow(mat))
res[,new_idx] <- mat
res[,inter_idx] <- matrix(rep(vec, nrow(mat)), byrow=TRUE, ncol=length(vec))
res
}
interleave_davidmaasp(mat, vec)
[,1] [,2] [,3] [,4] [,5] [,6]
[1,] 1 101 2 102 3 103
[2,] 4 101 5 102 6 103
[3,] 7 101 8 102 9 103
[4,] 10 101 11 102 12 103
Benchmarking
I’m only benchmarking for my my target problem size, but changing the dimensions of the initial matrix and vector changes the benchmarking results. Buyer beware!
N <- 1000
M <- 10
mat <- matrix(seq(M*N), nrow = N, ncol = M)
vec <- seq(M) + 100
res <- bench::mark(
interleave_coolbutuseless(mat, vec),
interleave_stuartlee(mat, vec), #
interleave_groundwalkgmb(mat, vec),#
interleave_kara_woo_1(mat, vec),
interleave_kara_woo_2(mat, vec),
interleave_mdsumner(mat, vec),
interleave_CookieSci(mat, vec),
interleave_BrodieGaslam(mat, vec),
interleave_knapply(mat, vec),
interleave_alistaire_1(mat, vec),
interleave_alistaire_2(mat, vec),
interleave_davidmaasp(mat, vec),
check = TRUE
)
expression | median | itr/sec | mem_alloc |
---|---|---|---|
interleave_coolbutuseless(mat, vec) | 134.49µs | 5502 | 199.78KB |
interleave_stuartlee(mat, vec) | 532.61µs | 1604 | 394.38KB |
interleave_groundwalkgmb(mat, vec) | 237.49µs | 3288 | 348.19KB |
interleave_kara_woo_1(mat, vec) | 127.77ms | 8 | 757.59KB |
interleave_kara_woo_2(mat, vec) | 37.27ms | 24 | 946.52KB |
interleave_mdsumner(mat, vec) | 496.8µs | 1639 | 484.12KB |
interleave_CookieSci(mat, vec) | 831.47µs | 922 | 1.39MB |
interleave_BrodieGaslam(mat, vec) | 689.41µs | 1070 | 725.27KB |
interleave_knapply(mat, vec) | 258.12µs | 3060 | 406.51KB |
interleave_alistaire_1(mat, vec) | 4.94ms | 186 | 767.38KB |
interleave_alistaire_2(mat, vec) | 321.57µs | 2372 | 516.99KB |
interleave_davidmaasp(mat, vec) | 225.02µs | 3472 | 320.55KB |
Loading required namespace: tidyr
And the winner is …
- There’s not that much speed difference between the fastest methods.
- Across a wider variety of benchmarks than I’ve shown here, Gabe Becker’s solution was often fastest.
- My
for
loop method is on par with others for my target dimensions (and has the added bonus of lowest memory allocatiions).