Menu

How to Rename Columns in R (names, colnames, dplyr rename)

Renaming data frame columns in R: names() and colnames(), rename by position vs by name, setNames(), dplyr rename(), and cleaning messy imported headers.

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

See the Names First: names() and colnames()

Column names live in an attribute you can read like any vector. names() and colnames() both return it for a data frame:

Same answer. The one real difference: colnames() also works on matrices, names() does not - so generic code uses colnames(). For everyday data frames they are interchangeable.

The key insight for everything below: renaming a column is assigning into this vector. There is no dedicated base function; you modify names(df) with the same indexing you use on any vector.

Rename by Position (Works, but Brittle)

Index the names vector by position and assign:

Column 2 is now price. Fine for a quick interactive fix - and a trap in a script. Positions are a promise about column order, and column order is the least stable thing about imported data: the day the CSV you read grows an extra column, names(df)[2] renames the wrong one, silently. If a script renames by position, a schema change breaks it without an error message.

Rename by Name (The Robust Base Idiom)

Match the old name instead of trusting a position:

Read the inner part first: names(sales) == "prc" produces FALSE TRUE FALSE - a logical mask over the names vector. Indexing with it selects the matching entry, and the assignment replaces it. If "prc" isn't there, nothing matches and nothing changes (no error - so check the result). This idiom survives reordering, added columns, and copy-paste into other scripts. Memorize it; it is the base R answer to "rename a column".

Several at Once

Replace the whole vector when you're naming every column (common right after an import), or use match() to rename a chosen subset:

match(old, names(df)) returns the positions of the old names, so the new names land exactly where the old ones were - order-safe, like the single-name idiom. If any old name is missing, match() returns NA and the assignment errors, which is the failure mode you want: loud.

setNames() for Pipelines

All the idioms above mutate a variable in two steps. setNames(object, names) returns a renamed copy in one expression, which is what a pipeline wants:

Handy when a function returns an unnamed or badly named frame and you want to fix it inline - read_something() |> setNames(c("id", "value")) - without a temporary variable.

The dplyr Way: rename() and rename_with()

dplyr's rename() takes new = old pairs - new name first, which everyone gets backwards once (static; the sandbox runs base R only):

library(dplyr)

sales |> rename(price = prc, qty = q)

Old names untouched by the call are kept as-is, and a misspelled old name is a hard error rather than a silent no-op - a genuine improvement over the base idiom. For renaming by rule rather than by pair, rename_with() applies a function to the names:

sales |> rename_with(toupper)                          # every column
sales |> rename_with(toupper, starts_with("p"))        # just some

See the dplyr intro for how these fit the verb family.

Cleaning Messy Imported Headers

Real CSV headers arrive as "Order ID", "unit.price ($)", "2024 Total" - names with spaces and symbols that force backtick-quoting everywhere. Base R's make.names() converts any character vector into valid R names:

Valid, but ugly in its own way (X2024.Total). This is why many teams standardize on the janitor package's clean_names(), which produces tidy snake_case (order_id, unit_price, x2024_total) in one call: df |> janitor::clean_names(). If you import messy files weekly, it pays for itself immediately.

What You Take Away

  • Column names are just a vector: read them with names()/colnames(), rename by assigning into it.
  • names(df)[2] <- "new" is brittle; names(df)[names(df) == "old"] <- "new" is the idiom to memorize.
  • match() renames several by name; setNames() renames inline in a pipeline.
  • dplyr's rename(new = old) errors on missing names (good) and rename_with() renames by rule.
  • For messy imported headers: make.names() in base, janitor::clean_names() for pretty results.

Next up: sorting - sort() for vectors, the order() trick for data frames, and arrange().

Frequently Asked Questions

How do I rename a column in R?

The robust base idiom is to match by name: names(df)[names(df) == "old"] <- "new". It finds the column called old wherever it sits and renames it. In dplyr it is rename(df, new = old) - note the order: new name first.

What is the difference between names() and colnames()?

On a data frame, nothing practical - both read and set the column names. colnames() also works on matrices (where names() does not), so code meant to handle both tends to use colnames(). On plain data frames, use whichever you like.

How do I rename a column by position in R?

Assign into the names vector at that position: names(df)[2] <- "price" renames the second column. It works, but it is brittle - the day a column is added or the CSV changes column order, position 2 is a different column. Prefer renaming by name.

How do I rename multiple columns at once in R?

Assign a full vector: names(df) <- c("id", "name", "price") replaces all names in order. To rename a chosen few by name, use match(): names(df)[match(c("old1", "old2"), names(df))] <- c("new1", "new2"). In dplyr: rename(df, new1 = old1, new2 = old2).

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED