Matrix Algebra (%*%, solve, t)
Reviewed & published by Brayan K
Matrix algebra in R is the set of tools — matrix multiplication with %*%, transpose with t(), and solving systems with solve() — that let you do real linear algebra on two-dimensional grids of numbers.
Part of the free R course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
By the end of this lesson you'll build matrices, multiply them the right way, transpose and invert them, and solve a system of linear equations in a single call.
What You'll Learn in This Lesson
- How to build matrices with matrix() and control fill order
- Why %*% (matrix product) differs from * (elementwise)
- How to transpose a matrix with t()
- How solve() computes an inverse and solves systems
- How det() tells you if a matrix is invertible
- How diag() and dimnames make matrices readable
1️⃣ Building Matrices
Use matrix() with a vector of values plus nrow and/or ncol. By default R fills down the columns; add byrow = TRUE to fill across rows the way you'd read them. The dim(), nrow(), and ncol() helpers report the shape.
# A matrix is a 2-D grid of values of the SAME type.
# Build one with matrix(); R fills DOWN columns by default.
A <- matrix(1:6, nrow = 2, ncol = 3)
A
# [,1] [,2] [,3]
# [1,] 1 3 5
# [2,] 2 4 6
# Fill across rows instead with byrow = TRUE:
B <- matrix(1:6, nrow = 2, byrow = TRUE)
B
# [,1] [,2] [,3]
# [1,] 1 2 3
# [2,] 4 5 6
dim(A) # rows, cols -> [1] 2 3
nrow(A) # -> [1] 2
ncol(A) # -> [1] 32️⃣ Multiplying and Transposing
The operator %*% performs true matrix multiplication, where each output cell is a row-times-column dot product. A plain * multiplies matching cells instead — useful, but a different operation. The transpose t() flips rows and columns.
# %*% is MATRIX multiplication. A plain * multiplies element by element!
# For A %*% B, the columns of A must equal the rows of B.
A <- matrix(c(1, 2, 3, 4), nrow = 2) # 2x2, filled by column
B <- matrix(c(5, 6, 7, 8), nrow = 2) # 2x2
A %*% B # true matrix product (rows x cols)
# [,1] [,2]
# [1,] 23 31
# [2,] 34 46
A * B # element-by-element — NOT the same thing
# [,1] [,2]
# [1,] 5 21
# [2,] 12 32
# t() TRANSPOSES — rows become columns:
t(A)
# [,1] [,2]
# [1,] 1 2
# [2,] 3 4Whenever you see a dimension-mismatch error from %*%, check that the inner dimensions agree: an m×n times an n×p gives an m×p result.
3️⃣ Inverses and Solving Systems
The single function solve() does double duty. Called with one matrix it returns the inverse; called as solve(A, b) it solves the linear system A x = b for the unknown vector x. Use det() to check invertibility and diag() to build an identity matrix.
# solve() with ONE argument returns the matrix INVERSE.
# solve(A, b) solves the linear system A %*% x = b for x.
A <- matrix(c(2, 1, 1, 3), nrow = 2) # 2x2 coefficient matrix
# Solve 2x + y = 5 , x + 3y = 10
b <- c(5, 10)
x <- solve(A, b)
x # -> [1] 1 3 (so x = 1, y = 3)
det(A) # determinant; non-zero means invertible -> [1] 5
# diag() makes an identity matrix (or pulls a diagonal):
diag(2)
# [,1] [,2]
# [1,] 1 0
# [2,] 0 1Your turn. Fill in the ___ blank, run it, and compare with the expected output.
# 🎯 YOUR TURN — replace each ___ then run it.
# 1) Build a 2x2 matrix M from the numbers 2, 0, 0, 3 (by column)
M <- matrix(c(2, 0, 0, 3), nrow = 2)
# 2) Build a second 2x2 matrix N from 1, 1, 1, 1
N <- ___ # 👉 replace ___ with matrix(c(1, 1, 1, 1), nrow = 2)
# 3) Print the matrix product M %*% N
print(M %*% N)
# 4) Print the transpose of M
print(t(M))
# ✅ Expected output:
# [,1] [,2]
# [1,] 2 2
# [2,] 3 3
# [,1] [,2]
# [1,] 2 0
# [2,] 0 3Mini-Challenge: Crack the Prices
Two equations, two unknowns — exactly what solve(A, b) is built for. Remember to use byrow = TRUE so your coefficient matrix matches the way you wrote the equations.
# 🎯 MINI-CHALLENGE: solve a small system of equations.
#
# A farm sells apples (a) and pears (p). Two days of sales:
# Day 1: 3a + 2p = 13 (3 apples, 2 pears, $13)
# Day 2: 1a + 4p = 16 (1 apple, 4 pears, $16)
#
# 1. Build the 2x2 coefficient matrix A (by ROW, byrow = TRUE).
# 2. Build the right-hand-side vector b = c(13, 16).
# 3. Use solve(A, b) to find the price of an apple and a pear.
# 4. Confirm by checking A %*% prices equals b.
#
# ✅ Expected: apple = 2, pear = 3.5 -> [1] 2.0 3.5
# your code hereCommon Errors (and the fix)
- Using * when you meant %*% — ❌ A * B multiplies cell-by-cell. ✅ Use A %*% B for the matrix product.
- "non-conformable arguments" — ❌ inner dimensions don't match. ✅ For A %*% B, ncol(A) must equal nrow(B).
- Inverting to solve a system — ❌ solve(A) %*% b is slower and less stable. ✅ Use solve(A, b).
- Forgetting byrow — ❌ entries land in the wrong cells. ✅ Add byrow = TRUE when you list values row by row.
Pro Tips
- 💡 Stack matrices fast: rbind() stacks rows, cbind() glues columns side by side.
- 💡 crossprod(A) computes t(A) %*% A faster than writing it out — common in regression.
- 💡 Check shape early: a quick dim() before %*% saves you from conformability errors.
📋 Quick Reference — Matrix Algebra
| Task | R Syntax |
|---|---|
| Create matrix | matrix(1:6, nrow = 2) |
| Matrix product | A %*% B |
| Transpose | t(A) |
| Inverse | solve(A) |
| Solve A x = b | solve(A, b) |
| Determinant | det(A) |
| Identity matrix | diag(n) |
🎉 Lesson Complete!
- ✅ matrix() builds a grid — control fill order with byrow
- ✅ %*% is the matrix product; * is elementwise
- ✅ t() transposes rows and columns
- ✅ solve(A) inverts; solve(A, b) solves a system
- ✅ det() checks invertibility; diag() makes an identity
Practice quiz
What does the %*% operator do?
- Element-wise multiplication
- True matrix multiplication
- Division
- Transposition
Answer: True matrix multiplication. %*% performs linear-algebra matrix multiplication (row-times-column dot products).
What does a plain * do between two matrices?
- Matrix product
- Solves a system
- Inverts them
- Element-by-element multiplication
Answer: Element-by-element multiplication. A single * multiplies matching cells (Hadamard), requiring equal shapes.
For A %*% B to work, what must be true?
- A's columns equal B's rows
- A and B are identical
- Both must be square
- A's rows equal B's rows
Answer: A's columns equal B's rows. The inner dimensions must match: ncol(A) must equal nrow(B).
What does t(A) compute?
- The total
- The determinant
- The transpose
- The inverse
Answer: The transpose. t() transposes A, turning rows into columns.
What does solve(A) with one argument return?
- The inverse of A
- The determinant
- The transpose
- The diagonal
Answer: The inverse of A. solve(A) returns the matrix inverse of A.
Which is the preferred way to solve the system A x = b?
- solve(A) %*% b
- A %*% b
- solve(A, b)
- det(A) * b
Answer: solve(A, b). solve(A, b) is faster and more numerically stable than inverting A first.
What does det(A) tell you?
- The matrix size
- Whether A is invertible (non-zero means yes)
- The number of rows
- The transpose
Answer: Whether A is invertible (non-zero means yes). A non-zero determinant means the matrix is invertible.
How does R fill matrix(1:6, nrow = 2) by default?
- Row by row
- Randomly
- Diagonally
- Down the columns
Answer: Down the columns. R is column-major: 1 and 2 fill the first column, then 3 and 4, and so on.
What does diag(2) produce?
- A 2x2 identity matrix
- The number 2
- A diagonal of twos
- A 2-row vector
Answer: A 2x2 identity matrix. diag(n) builds an n-by-n identity matrix.
What does 'system is computationally singular' mean?
- The matrix is too large
- There are too many rows
- The matrix has no inverse (determinant ~ 0)
- b is the wrong length
Answer: The matrix has no inverse (determinant ~ 0). It signals a non-invertible matrix, so the system has no unique solution.
Continue this course
- Previous: Writing Functions & Packages
- Next: Text Wrangling with stringr — Detect, extract, and replace text the tidyverse way with str_* functions
- Quick reference: R cheat sheet
Frequently asked questions
What's the difference between * and %*% for matrices?
A single * does element-by-element (Hadamard) multiplication: it lines up the two matrices cell-for-cell and multiplies matching positions, so both must be the same shape. The %*% operator does true linear-algebra matrix multiplication, where each output cell is the dot product of a row from the left matrix and a column from the right. For A %*% B the number of columns in A must equal the number of rows in B. Mixing these up is the single most common matrix bug in R.
Why does my matrix fill column-by-column instead of row-by-row?
R is column-major by default, so matrix(1:6, nrow = 2) puts 1 and 2 in the first column, 3 and 4 in the second, and so on. If you wrote your numbers out row by row in your head, add byrow = TRUE so matrix() fills across rows to match. This matters constantly when you hand-build a coefficient matrix for solve().
When should I use solve(A) versus solve(A, b)?
solve(A) returns the inverse of A, which is occasionally useful but numerically wasteful. To solve a linear system A x = b, prefer solve(A, b) directly — it is faster and more numerically stable than computing solve(A) %*% b. As a rule, if you only need x, never form the inverse explicitly.
What does it mean when solve() errors with 'system is computationally singular'?
It means the matrix has no inverse (or is so close to non-invertible that R refuses). Geometrically the equations are redundant or contradictory — the determinant is zero or near it. Check det(A): if it's zero, your system doesn't have a unique solution. This often happens when two rows are multiples of each other (collinear data).