tibble column_to_rownames() in R: Move Column Into Rownames
The column_to_rownames() function in the tibble package moves the values of a chosen column into a data frame's row names, drops that column, and returns a base data.frame ready for matrix routines that need labeled rows.
column_to_rownames(df, "id") # canonical: explicit var column_to_rownames(df) # default var = "rowname" df |> column_to_rownames("model") # pipe-friendly mtcars_t |> column_to_rownames("model") # restore mtcars rownames df |> mutate(id = paste0("s", row_number())) |> # create then promote column_to_rownames("id") rownames_to_column(df, "id") |> # round-trip column_to_rownames("id") as.matrix(column_to_rownames(df, "id")) # feed to matrix routines
Need explanation? Read on for examples and pitfalls.
What column_to_rownames() does in one sentence
column_to_rownames() returns a base data frame whose row names are the values of one of its columns, with that column removed. You pass a data frame and the name of the column to promote, and the function shifts those values into the row names attribute while stripping the column from the result. The return type is always a base data frame, never a tibble, because tibbles by design carry no row names.
The function lives in the tibble package and exists to bridge tidyverse pipelines back into base R territory. Routines like stats::dist(), stats::cor(), as.matrix(), and most heatmap and PCA helpers expect row names as the sample or observation label, then propagate those labels into their output. column_to_rownames() is the canonical promotion step at the end of a dplyr chain that prepares a frame for these label-carrying routines.
Syntax
column_to_rownames() takes the data frame first, then a var string naming the column to promote. The column must contain unique non-NA values; duplicates or missing values trigger an error.
The full signature is:
column_to_rownames(.data, var = "rowname")
Arguments:
.data: adata.frameor tibble. The return type is always basedata.frame.var: a string naming the column to promote. Defaults to"rowname"for round-trips withrownames_to_column().
The result has one fewer column than the input, with row names set to the promoted values.
var explicitly. The default "rowname" only exists to make round-trips with rownames_to_column() work without arguments. In a fresh pipeline, passing var = "model" or var = "sample_id" documents intent.Six common patterns
1. Promote a label column with an explicit var
The model column is gone from the body; its values are the new row labels. The remaining columns retain their original order. A call to rownames(df_rn) now returns the model strings instead of integer indices.
2. Default var = "rowname" round-trips with the inverse function
With no arguments, rownames_to_column() creates a column called "rowname", and column_to_rownames() consumes the same name on the way back. The output is identical to the original mtcars. In production code, prefer the explicit var = "model" so a future reader does not need to remember the default convention.
3. Pipe-friendly placement at the end of a dplyr chain
Push the promotion to the end of the chain. Dplyr verbs cannot reference rownames, so the recipe is: lift rownames into a column, do all dplyr work, then promote back to rownames when you're ready to hand the frame to a base R routine.
4. Build a labeled matrix for dist() or heatmap()
dist(), heatmap(), prcomp(), and friends label their output rows by rownames. Promoting sample into rownames before as.matrix() keeps s1, s2, s3, s4 flowing through the distance matrix, cluster dendrogram, or biplot. Without this step, the same routines fall back to integer indices and you lose the link back to the original samples.
5. Promote a freshly created id column
You can promote any column you create mid-pipeline. The paste0("car_", row_number()) step guarantees uniqueness, which column_to_rownames() will enforce on the next line. This pattern attaches synthetic identifiers (composite keys, batch numbers, condition labels) just before exporting to a labelled matrix.
6. Tibble inputs work, output is always data.frame
Tibble inputs are accepted. The return type is always base data.frame because tibbles cannot carry row names. To convert back, wrap the result with as_tibble(result, rownames = "id").
column_to_rownames() vs alternatives
Pick column_to_rownames() when you need rownames on the output and the labels live in a column. Several adjacent tools cover related row-axis tasks.
| Behavior | column_to_rownames() |
rownames(df) <- df$col |
tibble::deframe() |
as_tibble(rownames=) |
|---|---|---|---|---|
| Source | tibble | base R | tibble | tibble |
| Best for | End of dplyr chain | Quick base R one-liner | Two-column to named vector | Lift rownames during conversion |
| Drops promoted column | Yes | No (manual) | N/A (returns vector) | N/A (reverse direction) |
| Errors on duplicates | Yes | Silent (recycles) | No | No |
| Returns | data.frame | data.frame | named vector | tibble |
| Direction | column to rownames | column to rownames | column to vector | rownames to column |
When to use which:
- Use
column_to_rownames()for explicit, error-checked promotion at the end of a tidy pipeline. - Use base R
rownames(df) = df$col; df$col = NULLfor one-off scripts; expect silent duplicate recycling. - Use
tibble::deframe()when you want a named vector, not a labeled data frame. - Use
as_tibble(df, rownames = "var")for the reverse direction: lifting rownames out as a column during tibble conversion.
column_to_rownames() exists for the final hop, when a base R routine like dist() or prcomp() expects rownames as its label channel.Common pitfalls
Pitfall 1: duplicate values in the promoted column. Row names must be unique. The function passes values straight to row.names<-, which rejects duplicates with a base R error.
Pitfall 2: forgetting the result is a data.frame, not a tibble. Even with a tibble input, the output prints with base R formatting and downstream tibble-specific code may break.
If the next step expects a tibble, wrap the result with as_tibble(rownames = "id") to round-trip the rownames into a column.
var = "rowname" silently fails on unprepared frames. Calling column_to_rownames(df) without an explicit var only works if a column called "rowname" exists. On any other frame, you'll see Can't extract columns that don't exist. Pass var explicitly in production code.Pitfall 3: missing values in the promoted column. Row names cannot be NA. Any missing value errors out the call.
Try it yourself
Try it: Take USArrests, lift its state row names into a column called state, filter to states whose Murder rate is above 10, then promote state back into rownames so the result is a labeled data frame ready for dist(). Save the labeled frame to ex_arrests_mat.
Click to reveal solution
Explanation: rownames_to_column("state") lifts the labels into a column so filter() can see them. After filtering, column_to_rownames("state") promotes them back so the matrix passed to dist() carries its row labels through into the distance output.
Related tibble functions
Alongside column_to_rownames(), look at:
rownames_to_column(): the inverse operation, moves row names out into a new column.has_rownames(): returnsTRUEif a data frame carries non-trivial row names.remove_rownames(): drops row names entirely without saving them.rowid_to_column(): adds a fresh integer id column without touching existing row names.as_tibble(rownames = "var"): combines rowname lifting with tibble conversion in one call.tibble::deframe(): converts a two-column data frame into a named vector instead.
For the full reference, see the official tibble documentation.
FAQ
How do I move a column to row names in R?
Call column_to_rownames() from the tibble package, passing the data frame and the column to promote: column_to_rownames(df, "id"). The values in the named column become the row names, and the column itself is removed. The return type is base data.frame even if the input was a tibble. Values in the promoted column must be unique and non-NA.
What is the difference between column_to_rownames() and rownames(df) <- df$col?
column_to_rownames() is safer: it errors on duplicates and missing values, and it drops the promoted column for you. The base R assignment recycles duplicates silently into 1, 2, 3 row numbers in some R versions, leaves the original column in place, and returns whatever class the input was. For tidy pipelines, prefer the tibble call for the explicit error path.
Why does column_to_rownames() return a data.frame instead of a tibble?
Tibbles intentionally do not support row names; they make every per-row identifier a first-class column. column_to_rownames() exists precisely to bridge into base R routines that need rownames (dist(), prcomp(), heatmap(), as.matrix()), so it returns a base data.frame even when handed a tibble.
Can I undo column_to_rownames()?
Yes. rownames_to_column(df, "id") is the exact inverse: it lifts the row names back into a column. Round-tripping is lossless as long as no rows are reordered between the two calls. Note that rownames_to_column() returns a tibble.
Does column_to_rownames() modify the original data frame?
No. column_to_rownames() returns a new data frame; the original is unchanged. Assign the result back to persist the change, for example df = column_to_rownames(df, "id"). Tidyverse functions are pure, so changes only persist through explicit assignment.