tibble rownames_to_column() in R: Move Rownames to Column
The rownames_to_column() function in the tibble package moves a data frame's row names into a regular column and returns a tibble, with the var argument controlling the new column name (default "rowname").
rownames_to_column(mtcars) # default var = "rowname" rownames_to_column(mtcars, var = "model") # custom column name mtcars |> rownames_to_column("model") # pipe-friendly rownames_to_column(as.data.frame(x)) # works on data.frame, not matrix df |> rownames_to_column() |> as_tibble() # already a tibble after the call rownames_to_column(df, var = "id") |> head(3) # chain into further steps column_to_rownames(df, "id") # reverse operation
Need explanation? Read on for examples and pitfalls.
What rownames_to_column() does in one sentence
rownames_to_column() returns a tibble with the source data frame's row names moved into a new column at the leftmost position. You pass a data frame whose row names you want to preserve, and optionally a var argument naming the new column. The function returns a tibble, so existing row names are discarded after the move and the result has integer row numbers from one upward.
The function lives in the tibble package and exists because tibbles intentionally do not support row names. When you convert a base R data frame with meaningful row names (like mtcars, where each row is a car model) directly with as_tibble(), the model labels vanish. rownames_to_column() is the safe migration path: lift the labels into a real column first, then convert.
Syntax
rownames_to_column() takes the data frame first, then an optional var string naming the new column. The input must be a data.frame with non-trivial row names; tibbles are rejected because they have no row names to move.
The full signature is:
rownames_to_column(.data, var = "rowname")
Arguments:
.data: a base Rdata.frame. Tibbles are rejected because they have no row names to extract.var: a string naming the new column. Defaults to"rowname". Must not clash with an existing column name.
The new column is always inserted as the first column. The result is a tibble; row numbers reset to 1:nrow(.data).
var to a meaningful name. The default "rowname" is forgettable and reads poorly in downstream code. For mtcars, write rownames_to_column(mtcars, "model"); for a sample-by-gene matrix, rownames_to_column(df, "sample_id"). A descriptive name makes joins and filters self-documenting later.Six common patterns
1. Default conversion with the "rowname" column name
With no var argument, the new column is named "rowname" and placed at position one. The result is a tibble, so subsequent dplyr verbs work without further conversion.
2. Set a meaningful column name
Naming the column "model" tells the next reader what the values mean. The string can be any valid column name; if it clashes with an existing column the function errors.
3. Pipe-friendly conversion before dplyr verbs
Once the row names are a column, dplyr verbs can filter, sort, and join on them. This is the canonical reason to convert: dplyr operations cannot reference row names directly.
4. Convert from matrix via as.data.frame() first
rownames_to_column() accepts only data.frame inputs. Matrices, even with dimnames, must be promoted with as.data.frame() first. The row names then transfer cleanly.
5. Round-trip with column_to_rownames()
column_to_rownames() is the inverse: it takes a column name, moves the values back into row names, and drops the column. Round-tripping is exact when no rows are reordered between the two calls.
6. Preserve rownames when converting to tibble
The as_tibble() path silently discards row names; rownames_to_column() preserves them. Always prefer the latter when the row labels carry meaning.
rownames_to_column() vs alternatives
Pick rownames_to_column() when the input is a base R data frame with informative row names. Several adjacent tools handle related row-axis tasks; choosing the wrong one is the most common source of confusion.
| Behavior | rownames_to_column() |
as_tibble(rownames=) |
rowid_to_column() |
Base R assignment |
|---|---|---|---|---|
| Source | tibble | tibble | tibble | base R |
| Best for | Lift existing rownames | Lift rownames during conversion | Add new sequential id | Manual control |
| New column position | First | First | First | Any |
| Source preserved | Rownames removed | Rownames removed | Rownames untouched | Both kept |
| Returns | tibble | tibble | tibble | data.frame |
| Errors if column exists | Yes | Yes | Yes | No (overwrites) |
When to use which:
- Use
rownames_to_column()for explicit two-step conversion when the data frame is already in scope. - Use
as_tibble(df, rownames = "model")to lift rownames as part of the tibble conversion in one call. - Use
rowid_to_column()to add a fresh1:nrow(df)id, not for existing row names. - Avoid
cbind(rowname = rownames(df), df)from base R, it returns a data.frame and loses tibble printing.
rownames_to_column() exists to bridge legacy data.frame workflows into the tidyverse without silently losing information. If you find yourself reaching for row names on a tibble, you have a missing column instead.Common pitfalls
Pitfall 1: passing a tibble. Tibbles have no row names, so the function errors. Convert from a base data.frame instead, or check with is.data.frame() and !tibble::is_tibble() before calling.
Pitfall 2: var name collides with an existing column. The function refuses to overwrite. Pick a non-clashing name or rename the existing column first.
rownames(df) returns "1", "2", "3", the new column is chr, not int. If you need an integer id, pass the result through mutate(id = as.integer(rowname)) or use rowid_to_column() instead, which adds an integer column from the start.Pitfall 3: forgetting that the result has fresh row numbers. The output tibble has row.names of 1:nrow(.data), not the originals. If downstream code relies on the original rownames, capture them in the call.
Try it yourself
Try it: Convert USArrests (a base R dataset with US state names as row names) into a tibble whose first column is named state. Then filter to states whose Murder rate exceeds 10. Save the result to ex_high_murder.
Click to reveal solution
Explanation: USArrests is a base data.frame with state names as row names. rownames_to_column(var = "state") lifts them into a real column so filter() can reference the rate column directly. The result is a tibble with integer row numbers and the state label preserved.
Related tibble functions
Alongside rownames_to_column(), look at:
column_to_rownames(): the inverse operation, moves a column back into row names.rowid_to_column(): prepends a fresh integer id column from one upward, ignoring any existing row names.has_rownames(): returnsTRUEif a data frame carries non-trivial row names.remove_rownames(): drops row names without preserving them.as_tibble(rownames = "var"): combines rowname lifting with tibble conversion in a single call.dplyr::relocate(): move the new id column to a different position afterrownames_to_column().
For the full reference, see the official tibble documentation.
FAQ
How do you convert row names to a column in R?
Call rownames_to_column() from the tibble package on a base data.frame whose row names you want to preserve. Example: rownames_to_column(mtcars, var = "model") returns a tibble where the original row names like "Mazda RX4" live in a new first column named model. The var argument defaults to "rowname", but a descriptive name is preferable. The result is always a tibble with integer row numbers from one upward.
What is the difference between rownames_to_column() and rowid_to_column()?
rownames_to_column() lifts EXISTING row names into a column, preserving labels like "Mazda RX4" or "Alabama". rowid_to_column() adds a NEW sequential integer id column from one upward, ignoring any existing row names. Use the first when row names carry meaning, like dataset record identifiers. Use the second when you simply need a stable per-row id for joining or tracking row order through transformations.
Why does rownames_to_column() error on a tibble?
Tibbles intentionally do not support row names, so there is nothing to extract. The error message reads must be a data frame without row names. Either call the function on the base data.frame BEFORE converting with as_tibble(), or use the combined form as_tibble(df, rownames = "model"), which lifts row names during conversion in a single call.
Can I undo rownames_to_column()?
Yes. column_to_rownames(df, "model") is the exact inverse: it takes the column name, moves the values back into row names, and drops the column. Round-tripping is lossless as long as no rows are reordered or filtered between the two calls. Note that the round-trip returns a base data.frame, not a tibble, because tibbles do not support row names.
Does rownames_to_column() modify the original data frame?
No. rownames_to_column() returns a new tibble; the original data.frame is unchanged. Assign the result back to persist the change: mtcars_t <- rownames_to_column(mtcars, "model"). This is the standard tidyverse pattern, functions are pure and changes only persist through explicit assignment or piping into a chain that rebinds at the end.