ncol()
ncol(x) ncol() returns the number of columns in a matrix, array, or data frame. It returns NULL for atomic vectors since vectors have no dimensions.
Syntax
ncol(x)
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
x | matrix, array, data frame, or NULL | — | Input object to get column count from |
Examples
Basic usage with a matrix
The most straightforward application of ncol() is on a matrix created with matrix(), where the column count matches the ncol argument you supplied during construction. This example creates a 3-by-4 matrix filled column-wise with the integers 1 through 12, and ncol() confirms the expected width. Checking dimensions immediately after matrix construction is a quick sanity test that your reshape operation produced the intended layout before you pass the matrix into downstream computations.
mat <- matrix(1:12, nrow = 3, ncol = 4)
ncol(mat)
# [1] 4
Working with data frames
Data frames are the most common target for ncol() in everyday R work, because the column count tells you how many variables you are dealing with after an import or a join. For a data frame, ncol(df) is equivalent to length(df) and length(names(df)) — all three return the same integer — but ncol() makes the intent explicit when you are reasoning about the rectangular shape of the data rather than the list-of-columns implementation detail. In data pipeline code, stopifnot(ncol(df) == expected_cols) is a standard guard that catches malformed inputs before they cause cryptic errors downstream.
df <- data.frame(
name = c("Alice", "Bob", "Charlie"),
age = c(25, 30, 35),
score = c(85, 92, 78)
)
ncol(df)
# [1] 3
Using with NULL and vectors
The distinction between ncol() and NCOL() becomes important when writing generic functions that must handle both matrix and vector input without conditional branching. ncol() returns NULL for vectors because a vector has no dim attribute, while NCOL() treats vectors as single-column matrices and returns 1. If your function expects to iterate over columns with seq_len(ncol(x)), calling it on a vector will fail with a confusing error, whereas seq_len(NCOL(x)) works for both cases. Choose NCOL() when you want vector-to-matrix coercion and ncol() when you want to detect the dimensionality explicitly.
ncol(NULL)
# NULL
vec <- 1:10
ncol(vec)
# NULL
Common patterns
Loop over columns
Iterating over columns with 1:ncol(mat) is the idiomatic R way to apply a function to each column of a matrix when apply() feels like overkill for simple operations. The loop variable j runs from 1 to the column count, and mat[, j] extracts the j-th column as a vector, which you can then pass to any summary function. This pattern appears frequently in simulation code where you generate a matrix of replicates and need to compute per-column statistics without reshaping the data into a long format first.
mat <- matrix(1:12, nrow = 3)
for (j in 1:ncol(mat)) {
print(sum(mat[, j]))
}
Check if object has columns
Testing whether an object is two-dimensional before attempting column operations prevents a whole class of runtime errors in R. The helper has_cols() shown below returns TRUE when ncol() produces a non-NULL result, meaning the input has a well-defined column dimension. This pattern is useful at the top of functions that accept either vectors or matrices — you can branch on the result to handle each case with the appropriate indexing syntax, rather than forcing users to wrap their vectors in as.matrix() before calling your function.
has_cols <- function(x) {
!is.null(ncol(x))
}
has_cols(matrix(1:4, 2, 2))
# [1] TRUE
has_cols(1:10)
# [1] FALSE
Get last column
Selecting the last column of a matrix or data frame by its position rather than its name is a common need in pipelines where column order is known but column names vary — for instance, when the last column always holds the response variable in a modelling workflow. Writing mat[, ncol(mat)] expresses the intent directly and avoids hard-coding a column index that would break if columns were added or removed. For dropping the last column instead, mat[, -ncol(mat)] removes it, though mat[, seq_len(ncol(mat) - 1)] is more explicit about the intention.
mat <- matrix(1:12, nrow = 3, ncol = 4)
mat[, ncol(mat)]
# [1] 3 6 9 12
ncol() and nrow() in practice
ncol() returns the number of columns in a matrix or data frame. For vectors, it returns NULL because vectors have no column dimension. ncol(matrix(1:6, 2, 3)) returns 3. For data frames, ncol() equals length(), since a data frame is a list of column vectors.
A common use: checking data dimensions after import or transformation. stopifnot(ncol(df) == expected_cols) validates that preprocessing produced the expected number of columns. In defensive data pipeline code, asserting dimensions before processing prevents downstream errors from silently wrong inputs.
dim(x) returns c(nrow, ncol) for two-dimensional objects, so ncol(x) is equivalent to dim(x)[2]. For objects with more than two dimensions (arrays), dim() returns all dimensions, but ncol() still returns the size of dimension 2.
NCOL() and NROW() are variants that treat vectors as one-column matrices: NCOL(c(1,2,3)) returns 1 and NROW(c(1,2,3)) returns 3. These capitalized versions are useful in generic functions that should handle both vectors and matrices consistently. For data frames, ncol(df) equals length(df) equals length(names(df)). Use ncol() to compute the last column index without hard-coding: df[, ncol(df)] selects the last column.
Using ncol() for dynamic indexing is idiomatic: df[, ncol(df)] selects the last column regardless of how many columns the data frame has. df[, seq_len(ncol(df) - 1)] drops the last column. These patterns are more reliable than hard-coding column indices.
After cbind(), checking ncol() verifies the merge produced the expected number of columns. After reading data from a CSV, ncol(df) == expected_cols is a common assertion in data validation pipelines.
Pre-allocating a matrix with known column count
# Pre-allocate a matrix for simulation results
n_sims <- 1000
n_stats <- 4
results <- matrix(NA_real_, nrow = n_sims, ncol = n_stats)
# Fill the matrix in a loop
for (i in 1:n_sims) {
sample_data <- rnorm(50, mean = 10, sd = 3)
results[i, ] <- c(mean(sample_data), sd(sample_data),
median(sample_data), IQR(sample_data))
}
# Verify the structure
ncol(results)
# [1] 4
head(results, 3)
# [,1] [,2] [,3] [,4]
# [1,] 10.213569 3.048658 10.002312 3.893107
# [2,] 9.614949 3.094499 9.721127 4.640513
# [3,] 10.525936 2.621282 10.638959 3.562396
Pre-allocation with ncol explicitly set to the number of statistics you plan to track makes the intent clear and prevents accidental dimension errors from recycling. Each row of the results matrix holds one simulation replicate with four summary statistics, and ncol(results) confirms the expected width before downstream analysis. Without pre-allocation, growing the matrix row by row with rbind() would trigger repeated memory reallocations, degrading performance noticeably at the thousand-replicate scale shown here.