Chapter 2: Matrices as Linear Transformations
Matrices aren't just grids of numbers — they represent linear maps. Composition, rotation, scaling, and why matrix multiplication is defined the way it is.
A matrix is a function#
The most important reframe in linear algebra: a matrix is not just a table of numbers — it's a linear transformation , defined by .
"Linear" means it preserves vector addition and scalar multiplication:
Matrices act on the basis#
Because a linear map is fully determined by where it sends the basis vectors, the columns of a matrix are exactly the images of the standard basis vectors.
import numpy as np
import matplotlib.pyplot as plt
def plot_transform(A, title):
theta = np.linspace(0, 2 * np.pi, 100)
circle = np.array([np.cos(theta), np.sin(theta)])
transformed = A @ circle
fig, ax = plt.subplots(figsize=(5, 5))
ax.plot(*circle, '--', color='gray', alpha=0.5, label='unit circle')
ax.plot(*transformed, color='steelblue', label='transformed')
e1, e2 = A @ np.array([1, 0]), A @ np.array([0, 1])
ax.arrow(0, 0, *e1, head_width=0.08, color='red', length_includes_head=True)
ax.arrow(0, 0, *e2, head_width=0.08, color='green', length_includes_head=True)
ax.set_xlim(-3, 3); ax.set_ylim(-3, 3)
ax.axhline(0, color='gray', lw=0.5); ax.axvline(0, color='gray', lw=0.5)
ax.set_aspect('equal'); ax.set_title(title); ax.legend(loc='upper left', fontsize=8)
plt.tight_layout()
plt.show()
theta = np.pi / 4
R = np.array([[np.cos(theta), -np.sin(theta)],
[np.sin(theta), np.cos(theta)]])
S = np.array([[2, 0], [0, 0.5]])
A = R @ S
plot_transform(A, "Scale then rotate: A = R · S")Why matrix multiplication is defined that way#
Composing two linear transformations (matrix ) then (matrix ) should itself be linear, representable by a single matrix. That matrix is exactly the matrix product :
This is why matrix multiplication has the (initially odd-looking) row-times-column rule — it's forced by function composition.
import numpy as np
np.random.seed(0)
A = np.random.randn(3, 3)
B = np.random.randn(3, 3)
x = np.random.randn(3)
lhs = B @ (A @ x) # apply A, then B
rhs = (B @ A) @ x # apply combined matrix BA
print("B(Ax) =", np.round(lhs, 4))
print("(BA)x =", np.round(rhs, 4))
print("Equal:", np.allclose(lhs, rhs))The determinant as area/volume scaling#
The determinant of a 2×2 matrix tells you how much the transformation scales area (and whether it flips orientation).
import numpy as np
matrices = {
"Identity": np.eye(2),
"Scale by 2": np.array([[2, 0], [0, 2]]),
"Shear": np.array([[1, 1], [0, 1]]),
"Reflection": np.array([[1, 0], [0, -1]]),
"Singular (rank 1)": np.array([[1, 2], [2, 4]]),
}
for name, M in matrices.items():
print(f"{name:20s} det = {np.linalg.det(M): .3f}")Notice the singular matrix has determinant 0 — it collapses 2D space onto a line, destroying area entirely. This is the same condition as linear dependence of its columns from Chapter 1.
This series continues into systems of equations and null spaces — see "Systems of Linear Equations and the Null Space" for the interactive solver.