Code

Chapter 2

Code block 2.1/2.2

aScalar <- 4

Code block 2.3/2.4

This is a ggplot2 rather than base R version. But ggplot2 figures do look so much better.

library(ggplot2)
## Warning: package 'ggplot2' was built under R version 4.0.4
v <- c(2, -1)
ggplot(data=NULL, aes(x = c(0, v[1]), y=c(0, v[2])))  + 
  geom_line() + 
  geom_point(aes(x=v[1], y=v[2])) + 
  scale_x_continuous(name=expression(paste(X[1], " dim.")), limits = c(-3, 3)) + 
  scale_y_continuous(name=expression(paste(X[2], " dim.")), limits = c(-3, 3)) + 
  coord_equal()

Code block 2.5/2.6

In R atomic vectors are created via c() function but, technically, they are neither column, nor row vectors. Matrix multiplication manual states that it “multiplies two matrices, if they are conformable. If one argument is a vector, it will be promoted to either a row or column matrix to make the two arguments conformable. If both are vectors of the same length, it will return the inner product (as a matrix).” At the same time, transposing an atomic vector t(c(...)) transforms it into a single row matrix, thus a row vector, hinting that deep down an outcome of c() is a column vector.

To avoid ambiguity, I will use single row and single column matrices for, respectively, row and column vectors.

v1 <- matrix(c(2, 5, 5, 7), nrow = 1) # row vector
v2 <- matrix(c(2, 5, 5, 7), ncol = 1) # column vector
v3 <- matrix(c(2, 5, 5, 7))           # also a column vector

Code block 2.7/2.8

v1 <- matrix(c(2, 5, 5, 7), nrow = 1) # row vector
v1a <- t(c(2, 5, 5, 7))               # also a row vector
v2 <- t(v1)                           # column vector

Code block 2.9/2.10

For this example, you can also use atomic vectors directly without turning them into a column vector / single column matrix.

v1 <- matrix(c(2, 5, 4, 7), ncol=1)
v2 <- matrix(c(4, 1, 0, 2), ncol=1)
v3 <- 4 * v1  - 2 * v2

Chapter 3

Code block 3.1/3.2

There is no explicit dot product function in base R but there are multiple implementations in various libraries such as pracma used here.

v1 <- matrix(c(2, 5, 4, 7), ncol=1)
v2 <- matrix(c(4, 1, 0, 2), ncol=1)
dp <- pracma::dot(v1, v2)

However, a matrix multiplication of atomic vectors (we can convert a matrix back to an atomic vector via c() or as.vector()) gives us the dot product (well, the inner product, which is why the result is a 1-by-1 matrix).

dp <- c(v1) %*% as.vector(v2)

Code block 3.3/3.4

l1 <- 1
l2 <- 2
l3 <- -3
v1 <- matrix(c(4, 5, 1), ncol=1)
v2 <- matrix(c(-4, 0, -4), ncol=1)
v3 <- matrix(c(1, 3, 2), ncol=1)
l1 * v1 + l2 * v2 + l3 * v3
##      [,1]
## [1,]   -7
## [2,]   -4
## [3,]  -13

Code block 3.5/3.6

To compute the outer product, we must use atomic vectors, thus I’ve skipped the whole creating-column-vector-as-a-matrix thing.

v1 <- c(2, 5, 4, 7)
v2 <- c(4, 1, 0, 2)
op <- outer(v1, v2)
op <- v1 %o% v2 # alternative call as operation

Alternatively, if we do start with vectors as single column matrices…

v1 <- matrix(c(2, 5, 4, 7), ncol=1)
v2 <- matrix(c(4, 1, 0, 2), ncol=1)
op <- outer(c(v1), c(v2))
op <- c(v1) %o% c(v2) # alternative call as operation

Code block 3.7/3.8

v1 <- matrix(c(2, 5, 4, 7), ncol=1)
v2 <- matrix(c(4, 1, 0, 2), ncol=1)
v3 <- v1 * v2

Code block 3.9/3.10

Please note that you need to explicitly specify the 2-norm via type="2", as the one norm is used by default.

v <- matrix(c(2, 5, 4, 7), ncol=1)
vMag <- norm(v, type="2")
v_unit <- v / vMag

Chapter 5

Code block 5.1/5.2

There is only one way to transpose a matrix in R: via function t().

A <- matrix(runif(n=2*5), nrow=2, ncol=5)
At1 <- t(A)

Code block 5.3/5.4

I <- diag(4)
O <- matrix(1, nrow=4, ncol=1) # or rep(1, times=4) to create an atomic vector
Z <- matrix(0, nrow=4, ncol=4)

Code block 5.5/5.6

D <- diag(c(1, 2, 3, 5))  # diagonal matrix
R <- matrix(runif(n = 3 * 4), nrow=3, ncol=4)
d <- diag(R) # diagonal elements

Code block 5.7/5.8

In r cbind() and rbind() concatenate, respectively, by column and row.

A <- matrix(runif(n = 3 * 5), nrow=3, ncol=5)
B <- matrix(runif(n = 3 * 4), nrow=3, ncol=4)
AB <- cbind(A, B)

Code block 5.9/5.10

There are two ways to compute lower and upper triangular parts of a matrix. First, to use tril() and triu() function from pracma library.

A <- matrix(runif(n = 5 * 5), nrow=5, ncol=5)
L <- pracma::tril(A)
U <- pracma::triu(A)

Alternatively, you can use base R functions lower.tri() and upper.tri() that give you a matrix of the same size with logical values indicating whether an element belongs to, respectively, lower or upper triangle. Note that by default, the diagonal is not included!

A <- matrix(runif(n = 5 * 5), nrow=5, ncol=5)

L <- A
# by setting the UPPER triangular part to 0, we get the LOWER triangular part and the diagonal
L[upper.tri(L)] <- 0

U <- A
# by setting the LOWER triangular part to 0, we get the UPPER triangular part and the diagonal
U[lower.tri(U)] <- 0

Code block 5.11/5.12

Note that there is a toeplitz() function in stats (base R) and Topelitz() (note the first capital letter) in pracma library. Here, I use base R version.

t <- c(1, 2, 3, 4)
T <- stats::toeplitz(t)
H <- pracma::hankel(t, b= c(t[-1], t[1]))
## Warning in pracma::hankel(t, b = c(t[-1], t[1])): a[n] not equal to b[1], b[1]
## set to a[n].

Code block 5.13/5.14

l <- 0.01
I <- diag(4)
A <- matrix(runif(4 * 4), nrow=4, ncol=4)
As <- A + l * I

Code block 5.15/5.16

Base R does not implement trace function, as it is simply a sum(diag(M)). However, you can use Trace() from pracma library.

A <- matrix(runif(4 * 4), nrow=4, ncol=4)
tr <- pracma::Trace(A)

Chapter 6

Code block 6.1/6.2

M1 <- matrix(runif(n=4*3), nrow=4, ncol=3)
M2 <- matrix(runif(n=3*5), nrow=3, ncol=5)
C <- M1 %*% M2

Code block 6.3/6.4

A <- matrix(runif(n=2*2), nrow=2, ncol=2)
B <- matrix(runif(n=2*2), nrow=2, ncol=2)
C1 <- A %*% B
C2 <- B %*% A

Code block 6.5/6.6

M1 <- matrix(runif(n=4*3), nrow=4, ncol=3)
M2 <- matrix(runif(n=4*3), nrow=4, ncol=3)
C <- M1 * M2

Code block 6.7/6.8

Note that by default, matrix is constructed by column. To match the code, we need to use byrow=TRUE option.

A <- matrix(c(1, 2, 3, 4, 5, 6), nrow=2, byrow = TRUE)
c(A)
## [1] 1 4 2 5 3 6

Code block 6.9/6.10

A <- matrix(runif(n=4*3), nrow=4, ncol=3)
B <- matrix(runif(n=4*3), nrow=4, ncol=3)
f <- pracma::Trace(t(A) %*% B)

Code block 6.11/6.12

A <- matrix(runif(n=4*3), nrow=4, ncol=3)
norm(A, type="F")
## [1] 2.097149

Chapter 7

Code block 7.1/7.2

You can use Rank() from the pracma library. Alternatively, you can use rankMatrix() function from the Matrix library, which in addition to the rank itself, returns information on the method used to estimate the rank via attributes.

A <- matrix(runif(n=3*6), nrow=3, ncol=6)
r1 <- pracma::Rank(A)
r2 <- Matrix::rankMatrix(A)

Code block 7.3/7.4

s <- runif(n=1)
A <- matrix(runif(n=3*5), nrow=3, ncol=5)
r1 <- pracma::Rank(A)
r2 <- pracma::Rank(s * A)
print(c(r1, r2))
## [1] 3 3

Code block 7.5/7.6

Source code for Rank() function from pracma library.

pracma::Rank
## function (M) 
## {
##     if (length(M) == 0) 
##         return(0)
##     if (!is.numeric(M)) 
##         stop("Argument 'M' must be a numeric matrix.")
##     if (is.vector(M)) 
##         M <- matrix(c(M), nrow = length(M), ncol = 1)
##     r1 <- qr(M)$rank
##     sigma <- svd(M)$d
##     tol <- max(dim(M)) * max(sigma) * .Machine$double.eps
##     r2 <- sum(sigma > tol)
##     if (r1 != r2) 
##         warning("Rank calculation may be problematic.")
##     return(r2)
## }
## <bytecode: 0x0000000015c3e448>
## <environment: namespace:pracma>

Source code for rankMatrix() function from Matrix library.

Matrix::rankMatrix
## function (x, tol = NULL, method = c("tolNorm2", "qr.R", "qrLINPACK", 
##     "qr", "useGrad", "maybeGrad"), sval = svd(x, 0, 0)$d, warn.t = TRUE) 
## {
##     stopifnot(length(d <- dim(x)) == 2)
##     p <- min(d)
##     method <- match.arg(method)
##     if (useGrad <- (method %in% c("useGrad", "maybeGrad"))) {
##         stopifnot(length(sval) == p, diff(sval) <= 0)
##         if (sval[1] == 0) {
##             useGrad <- FALSE
##             method <- eval(formals()[["method"]])[[1]]
##         }
##         else {
##             ln.av <- log(abs(sval))
##             diff1 <- diff(ln.av)
##             if (method == "maybeGrad") {
##                 grad <- (min(ln.av) - max(ln.av))/p
##                 useGrad <- !is.na(grad) && min(diff1) <= min(-3, 
##                   10 * grad)
##             }
##         }
##     }
##     if (!useGrad) {
##         x.dense <- is.numeric(x) || is(x, "denseMatrix")
##         if ((Meth <- method) == "qr") 
##             method <- if (x.dense) 
##                 "qrLINPACK"
##             else "qr.R"
##         else Meth <- substr(method, 1, 2)
##         if (Meth == "qr") {
##             if (is.null(tol)) 
##                 tol <- max(d) * .Machine$double.eps
##         }
##         else {
##             if (is.null(tol)) {
##                 if (!x.dense && missing(sval) && prod(d) >= 100000L) 
##                   warning(gettextf("rankMatrix(<large sparse Matrix>, method = '%s') coerces to dense matrix.\n Probably should rather use method = 'qr' !?", 
##                     method), immediate. = TRUE, domain = NA)
##                 stopifnot(diff(sval) <= 0)
##                 tol <- max(d) * .Machine$double.eps
##             }
##             else stopifnot((tol <- as.numeric(tol)[[1]]) >= 0)
##         }
##     }
##     structure(if (useGrad) 
##         which.min(diff1)
##     else if (Meth == "qr") {
##         if ((do.t <- (d[1L] < d[2L])) && warn.t) 
##             warning(gettextf("rankMatrix(x, method='qr'): computing t(x) as nrow(x) < ncol(x)"))
##         q.r <- qr(if (do.t) 
##             t(x)
##         else x, tol = tol, LAPACK = method != "qrLINPACK")
##         if (x.dense && (method == "qrLINPACK")) 
##             q.r$rank
##         else {
##             diagR <- if (x.dense) 
##                 diag(q.r$qr)
##             else diag(q.r@R)
##             d.i <- abs(diagR)
##             if ((mdi <- max(d.i)) > 0) 
##                 sum(d.i >= tol * mdi)
##             else 0L
##         }
##     }
##     else if (sval[1] > 0) 
##         sum(sval >= tol * sval[1])
##     else 0L, method = method, useGrad = useGrad, tol = if (useGrad) 
##         NA
##     else tol)
## }
## <bytecode: 0x00000000204f0388>
## <environment: namespace:Matrix>

Chapter 8

Code block 8.1/8.2

A <- matrix(runif(n=3*4), nrow=3, ncol=4)
pracma::nullspace(A)
##             [,1]
## [1,] -0.55208467
## [2,]  0.67249530
## [3,]  0.48849457
## [4,]  0.06576957

Chapter 9

Code block 9.1/9.2

z <- complex(real=3, imaginary=4)
Z <- complex(length.out=2, real=0, imaginary=0)
Z[1] <- 3 + 4i

Code block 9.3/9.4

Base R does not have a function that generates random integers on the interval. I will use sample() with replacement from the range of integers to replicate this. Also note that I have renamed i to im to minimize the confusion.

r <- sample(-3:4, size=3, replace = TRUE)
im <- sample(-3:4, size=3, replace = TRUE)
Z <- r + im * 1i
print(Z)
## [1] -3-3i  1+4i  3+3i
print(Conj(Z))
## [1] -3+3i  1-4i  3-3i

Code block 9.5/9.6

pracma::dot() implements the Hermitian dot product.

v <- c(0, 1i)
pracma::dot(v, v)
## [1] 1+0i

Chapter 10

Code block 10.1/10.2

You can use pracma::lu() function but note that it works only on square, positive definite matrices.

# Using the square matrix from practice problem b
A <- matrix(c(2, 0, 1, 1, 1, 2, 3, 1, 3), nrow=3, byrow=TRUE)
pracma::lu(A)
## $L
##      [,1] [,2] [,3]
## [1,]  1.0    0    0
## [2,]  0.5    1    0
## [3,]  1.5    1    1
## 
## $U
##      [,1] [,2] [,3]
## [1,]    2    0  1.0
## [2,]    0    1  1.5
## [3,]    0    0  0.0

Code block 10.3/10.4

A <- matrix(runif(n=2*4), nrow=2, ncol=4)
pracma::rref(A)
##      [,1] [,2]      [,3]      [,4]
## [1,]    1    0  2.157181  1.754766
## [2,]    0    1 -1.707549 -2.123415

Chapter 11

Code block 11.1/11.2

A <- matrix(runif(n=3*3), nrow=3, ncol=3)
det(A)
## [1] 0.2765355

Chapter 12

Code block 12.1/12.2

In R, you find the inverse via solve(). The latter solves an equation \(Ax = b\), omitting the second argument defaults b = I and the equation is solved to find the inverse. I have added round() to make it easier to see that \(A A^-1\) produces an identity matrix.

A <- matrix(rnorm(n=3*3), nrow=3, ncol=3)
Ai <- solve(A)
round(A %*% Ai, 4)
##      [,1] [,2] [,3]
## [1,]    1    0    0
## [2,]    0    1    0
## [3,]    0    0    1

Code block 12.3/12.4

A <- matrix(rnorm(n=3*3), nrow=3, ncol=3)
Acat <- cbind(A, diag(1, nrow=3, ncol=3))
Ar <- pracma::rref(Acat) # RREF
Ar <- Ar[, 4:6] # keep inverse
Ai <- solve(A)
round(Ar - Ai, 4)
##      [,1] [,2] [,3]
## [1,]    0    0    0
## [2,]    0    0    0
## [3,]    0    0    0

Code block 12.5/12.6

A <- matrix(rnorm(n=5*3), nrow=5, ncol=3)
Al <- solve(t(A) %*% A) %*% t(A)
round(Al %*% A, 4)
##      [,1] [,2] [,3]
## [1,]    1    0    0
## [2,]    0    1    0
## [3,]    0    0    1

Code block 12.7/12.8

A <- matrix(rnorm(n=3*3), nrow=3, ncol=3)
A[2, ] <- A[1, ]
Api <- pracma::pinv(A)
Api %*% A
##             [,1]        [,2]       [,3]
## [1,]  0.62174449 -0.48151760 0.05761148
## [2,] -0.48151760  0.38703022 0.07333917
## [3,]  0.05761148  0.07333917 0.99122529

Chapter 13

Code block 13.1/13.2

A <- matrix(c(1, 2, 3, 1, 1, 1), nrow=3, byrow=TRUE)
b <- matrix(c(5.5, -3.5, 1.5), nrow=3)
lsfit(A, b)$coefficients
## Intercept        X1        X2 
##       0.0      -2.5       4.0

Code block 13.3/13.4

In R, you first perform QT decomposition via qr() and get an object. Then, you can extract component matrices of the decomposition via qr.Q() and qr.R().

A <- matrix(rnorm(n=4*3), nrow=4, ncol=3)
QR <- qr(A)
Q <- qr.Q(QR)
R <- qr.R(QR)

Chapter 15

Code block 15.1/15.2

A <- matrix(c(2, 3, 3, 2), nrow=2, ncol=2, byrow=TRUE)
eigenA <- eigen(A)
V <- eigenA$vectors
L <- eigenA$values

Code block 15.3/15.4

n <- 3
A <- matrix(rnorm(n=n^2), nrow=n, ncol=n)
B <- matrix(rnorm(n=n^2), nrow=n, ncol=n)
eigenAB <- geigen::geigen(A, B)
evecs <- eigenAB$vectors
evals <- eigenAB$vals

Chapter 16

Code block 16.1/16.2

Note that by default number of left (matrix U) and right (matrix V) singular vectors is determined as, respectively, nu = min(n, p) and nv = min(n, p) for the n × p matrix. Therefore, I included nv=ncol(A) to replicate output by Python. Also note that R, like Matlab, return V. Also note that singular values attribute is d and it is a vector not a diagonal matrix.

A <- matrix(c(1, 1, 0, 0, 1, 1), nrow=2, ncol=3, byrow=TRUE)
svdA <- svd(A, nv=ncol(A))
U <- svdA$u
s <- svdA$d
V <- svdA$v

Code block 16.3/16.4

A <- matrix(rnorm(n=5^2), nrow=5, ncol=5)
s <- svd(A)$d
condnum <- max(s) / min(s)

#compare above with pracma::cond() 
print(c(condnum, pracma::cond(A)))
## [1] 13.12641 13.12641

Chapter 17

Code block 17.1/17.2

m <- 4
A <- matrix(rnorm(n=m^2), nrow=m, ncol=m)
v <- matrix(rnorm(n=m), nrow=1, ncol=m)
v %*% A %*% t(v)
##           [,1]
## [1,] -2.010692