bind_array

Dimensional Binding of Arrays with Broadcasting

Description

bind_array() binds (atomic/recursive) arrays and (atomic/recursive) matrices.
Allows for broadcasting.

Usage

bind_array(
  input,
  along,
  rev = FALSE,
  ndim2bc = 16L,
  name_along = TRUE,
  comnames_from = 1L
)

Arguments

input a list of arrays; both atomic and recursive arrays are supported, and can be mixed.
If argument input has length 0, or it contains exclusively objects where one or more dimensions are 0, an error is returned.
If input has length 1, bind_array() simply returns input[[1L]].
input may not contain more than 2^16 objects.
along a single integer, indicating the dimension along which to bind the dimensions.
I.e. use along = 1 for row-binding, along = 2 for column-binding, etc.
Specifying along = 0 will bind the arrays on a new dimension before the first, making along the new first dimension.
Specifying along = N + 1, with N = max(lst.ndim(input)), will create an additional dimension (N + 1) and bind the arrays along that new dimension.
rev Boolean, indicating if along should be reversed, counting backwards.
If FALSE (default), along works like normally; if TRUE, along is reversed.
I.e. along = 0, rev = TRUE is equivalent to along = N+1, rev = FALSE;
and along = N+1, rev = TRUE is equivalent to along = 0, rev = FALSE;
with N = max(lst.ndim(input)).
ndim2bc a single non-negative integer;
specify here the maximum number of dimensions that are allowed to be broadcasted when binding arrays.
If ndim2bc = 0L, no broadcasting will be allowed at all.
name_along Boolean, indicating if dimension along should be named.
Please run the code in the examples section to get a demonstration of the naming behaviour.
comnames_from either an integer scalar or NULL.
Indicates which object in input should be used for naming the shared dimension.
If NULL, no communal names will be given.
For example:
When binding columns of matrices, the matrices will share the same rownames.
Using comnames_from = 10 will then result in bind_array() using rownames(input[[10]]) for the rownames of the output.

Details

The API of bind_array() is inspired by the fantastic abind::abind() function by Tony Plare & Richard Heiberger (2016).
But bind_array() differs considerably from abind::abind in the following ways:

  • bind_array() allows for broadcasting, while abind::abind does not support broadcasting.

  • bind_array() is generally faster and more memory-efficient than abind::abind, as bind_array() relies heavily on ‘C’ and ‘C++’ code.

  • bind_array() differs from abind::abind in that it can handle recursive arrays properly
    (the abind::abind function would unlist everything to atomic arrays, ruining the structure).

  • unlike abind::abind, bind_array() only binds (atomic/recursive) arrays and matrices.
    bind_array()does not attempt to convert things to arrays when they are not arrays, but will give an error instead.
    This saves computation time and prevents unexpected results.

  • bind_array() has more streamlined naming options, compared to abind::abind.

Value

An array.

References

Plate T, Heiberger R (2016). abind: Combine Multidimensional Arrays. R package version 1.4-5, https://CRAN.R-project.org/package=abind.

Examples

library("broadcast")


# Simple example ====
x <- array(1:20, c(5, 4))
y <- array(-1:-15, c(5, 3))
z <- array(21:40, c(5, 4))
input <- list(x, y, z)
# column binding:
bind_array(input, 2L)
##      [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10] [,11]
## [1,]    1    6   11   16   -1   -6  -11   21   26    31    36
## [2,]    2    7   12   17   -2   -7  -12   22   27    32    37
## [3,]    3    8   13   18   -3   -8  -13   23   28    33    38
## [4,]    4    9   14   19   -4   -9  -14   24   29    34    39
## [5,]    5   10   15   20   -5  -10  -15   25   30    35    40


# Broadcasting example ====
x <- array(1:20, c(5, 4))
y <- array(-1:-5, c(5, 1))
z <- array(21:40, c(5, 4))
input <- list(x, y, z)
bind_array(input, 2L)
##      [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9]
## [1,]    1    6   11   16   -1   21   26   31   36
## [2,]    2    7   12   17   -2   22   27   32   37
## [3,]    3    8   13   18   -3   23   28   33   38
## [4,]    4    9   14   19   -4   24   29   34   39
## [5,]    5   10   15   20   -5   25   30   35   40


# Mixing types ====
# here, atomic and recursive arrays are mixed,
# resulting in recursive arrays

# creating the arrays:
x <- c(
  lapply(1:3, \(x)sample(c(TRUE, FALSE, NA))),
  lapply(1:3, \(x)sample(1:10)),
  lapply(1:3, \(x)rnorm(10)),
  lapply(1:3, \(x)sample(letters))
) |> matrix(4, 3, byrow = TRUE)
dimnames(x) <- list(letters[1:4], LETTERS[1:3])
print(x)
##   A            B            C           
## a logical,3    logical,3    logical,3   
## b integer,10   integer,10   integer,10  
## c numeric,10   numeric,10   numeric,10  
## d character,26 character,26 character,26

y <- matrix(1:12, 4, 3)
print(y)
##      [,1] [,2] [,3]
## [1,]    1    5    9
## [2,]    2    6   10
## [3,]    3    7   11
## [4,]    4    8   12
z <- matrix(letters[1:12], c(4, 3))

# column-binding:
input <- list(x = x, y = y, z = z)
bind_array(input, along = 2L)
##   A            B            C            y.1 y.2 y.3 z.1 z.2 z.3
## a logical,3    logical,3    logical,3    1   5   9   "a" "e" "i"
## b integer,10   integer,10   integer,10   2   6   10  "b" "f" "j"
## c numeric,10   numeric,10   numeric,10   3   7   11  "c" "g" "k"
## d character,26 character,26 character,26 4   8   12  "d" "h" "l"



# Illustrating `along` argument ====
# using recursive arrays for clearer visual distinction
input <- list(x = x, y = y)

bind_array(input, along = 0L) # binds on new dimension before first
## , , A
## 
##   a         b          c          d           
## x logical,3 integer,10 numeric,10 character,26
## y 1         2          3          4           
## 
## , , B
## 
##   a         b          c          d           
## x logical,3 integer,10 numeric,10 character,26
## y 5         6          7          8           
## 
## , , C
## 
##   a         b          c          d           
## x logical,3 integer,10 numeric,10 character,26
## y 9         10         11         12
bind_array(input, along = 1L) # binds on first dimension (i.e. rows)
##     A            B            C           
## a   logical,3    logical,3    logical,3   
## b   integer,10   integer,10   integer,10  
## c   numeric,10   numeric,10   numeric,10  
## d   character,26 character,26 character,26
## y.1 1            5            9           
## y.2 2            6            10          
## y.3 3            7            11          
## y.4 4            8            12
bind_array(input, along = 2L)
##   A            B            C            y.1 y.2 y.3
## a logical,3    logical,3    logical,3    1   5   9  
## b integer,10   integer,10   integer,10   2   6   10 
## c numeric,10   numeric,10   numeric,10   3   7   11 
## d character,26 character,26 character,26 4   8   12
bind_array(input, along = 3L) # bind on new dimension after last
## , , x
## 
##   A            B            C           
## a logical,3    logical,3    logical,3   
## b integer,10   integer,10   integer,10  
## c numeric,10   numeric,10   numeric,10  
## d character,26 character,26 character,26
## 
## , , y
## 
##   A B C 
## a 1 5 9 
## b 2 6 10
## c 3 7 11
## d 4 8 12

bind_array(input, along = 0L, TRUE) # binds on new dimension after last
## , , x
## 
##   A            B            C           
## a logical,3    logical,3    logical,3   
## b integer,10   integer,10   integer,10  
## c numeric,10   numeric,10   numeric,10  
## d character,26 character,26 character,26
## 
## , , y
## 
##   A B C 
## a 1 5 9 
## b 2 6 10
## c 3 7 11
## d 4 8 12
bind_array(input, along = 1L, TRUE) # binds on last dimension (i.e. columns)
##   A            B            C            y.1 y.2 y.3
## a logical,3    logical,3    logical,3    1   5   9  
## b integer,10   integer,10   integer,10   2   6   10 
## c numeric,10   numeric,10   numeric,10   3   7   11 
## d character,26 character,26 character,26 4   8   12
bind_array(input, along = 2L, TRUE)
##     A            B            C           
## a   logical,3    logical,3    logical,3   
## b   integer,10   integer,10   integer,10  
## c   numeric,10   numeric,10   numeric,10  
## d   character,26 character,26 character,26
## y.1 1            5            9           
## y.2 2            6            10          
## y.3 3            7            11          
## y.4 4            8            12
bind_array(input, along = 3L, TRUE) # bind on new dimension before first
## , , A
## 
##   a         b          c          d           
## x logical,3 integer,10 numeric,10 character,26
## y 1         2          3          4           
## 
## , , B
## 
##   a         b          c          d           
## x logical,3 integer,10 numeric,10 character,26
## y 5         6          7          8           
## 
## , , C
## 
##   a         b          c          d           
## x logical,3 integer,10 numeric,10 character,26
## y 9         10         11         12



# binding, with empty arrays ====
emptyarray <- array(numeric(0L), c(0L, 3L))
dimnames(emptyarray) <- list(NULL, paste("empty", 1:3))
print(emptyarray)
##      empty 1 empty 2 empty 3
input <- list(x = x, y = emptyarray)
bind_array(input, along = 1L, comnames_from = 2L) # row-bind
##   A            B            C           
## a logical,3    logical,3    logical,3   
## b integer,10   integer,10   integer,10  
## c numeric,10   numeric,10   numeric,10  
## d character,26 character,26 character,26



# Illustrating `name_along` ====
x <- array(1:20, c(5, 3), list(NULL, LETTERS[1:3]))
y <- array(-1:-20, c(5, 3))
z <- array(-1:-20, c(5, 3))

bind_array(list(a = x, b = y, z), 2L)
##      A  B  C b.1 b.2 b.3           
## [1,] 1  6 11  -1  -6 -11 -1  -6 -11
## [2,] 2  7 12  -2  -7 -12 -2  -7 -12
## [3,] 3  8 13  -3  -8 -13 -3  -8 -13
## [4,] 4  9 14  -4  -9 -14 -4  -9 -14
## [5,] 5 10 15  -5 -10 -15 -5 -10 -15
bind_array(list(x, y, z), 2L)
##      A  B  C                      
## [1,] 1  6 11 -1  -6 -11 -1  -6 -11
## [2,] 2  7 12 -2  -7 -12 -2  -7 -12
## [3,] 3  8 13 -3  -8 -13 -3  -8 -13
## [4,] 4  9 14 -4  -9 -14 -4  -9 -14
## [5,] 5 10 15 -5 -10 -15 -5 -10 -15
bind_array(list(a = unname(x), b = y, c = z), 2L)
##      a.1 a.2 a.3 b.1 b.2 b.3 c.1 c.2 c.3
## [1,]   1   6  11  -1  -6 -11  -1  -6 -11
## [2,]   2   7  12  -2  -7 -12  -2  -7 -12
## [3,]   3   8  13  -3  -8 -13  -3  -8 -13
## [4,]   4   9  14  -4  -9 -14  -4  -9 -14
## [5,]   5  10  15  -5 -10 -15  -5 -10 -15
bind_array(list(x, a = y, b = z), 2L)
##      A  B  C a.1 a.2 a.3 b.1 b.2 b.3
## [1,] 1  6 11  -1  -6 -11  -1  -6 -11
## [2,] 2  7 12  -2  -7 -12  -2  -7 -12
## [3,] 3  8 13  -3  -8 -13  -3  -8 -13
## [4,] 4  9 14  -4  -9 -14  -4  -9 -14
## [5,] 5 10 15  -5 -10 -15  -5 -10 -15
input <- list(x, y, z)
names(input) <- c("", NA, "")
bind_array(input, 2L)
##      A  B  C                      
## [1,] 1  6 11 -1  -6 -11 -1  -6 -11
## [2,] 2  7 12 -2  -7 -12 -2  -7 -12
## [3,] 3  8 13 -3  -8 -13 -3  -8 -13
## [4,] 4  9 14 -4  -9 -14 -4  -9 -14
## [5,] 5 10 15 -5 -10 -15 -5 -10 -15