15  Matrices and arrays

R matrices and arrays can be understood as higher-dimensional generalizations of R vectors. As with vectors, it is important in programming to distinguish matrices from the mathematical objects of the same name. Thus, in programming, matrices can simply serve for the tabular storage of numerical data, without the primary goal necessarily being the application of mathematical matrix calculus. Conversely, elementwise arithmetic operations with matrices in programming represent rather special mathematical operations such as the Hadamard product in mathematics. In this section, we primarily want to examine matrices and arrays as data containers in imperative programming with R. An introduction to matrix calculus with the same data structures is given in Chapter 9.

15.1 Matrices

Abstractly speaking, R matrices are two-dimensional, rectangular data structures of the form \[\begin{equation} M = \begin{pmatrix} m_{11} & m_{12} & \cdots & m_{1n_c} \\ m_{21} & m_{22} & \cdots & m_{2n_c} \\ \vdots & \vdots & \ddots & \vdots \\ m_{n_r1} & m_{n_r2} & \cdots & {m_{n_rn_c}} \\ \end{pmatrix} \end{equation}\] The elements \(m_{ij}, i = 1,...,n_r, j = 1,...,n_c\) of the matrix are necessarily of the same data type. In the above scheme, \(n_r\) denotes the number of rows and \(n_c\) denotes the number of columns of the matrix \(M\). Each element of a matrix therefore has a row index \(i\) and a column index \(j\). Intuitively, R matrices are thus numerically indexed tables. Formally, matrices in R are atomic vectors interpreted two-dimensionally.

15.1.1 Generation

The R function matrix() is used to generate a matrix; it fills matrices with the elements of a vector. The general syntax of this function is M = matrix(v, nrow, ncol, byrow), where

  • v denotes a vector of length nrow\(\cdot\) ncol,
  • nrow denotes the desired number of rows of M,
  • ncol denotes the desired number of columns of M,
  • the logical variable byrow denotes the filling order of M.

Since, for a given length of v and a given number of rows or columns, the corresponding number of columns or rows of the matrix follows, it is only necessary to specify either nrow or ncol, as the following examples show.

matrix(c(1:12), nrow = 3)                # 3 x 4 matrix of the numbers 1,...,12, byrow = FALSE
     [,1] [,2] [,3] [,4]
[1,]    1    4    7   10
[2,]    2    5    8   11
[3,]    3    6    9   12
matrix(c(1:12), ncol = 4)                # 3 x 4 matrix of the numbers 1,...,12, byrow = FALSE
     [,1] [,2] [,3] [,4]
[1,]    1    4    7   10
[2,]    2    5    8   11
[3,]    3    6    9   12
matrix(c(1:12), nrow = 3, byrow = TRUE)  # 3 x 4 matrix of the numbers 1,...,12, byrow = TRUE
     [,1] [,2] [,3] [,4]
[1,]    1    2    3    4
[2,]    5    6    7    8
[3,]    9   10   11   12

Existing matrices with the same number of rows can be concatenated columnwise with the function cbind().

A = matrix(c(1:4) , nrow = 2)  # 2 x 2 matrix of the numbers 1,...,4
A
     [,1] [,2]
[1,]    1    3
[2,]    2    4
B = matrix(c(5:10), nrow = 2)  # 2 x 3 matrix of the numbers 5,...,10
B
     [,1] [,2] [,3]
[1,]    5    7    9
[2,]    6    8   10
C = cbind(A,B)                 # columnwise concatenation of A and B
C
     [,1] [,2] [,3] [,4] [,5]
[1,]    1    3    5    7    9
[2,]    2    4    6    8   10

Equivalently, existing matrices with the same number of columns can be concatenated rowwise with the function rbind().

A = matrix(c(1:6) , nrow = 2, byrow = TRUE)  # 2 x 3 matrix of the numbers 1,...,6
print(A)
     [,1] [,2] [,3]
[1,]    1    2    3
[2,]    4    5    6
B = matrix(c(7:9), nrow = 1)                 # 1 x 3 matrix of the numbers 7,...,9
print(B)
     [,1] [,2] [,3]
[1,]    7    8    9
C = rbind(A,B)                               # rowwise concatenation of A and B
print(C)
     [,1] [,2] [,3]
[1,]    1    2    3
[2,]    4    5    6
[3,]    7    8    9

15.1.2 Characterization

The familiar function typeof() can be used to output the elementary data type of a matrix:

typeof(matrix(c(1,0,0,1)    , nrow = 2))  # 2 x 2 double matrix
[1] "double"
typeof(matrix(c(1L,0L,0L,1L), nrow = 2))  # 2 x 2 integer matrix
[1] "integer"
typeof(matrix(c(T,T,F,F)    , nrow = 2))  # 2 x 2 logical matrix
[1] "logical"
typeof(matrix(c("a","b","c"), nrow = 1))  # 1 x 3 character matrix
[1] "character"

The number of rows or columns of a matrix can be output with the functions nrow() (number of rows) and ncol() (number of columns).

C = matrix(1:12, nrow = 3)  # 3 x 4 matrix
nrow(C)                     # number of rows
[1] 3
ncol(C)                     # number of columns
[1] 4

15.1.3 Indexing

In general, matrix elements are indexed with a row index and a column index. The first of two matrix indices always indexes the row, and the second always indexes the column. The remaining principles of matrix indexing correspond essentially to the principles of vector indexing. One special feature is that a missing row or column index indexes all elements of the corresponding dimension.

A = matrix(c(2:7)^2, nrow = 2)  # 2 x 3 matrix of the numbers 2^2,...,7^2
A
     [,1] [,2] [,3]
[1,]    4   16   36
[2,]    9   25   49
A[1,3]                          # element in row 1, column 3 of A [36]
[1] 36
A[2,2]                          # element in row 2, column 2 of A [25]
[1] 25
A[2,]                           # all elements of row 2 [9,25,49]
[1]  9 25 49
A[,3]                           # all elements of column 3 [36,49]
[1] 36 49
A[1:2,1:2]                      # submatrix of the first two rows and columns
     [,1] [,2]
[1,]    4   16
[2,]    9   25
A[A>10]                         # elements of A greater than 10 [16,25,36,49]
[1] 16 25 36 49
A[1,c(F,F,T)]                   # element in row 1, column 3 of A [36]
[1] 36

15.1.4 Arithmetic

If one applies unary arithmetic operators and functions to matrices, these are evaluated elementwise, as with vectors.

A = matrix(c(1:4), nrow = 2)  # 2 x 2 matrix of the numbers 1,2,3,4
A
     [,1] [,2]
[1,]    1    3
[2,]    2    4
B = A^2                       # result B[i,j] = A[i,j]^2, 1 <= i,j <= 2
B
     [,1] [,2]
[1,]    1    9
[2,]    4   16
C = sqrt(B)                   # result C[i,j] = sqrt(A[i,j]^2), 1 <= i,j <= 2
C
     [,1] [,2]
[1,]    1    3
[2,]    2    4
D = exp(A)                    # result D[i,j] = exp(A[i,j]), 1 <= i,j <= 2
D
         [,1]     [,2]
[1,] 2.718282 20.08554
[2,] 7.389056 54.59815

Like vectors, matrices with identical numbers of rows and columns can be linked elementwise with the binary arithmetic operators +, -, *, and /. The following examples illustrate this:

A = matrix(c(1:4), nrow = 2)  # 2 x 2 matrix of the numbers 1,2,3,4
A
     [,1] [,2]
[1,]    1    3
[2,]    2    4
B = matrix(c(5:8), nrow = 2)  # 2 x 2 matrix of the numbers 5,6,7,8
B
     [,1] [,2]
[1,]    5    7
[2,]    6    8
C = A + B                     # result C[i,j] = A[i,j] + B[i,j], 1 <= i,j <= 2
C
     [,1] [,2]
[1,]    6   10
[2,]    8   12
D = A * B                     # result D[i,j] = A[i,j] * B[i,j], 1 <= i,j <= 2
D
     [,1] [,2]
[1,]    5   21
[2,]   12   32

In addition, R matrices can be used for computations in the sense of matrix calculus from linear algebra. We show only a few examples here; a detailed presentation of the mathematical principles and their R implementation can be found in Chapter 9.

C = A %*% B       # 2 x 2 matrix product
C
     [,1] [,2]
[1,]   23   31
[2,]   34   46
A_T = t(A)        # transposition of A
A_T
     [,1] [,2]
[1,]    1    2
[2,]    3    4
A_inv = solve(A)  # inverse of A
A_inv
     [,1] [,2]
[1,]   -2  1.5
[2,]    1 -0.5
A_det = det(A)    # determinant of A
A_det
[1] -2

15.1.5 Attributes

Formally, matrices are atomic vectors with a dim attribute.

A = matrix(1:12, nrow = 4 )  # 4 x 3 matrix
attributes(A)                # call the attributes of A
$dim
[1] 4 3

As with vectors, one can also give names to matrix elements, although this is rather unusual for matrices as well. Specifically, the functions rownames() and colnames() define the matrix attribute dimnames.

rownames(A) = c("P1","P2","P3","P4")  # naming the rows of A
colnames(A) = c("Age", "Hgt", "Wgt")  # naming the columns of A
A                                     # matrix with attribute dimnames
   Age Hgt Wgt
P1   1   5   9
P2   2   6  10
P3   3   7  11
P4   4   8  12
attr(A, "dimnames")                   # call the attribute dimnames
[[1]]
[1] "P1" "P2" "P3" "P4"

[[2]]
[1] "Age" "Hgt" "Wgt"

15.2 Arrays

R arrays are \(d\)-dimensional, hyperrectangular data structures. Intuitively, arrays can be understood as the generalization of matrices to more than two dimensions. For \(d = 1\), an array corresponds to a vector; for \(d = 2\), an array corresponds to a matrix. As with vectors and matrices, the elements of an array must be of the same data type. Arrays are characterized by how many elements \(d_i, i = 1,...,d\) the \(i\)-th dimension can represent. Each element of an array is indexed by a \(d\)-dimensional index \(i_1,i_2,...,i_d\).

15.2.1 Generation

Analogously to the function matrix(), the function array() is used to fill an array with vector elements. The general syntax of this function is A = array(v, dim), where v is a vector of array data and dim is a vector of integer values that represents the maximal indices in each of the length(dim) dimensions of the array. For example, the following command generates a \(2 \times 2 \times 3\) array.

A = array(1:12, dim = c(2,2,3))  # 2 x 2 x 3 array of the numbers 1,...,12
A
, , 1

     [,1] [,2]
[1,]    1    3
[2,]    2    4

, , 2

     [,1] [,2]
[1,]    5    7
[2,]    6    8

, , 3

     [,1] [,2]
[1,]    9   11
[2,]   10   12

Three-dimensional arrays can be imagined intuitively as matrices placed one behind another in space. The third dimension then represents the “pages” of the array. However, when working with higher-dimensional arrays, it is useful to try to detach oneself from spatial conceptions of arrays and instead understand arrays as data containers whose elements can be addressed uniquely by their indices.

15.2.2 Characterization

The length() function outputs the number of elements of an array.

A = array(1:12, dim = c(2,2,3))  # 2 x 2 x 3 array of the numbers 1,...,12
length(A)                        # number of elements of the array
[1] 12

The function typeof() outputs the elementary data type of an array:

typeof(A)  # type of the array
[1] "integer"

The function dim() outputs the dimensions of an array:

dim(A)  # dimension of the array
[1] 2 2 3

15.2.3 Indexing

Indexing of arrays is analogous to vector and matrix indexing, with the difference that three or more indices are needed to index an array element.

A     = array(1:12, dim = c(2,2,3))  # 2 x 2 x 3 array of the numbers 1,...,12
a_223 = A[2,2,3]                     # array element with index address [2,2,3] (12)

15.2.4 Arithmetic

As with vectors and matrices, unary arithmetic operators are evaluated elementwise for arrays. Likewise, binary arithmetic operators are evaluated elementwise for arrays of identical dimensionality.

A = array(1:4, dim = c(1,2,2))  # 1 x 2 x 2 array of the numbers 1,...,4
A
, , 1

     [,1] [,2]
[1,]    1    2

, , 2

     [,1] [,2]
[1,]    3    4
B = array(5:8, dim = c(1,2,2))  # 1 x 2 x 2 array of the numbers 5,...,8
B
, , 1

     [,1] [,2]
[1,]    5    6

, , 2

     [,1] [,2]
[1,]    7    8
C = A^2                         # elementwise exponentiation
C
, , 1

     [,1] [,2]
[1,]    1    4

, , 2

     [,1] [,2]
[1,]    9   16
D = A + B                       # elementwise addition
D
, , 1

     [,1] [,2]
[1,]    6    8

, , 2

     [,1] [,2]
[1,]   10   12

15.2.5 Attributes

Formally, arrays are atomic vectors with a dim attribute.

A = array(1:12, dim = c(2,2,3))  # 2 x 2 x 3 array of the numbers 1,...,12
attributes(A)                    # call the attributes of A
$dim
[1] 2 2 3

As with matrices, the function dimnames() can be used to name the array dimensions, but here too the naming of array dimensions is rather unusual.