Lists: Heterogeneous, Nested Containers
A list is R's general-purpose container: unlike a vector, its elements can be of any type or structure — numbers, character strings, vectors, data frames, even other lists — and elements don't need to share a length or type. Lists are built with list(name1 = value1, name2 = value2, ...), and because they can hold nested structures, a list is the natural way to represent something like a function's return value bundling multiple results (a fitted model, its coefficients, and diagnostic statistics) together.
Cricket analogy: A list representing a player's full profile — name (character), career average (numeric), and a nested vector of century scores — mirrors a cricket board's player dossier holding mixed data types under one record.
Matrices: Two-Dimensional, Single-Type Grids
A matrix is a two-dimensional, single-type generalization of a vector: matrix(data, nrow, ncol, byrow = FALSE) fills a rectangular grid column by column by default (or row by row if byrow = TRUE), and every element must share the same atomic type just like a vector. dim(m) reports the number of rows and columns, and because a matrix is built on top of a vector, it inherits the same type restriction — mixing numeric and character values in matrix() coerces everything to character, exactly as it would in c().
Cricket analogy: A run-rate matrix with rows for overs and columns for each batter, filled byrow=TRUE to match how a scorer records ball-by-ball totals across an over, mirrors matrix()'s row-filling option.
# List: heterogeneous, nested
player <- list(
name = "Meera",
scores = c(45, 78, 23),
stats = list(average = 48.7, strikeRate = 91.2)
)
player[["stats"]][["average"]] # 48.7
# Matrix: homogeneous, 2D
runMatrix <- matrix(1:6, nrow = 2, byrow = TRUE)
t(runMatrix) # transpose
apply(runMatrix, 1, sum) # row sumsIndexing Lists and Matrices
Single brackets on a list, list1[2], return a sub-list containing just that element (still wrapped in list structure), while double brackets, list1[[2]], extract the element itself in its native type — this distinction matters because list1[2] + 1 fails but list1[[2]] + 1 works if the second element is numeric. Matrices use m[row, col] with either being left blank to select an entire row or column, or given as vectors for multiple rows/columns, e.g., m[1:2, 3] returns rows 1 and 2 from column 3.
Cricket analogy: player[['average']] pulling the raw numeric average out of a player list is like reading the exact number off a scorecard, whereas player['average'] is like handing someone the whole scorecard page just to show one stat.
%*% performs genuine matrix multiplication where the number of columns in the first matrix must equal the number of rows in the second, producing a new matrix via dot products — this is different from *, which multiplies corresponding elements position-by-position and requires matching dimensions (or recyclable ones).
Matrix Operations and Converting Between Structures
t() transposes a matrix, flipping rows and columns; %*% performs true matrix multiplication (following linear algebra rules on dimensions), while the plain * operator does element-wise multiplication instead; solve(m) computes a matrix's inverse (requiring a square, non-singular matrix); and apply(m, 1, sum) or apply(m, 2, mean) runs a function over each row (margin 1) or column (margin 2). Converting structures is straightforward: as.matrix(df) turns a data frame into a matrix (coercing to one type if columns differ), and as.list(v) turns a vector into a list of single-element entries.
Cricket analogy: apply(runMatrix, 1, sum) computing each batter's total runs across overs (margin 1 = rows) is like a scorer summing each row of a ball-by-ball chart to get final individual totals.
matrix() enforces a single atomic type just like vectors: matrix(c(1, 2, 'three', 4), nrow = 2) silently coerces every element to character, which can produce confusing downstream errors when you later try numeric operations on what looks like a numeric matrix.
- Lists hold heterogeneous, possibly nested elements of any type or length; matrices hold a single type in a strict 2D grid.
- Single brackets [i] on a list return a sub-list; double brackets [[i]] return the raw element.
- matrix(data, nrow, ncol, byrow=) controls how a vector of values fills the 2D grid.
- m[row, col] indexes a matrix; leaving row or col blank selects that whole dimension.
- %*% is true matrix multiplication; * is element-wise — they are not interchangeable.
- as.matrix() and as.list() convert between structures but may trigger type coercion.
Practice what you learned
1. What is the key structural difference between a list and a matrix in R?
2. What does list1[[2]] return compared to list1[2]?
3. What does the %*% operator do that * does not?
4. In matrix(1:6, nrow=2, byrow=TRUE), how is the data filled?
5. What happens when matrix() is given both numeric and character values?
Was this page helpful?
You May Also Like
Vectors in R
Learn how R's core data structure — the atomic vector — stores, indexes, and vectorizes operations over ordered collections of same-typed values.
Data Frames in R
Understand R's core tabular data structure — how data.frame combines vectors of equal length into rows and columns for real-world datasets.
Factors in R
Learn how R represents categorical data with factors — encoding fixed levels efficiently and controlling ordinal comparisons.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics