1.3 Matrices


: 25 minutes

A matrix is a rectangular grid of numbers, much like a spreadsheet. In data work the convention is nearly universal: rows are records (one observation each) and columns are features (one measured attribute each).

Dragging our hypothetical BMI study a little further, imagine measuring the feature vector (weight, height, age) once for each of the 25 students in the class. We could keep 25 separate variables student1, student2, …, each a vector—but then every question about the data (“what is the average height?”) turns into a loop over 25 variable names. Stacking the 25 feature vectors as rows of one rectangular object is both cheaper to store and far easier to manipulate. That object is a matrix, and it looks like a table:

A data matrix: records down, features across
student weight height age
student 1 153 68 46
student 2 196 55 30
student 25 163 58 26

We denote matrices by bold capital letters (e.g., \bold{X}, \bold{Y}, and \bold{Z}). The expression \bold{A} \in \mathbb{R}^{m \times n} indicates that a matrix \bold{A} contains m \times n real-valued scalars, arranged as m rows and n columns. Alternatively, we say the size (or shape) of \bold{A} is (m,n).

When m = n, we say that a matrix is square. To refer to an individual element, we subscript both the row and column indices, e.g., a_{ij} is the value that belongs to \bold{A}’s i^{\textrm{th}} row and j^{\textrm{th}} column:

\bold{A}=\begin{bmatrix} 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{bmatrix}. \tag{3.1}

Row first, then column. In a_{ij} the row index always comes first, and in code the outer index always selects a row. Memorise this now; almost every shape error you will hit this semester traces back to it.

A matrix can be read in two complementary ways, and switching between them freely is the skill this module is really teaching:

Note Matrix Size

Let \bold{A} denote the matrix containing (along each row) the populations of the largest three cities in each of the states in the US. What is the size of \bold{A}?

(50, 3)

Some Named Matrices

A handful of shapes come up so often that they have names. Let \bold{A} be square, of size n\times n.

  • The zero matrix \bold{0} has every entry equal to 0 (it need not be square).
  • \bold{A} is diagonal if a_{ij}=0 whenever i\neq j; only the entries a_{11},\ldots,a_{nn} may be non-zero.
  • The identity matrix \bold{I}_n is the diagonal matrix whose diagonal entries are all 1. It plays the role of the number 1: \bold{I}\bold{A}=\bold{A}\bold{I}=\bold{A}.
  • \bold{A} is upper triangular if a_{ij}=0 whenever i>j, and lower triangular if a_{ij}=0 whenever i<j.
  • \bold{A} is symmetric if a_{ij}=a_{ji} for all i,j, i.e. it is unchanged by reflection across the diagonal.

\bold{I}_3=\begin{bmatrix}1&0&0\\0&1&0\\0&0&1\end{bmatrix},\quad \bold{D}=\begin{bmatrix}2&0&0\\0&-1&0\\0&0&5\end{bmatrix},\quad \bold{U}=\begin{bmatrix}1&7&3\\0&4&2\\0&0&6\end{bmatrix},\quad \bold{S}=\begin{bmatrix}1&7&3\\7&4&2\\3&2&6\end{bmatrix}.

Symmetric matrices are not a curiosity: a correlation matrix and a covariance matrix are both symmetric, and so is the \bold{A}^T\bold{A} that shows up in the normal equations of regression.

Matrices in Python

Without NumPy, the closest thing Python offers is a list of lists: the outer list holds the rows, and each inner list is one row.

Indexing chains the two positions, outer first:

Getting a column is harder, and that asymmetry is the point. There is no X[:][1] shortcut that works—you have to visit every row:

Nothing in Python stops you from building a ragged list of lists whose rows have different lengths, which is not a matrix at all. Nothing checks it for you either—until NumPy next week, where rectangularity, shape, and column access all come for free.

Note Matrix Orientation

A data matrix X is stored as a list of lists. Each row is one student, and the three columns are (weight, height, age), in that order, for 25 students. What does X[2][0] return?

Two conventions have to be combined here, and mixing them up is the most common bug of the week.

  1. Rows come first. X[2] selects a record, not a feature: it is the entire feature vector of one student. There is no way to reach a column with a single index into a list of lists—for a column you must walk the rows, as in [row[0] for row in X].
  2. Indices are zero-based. X[2] is the third student and [0] is the first feature. In the mathematical notation a_{ij} the same entry would be written a_{31}.

So X[2][0] is the weight of the third student. Note that the shape of X is (25, 3): 25 rows, 3 columns—records down, features across.

Exercises

Note Row or Column?

X holds four students, one per row, with columns (weight, height, age). A classmate wants the height column and writes X[1]. It returns a list of three numbers, which looks convincing—and is the wrong three numbers.

Run their line, see what they actually got, then build heights: the height of every student, in row order.

heights = [row[1] for row in X]

Note How Big Is It?

Write shape(M) returning the tuple (m, n) for a list of lists: m rows and n columns. Do not hard-code any number—your function is tested on three different matrices.

def shape(M):
    return (len(M), len(M[0]))
Note Build the Identity

Construct \bold{I}_n as a list of lists, for any n—your function is tried on three different sizes. The entry in row i, column j should be 1 when i=j and 0 otherwise, so the condition you need is a comparison of the two loop indices.

def identity(n):
    return [[1 if i == j else 0 for j in range(n)] for i in range(n)]