Matrices as Transformations#

One of the most powerful ways to think about matrices is as transformations of space. When you multiply a matrix by a vector, you’re transforming that vector to a new location.

The Key Insight#

A matrix \(A\) transforms a vector \(\mathbf{v}\) into a new vector \(\mathbf{w}\):

\[ \mathbf{w} = A\mathbf{v} \]

Different matrices perform different transformations: rotations, scalings, reflections, shears, and more.

import numpy as np
import matplotlib.pyplot as plt

def plot_transformation(A, vectors=None, title=""):
    """Plot original and transformed vectors."""
    if vectors is None:
        # Default: unit vectors and a test vector
        vectors = np.array([[1, 0], [0, 1], [1, 1]]).T
    
    transformed = A @ vectors
    origins = np.zeros(vectors.shape[1])
    
    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
    
    # Original vectors
    ax1.quiver(origins, origins, vectors[0], vectors[1], angles='xy', scale_units='xy', 
               scale=1, color=['red', 'blue', 'green'], width=0.006)
    ax1.set_xlim(-3, 3)
    ax1.set_ylim(-3, 3)
    ax1.grid(True, alpha=0.3)
    ax1.axhline(0, color='black', linewidth=0.5)
    ax1.axvline(0, color='black', linewidth=0.5)
    ax1.set_aspect('equal')
    ax1.set_title('Original Vectors')
    
    # Transformed vectors
    ax2.quiver(origins, origins, transformed[0], transformed[1], angles='xy', 
               scale_units='xy', scale=1, color=['red', 'blue', 'green'], 
               width=0.006)
    ax2.set_xlim(-3, 3)
    ax2.set_ylim(-3, 3)
    ax2.grid(True, alpha=0.3)
    ax2.axhline(0, color='black', linewidth=0.5)
    ax2.axvline(0, color='black', linewidth=0.5)
    ax2.set_aspect('equal')
    ax2.set_title('Transformed Vectors')
    
    if title:
        fig.suptitle(title, fontsize=14, fontweight='bold')
    
    plt.tight_layout()
    plt.show()

Scaling#

A diagonal matrix scales vectors along each axis:

\[\begin{split} A = \begin{bmatrix} 2 & 0 \\ 0 & 0.5 \end{bmatrix} \end{split}\]

This doubles the \(x\)-component and halves the \(y\)-component.

# Scaling matrix
S = np.array([[2, 0],
              [0, 0.5]])

print("Scaling matrix:")
print(S)

plot_transformation(S, title="Scaling: 2x horizontal, 0.5x vertical")
Scaling matrix:
[[2.  0. ]
 [0.  0.5]]
../_images/3f26acb6127c5a994647371884c6e438bbc197d2b4cf6c4417c677ead2435ffc.png

Rotation#

A rotation matrix rotates vectors by an angle \(\theta\):

\[\begin{split} R(\theta) = \begin{bmatrix} \cos\theta & -\sin\theta \\ \sin\theta & \cos\theta \end{bmatrix} \end{split}\]

Let’s rotate by 90 degrees (\(\pi/2\) radians):

# 90 degree rotation
theta = np.pi / 2
R = np.array([[np.cos(theta), -np.sin(theta)],
              [np.sin(theta), np.cos(theta)]])

print("Rotation matrix (90°):")
print(R)

plot_transformation(R, title="Rotation: 90° counterclockwise")
Rotation matrix (90°):
[[ 6.123234e-17 -1.000000e+00]
 [ 1.000000e+00  6.123234e-17]]
../_images/469fbf6af5b223a29dd5e35e59d8f59ec1bebdb1f9c12f76c5ace6ca20f48b62.png

Reflection#

Reflection across the \(x\)-axis flips the \(y\)-coordinate:

\[\begin{split} F = \begin{bmatrix} 1 & 0 \\ 0 & -1 \end{bmatrix} \end{split}\]
# Reflection across x-axis
F = np.array([[1, 0],
              [0, -1]])

print("Reflection matrix:")
print(F)

plot_transformation(F, title="Reflection: across x-axis")
Reflection matrix:
[[ 1  0]
 [ 0 -1]]
../_images/4b26daf0dafc674ea5d9691a9612c0d5bfaef79d9985dd507a061b42da99187e.png

Shear#

A shear transformation “skews” space:

\[\begin{split} H = \begin{bmatrix} 1 & 1 \\ 0 & 1 \end{bmatrix} \end{split}\]

This adds the \(y\)-coordinate to the \(x\)-coordinate, creating a slant.

# Horizontal shear
H = np.array([[1, 1],
              [0, 1]])

print("Shear matrix:")
print(H)

plot_transformation(H, title="Shear: horizontal")
Shear matrix:
[[1 1]
 [0 1]]
../_images/c2cfdff7c0724b5f3601bd928a31e7f39b60f04fcf45410220a69718856eb903.png

Composing Transformations#

You can combine transformations by multiplying matrices. The order matters!

Let’s rotate by 45° and then scale:

# Rotation by 45 degrees
theta = np.pi / 4
R45 = np.array([[np.cos(theta), -np.sin(theta)],
                [np.sin(theta), np.cos(theta)]])

# Scaling
S = np.array([[2, 0],
              [0, 0.5]])

# Combined: scale THEN rotate
combined = R45 @ S

print("Combined transformation (rotate after scale):")
print(combined)

plot_transformation(combined, title="Combined: Scale then Rotate 45°")
Combined transformation (rotate after scale):
[[ 1.41421356 -0.35355339]
 [ 1.41421356  0.35355339]]
../_images/e5f30c0b6b1fc8b8070f9a10ce50a99d1b8f8a58b54fb8121c4ae1d7a4113fc5.png

Transforming the Grid#

Let’s visualize how a transformation affects an entire grid of points:

def plot_grid_transformation(A, title=""):
    """Plot how a grid is transformed."""
    # Create a grid of points
    x = np.linspace(-2, 2, 9)
    y = np.linspace(-2, 2, 9)
    X, Y = np.meshgrid(x, y)
    
    # Flatten to column vectors
    points = np.vstack([X.ravel(), Y.ravel()])
    
    # Transform
    transformed = A @ points
    
    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
    
    # Original grid
    ax1.scatter(points[0], points[1], c='blue', s=20, alpha=0.6)
    ax1.set_xlim(-3, 3)
    ax1.set_ylim(-3, 3)
    ax1.grid(True, alpha=0.3)
    ax1.axhline(0, color='black', linewidth=0.5)
    ax1.axvline(0, color='black', linewidth=0.5)
    ax1.set_aspect('equal')
    ax1.set_title('Original Grid')
    
    # Transformed grid
    ax2.scatter(transformed[0], transformed[1], c='red', s=20, alpha=0.6)
    ax2.set_xlim(-3, 3)
    ax2.set_ylim(-3, 3)
    ax2.grid(True, alpha=0.3)
    ax2.axhline(0, color='black', linewidth=0.5)
    ax2.axvline(0, color='black', linewidth=0.5)
    ax2.set_aspect('equal')
    ax2.set_title('Transformed Grid')
    
    if title:
        fig.suptitle(title, fontsize=14, fontweight='bold')
    
    plt.tight_layout()
    plt.show()

# Try with a shear transformation
H = np.array([[1, 0.5],
              [0.5, 1]])

plot_grid_transformation(H, title="How a grid is sheared")
../_images/9b826632fd148faea15c3ff305ac2d357d1dfe9644ff5cc8dbec53c8928b2578.png

The Identity Matrix#

The identity matrix \(I\) leaves vectors unchanged:

\[\begin{split} I = \begin{bmatrix} 1 & 0 \\ 0 & 1 \end{bmatrix} \end{split}\]
\[ I\mathbf{v} = \mathbf{v} \]

It’s the “do nothing” transformation.

I = np.eye(2)

v = np.array([2, 3])
result = I @ v

print(f"I @ {v} = {result}")
print("\nThe identity matrix leaves vectors unchanged.")
I @ [2 3] = [2. 3.]

The identity matrix leaves vectors unchanged.

Why This Matters#

Thinking of matrices as transformations is incredibly powerful:

  • Computer Graphics: Every 3D rotation, translation, and projection is a matrix

  • Robotics: Transforming coordinates between robot joints

  • Data Science: PCA and dimensionality reduction are geometric transformations

  • Physics: State transformations in quantum mechanics

When you see matrix multiplication, think: “What transformation is happening here?”

Next Steps#

Now that you understand matrices as transformations, you’re ready to explore: