Matrices in Python: Rectangular Arrays, Equality, and Element-Wise Operations

Part 1 of 5 in Matrix Algebra: Systems, Pivots, and Inverses

Matrices in Python

A matrix is a rectangular array of numbers arranged in m rows and n columns — conventionally written as an m × n matrix. In mathematics:

(a11a12a1na21a22a2nam1am2amn)\begin{pmatrix} a_{11} & a_{12} & \cdots & a_{1n} \\ a_{21} & a_{22} & \cdots & a_{2n} \\ \vdots & \vdots & \ddots & \vdots \\ a_{m1} & a_{m2} & \cdots & a_{mn} \end{pmatrix}

In Python, the natural representation is a list of lists: the outer list holds rows, each inner list is one row’s values. A[i][j] picks row i, column j (0-indexed).

This post covers three foundational operations — all in pure Python, no NumPy required:

  1. Equality — when two matrices are “the same”
  2. Element-wise addition and subtraction — cell-by-cell arithmetic with dimension guards
  3. Scalar multiplication — scaling every element by one number

Matrices and equality

Two matrices A and B are equal if and only if:

  1. They have the same number of rows (both m),
  2. Each corresponding row has the same length (both n), and
  3. A[i][j] == B[i][j] for every pair (i, j).

Both shape and values must match — a matrix with different dimensions is never equal to another, regardless of what it contains.

The code checks all three conditions:

def matrix_equal(A, B):
    if len(A) != len(B):          # row count?
        return False
    for i in range(len(A)):
        if len(A[i]) != len(B[i]):  # column count per row?
            return False
        for j in range(len(A[i])):
            if A[i][j] != B[i][j]:   # element value?
                return False
    return True

The three comparisons show the rule: matching shape + matching values produces equality, while a mismatch at either layer produces False.

Element-wise addition and subtraction

For two m × n matrices A and B, element-wise operations produce:

Cij=Aij+BijorDij=AijBijC_{ij} = A_{ij} + B_{ij} \quad\text{or}\quad D_{ij} = A_{ij} - B_{ij}

This is only defined when both matrices share the same dimensions — unlike array broadcasting in NumPy, plain Python lists need an explicit shape guard:

def matrix_add(A, B):
    shA, shB = shape(A), shape(B)
    if shA is None or shB is None or shA != shB:
        return None   # dimension mismatch — not just silently wrong
    rows, cols = shA
    return [[A[i][j] + B[i][j] for j in range(cols)]
                  for i in range(rows)]

The third case — adding a 2×3 matrix to a 2×2 matrix — produces None. That’s the guard doing its job: it prevents producing garbage by rejecting inputs that don’t form a valid operation.

Scalar multiplication

Here’s where scalar multiplication differs from addition and subtraction. For any scalar k and any matrix A:

Cij=kAijC_{ij} = k \cdot A_{ij}

There is no dimension constraint — you can scale any matrix by any number:

def scalar_multiply(A, k):
    rows = len(A)
    cols = len(A[0]) if rows else 0
    return [[k * A[i][j] for j in range(cols)]
                  for i in range(rows)]

A negative scalar flips signs, and a fractional scalar like 3.5 smoothly interpolates — every element gets the same treatment.

Takeaway

A matrix in Python is a list of equal-length lists. Equality demands shape and values match. Addition and subtraction require matching shapes as a pre-condition; scalar multiplication works on anything, any time. The pattern behind all three operations is the same: iterate cell by cell, do your math, collect into a new list of lists.