Menu

Matrices in R: matrix(), cbind, rbind and Matrix Math

How to build matrices with matrix(), cbind() and rbind(), index rows and columns, and keep element-wise * separate from true matrix multiplication %*%.

This page includes runnable editors - edit, run, and see output instantly.

A Matrix Is a Vector with Dimensions

A matrix in R is a rectangle of values - rows and columns - where every cell holds the same type, usually numbers. Under the hood it's literally a vector with a dim attribute stapled on, which explains most of its behavior: one type throughout, vectorized math everywhere.

You build one by reshaping a vector with matrix():

Six values, two rows - R works out that three columns are needed. dim() reports both dimensions at once as 2 3; nrow() and ncol() give them separately.

Look closely at the printed matrix: the values run 1 2 down the first column, then 3 4 down the next. R fills matrices column by column by default. If your data reads row by row - which is how humans usually write it - say so with byrow = TRUE:

Now the first row is 1 2 3. Forgetting byrow = TRUE doesn't error - it silently gives you a transposed-looking arrangement of the same numbers, so make checking the printed layout a habit whenever you construct a matrix from raw values.

Indexing: m[row, column]

Matrix indexing takes two positions inside one pair of brackets, row before the comma, column after it - both counting from 1:

Leaving a position empty means "all of them": m[1, ] is the whole first row, m[, 2] the whole second column. Notice both come back as plain vectors - R drops the dimension that collapsed to size 1. That's convenient interactively and a trap in code, because a function expecting a matrix will choke on the vector. Ask R to keep the shape with drop = FALSE:

dim() now reports 1 3 - still a matrix. Any time you slice a single row or column inside a function, write drop = FALSE; the bug it prevents (code that works on wide data and breaks on one-column data) is a miserable one to track down.

Logical masks work here too: m[m > 3] returns all cells above 3, as a vector.

Building with cbind() and rbind()

Instead of reshaping one long vector, you can assemble a matrix from pieces: cbind() binds vectors together as columns, rbind() as rows. The same functions also extend an existing matrix:

The names travel with the vectors: cbind() used heights and weights as column names automatically, which keeps the printed matrix readable. Both functions insist that lengths line up (with recycling for length-1 values); binding a length-3 vector onto a 4-row matrix warns you.

Column and row names can also be set directly via colnames(m) <- ... and rownames(m) <- ..., after which you can index by name: people[, "weights"].

Element-wise * vs True Matrix Multiplication %*%

Here's the distinction that matters most in this whole article. R has two multiplication operators for matrices, and they compute entirely different things:

  • a * a is element-wise: each cell multiplied by the matching cell. 1 2 3 4 become 1 4 9 16, arranged in the same shape. This is just vectorized arithmetic, the same * you use on vectors.
  • a %*% a is matrix multiplication from linear algebra - each result cell is a row of the first matrix times a column of the second, summed. The same input gives 7 10 15 22: different numbers entirely.

Run the snippet and compare the two outputs side by side; seeing them differ on identical inputs is what makes the distinction stick. If you write * where the math calls for %*%, R won't warn you - the shapes are compatible either way for square matrices - you just get wrong numbers. In statistics code (covariance matrices, linear model algebra) this is one of the classic silent bugs.

t() transposes - flips rows and columns - and shows up constantly next to %*% because matrix multiplication needs inner dimensions to match:

For completeness: solve(m) inverts a matrix, and %*% with a vector treats it as a one-column matrix. That's as deep as most data work needs to go.

Row and Column Summaries

Summing or averaging across rows and columns is so common that R ships dedicated, fast functions for it:

rowSums() collapses each row to one number (6 15 here), colSums() each column (5 7 9), and the Means variants average instead. Prefer these over hand-rolled loops or even apply(m, 1, sum) - they're clearer and faster. For summaries these four don't cover (say, a per-column maximum), the apply family is the general tool: apply(m, 2, max).

Matrix or Data Frame?

Both are rectangular, so which do you reach for?

  • Matrix: every cell the same type, and the math matters. Numeric computation, linear algebra, distance calculations, image-like grids. Matrices are leaner and their operations faster precisely because of the one-type guarantee.
  • Data frame: columns of different types - names next to ages next to logical flags. This is real-world tabular data, and it's what nearly all data-analysis functions expect.

A good rule: if you'd naturally open it in a spreadsheet with named, mixed columns, it's a data frame. If it's a grid of numbers you intend to do algebra on, it's a matrix. Converting between them is easy (as.matrix(), as.data.frame()) - but as.matrix() on a data frame with any text column coerces everything to character, so convert only the numeric columns.

What You Take Away

  • A matrix is a vector with dimensions: one type throughout, built with matrix(data, nrow, ncol) - and it fills column by column unless you pass byrow = TRUE.
  • Index as m[row, col]; an empty position means "all"; add drop = FALSE when slicing single rows or columns inside code.
  • cbind() and rbind() assemble matrices from vectors or extend existing ones.
  • * is element-wise, %*% is real matrix multiplication - same inputs, different answers, no warning.
  • rowSums() / colSums() / rowMeans() / colMeans() handle the everyday summaries.

Next up: factors - how R represents categorical data, and the traps that come with it.

Frequently Asked Questions

How do you create a matrix in R?

matrix(1:6, nrow = 2) reshapes a vector into 2 rows and 3 columns, filling column by column. Add byrow = TRUE to fill row by row instead. You can also assemble a matrix from vectors: cbind() glues them as columns, rbind() as rows.

What is the difference between * and %*% in R?

* multiplies element by element - each cell times the matching cell, shapes must line up. %*% is true matrix multiplication from linear algebra (rows times columns, so the inner dimensions must match). They give completely different results on the same matrices, and using * where you meant %*% is a classic silent bug.

How do you get one row or column of a matrix in R?

Leave the other position empty: m[1, ] is the first row, m[, 2] is the second column. Both come back as plain vectors by default. Add drop = FALSE - as in m[1, , drop = FALSE] - to keep the result as a one-row or one-column matrix, which matters when later code expects two dimensions.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED