How to dplyr rename a column, by column index?
Asked Answered
H

4

50

The following code renames first column in the data set:

require(dplyr)    
mtcars %>%
        setNames(c("RenamedColumn", names(.)[2:length(names(.))]))

Desired results:

                    RenamedColumn cyl  disp  hp drat    wt  qsec vs am gear carb
Mazda RX4                    21.0   6 160.0 110 3.90 2.620 16.46  0  1    4    4
Mazda RX4 Wag                21.0   6 160.0 110 3.90 2.875 17.02  0  1    4    4
Datsun 710                   22.8   4 108.0  93 3.85 2.320 18.61  1  1    4    1

Would it be possible to arrive at the same result using rename and column index?

This:

mtcars %>%
    rename(1 = "ChangedNameAgain")

will fail:

Error in source("~/.active-rstudio-document", echo = TRUE) : 
  ~/.active-rstudio-document:7:14: unexpected '='
6: mtcars %>%
7:     rename(1 =
                ^

Similarly trying to use rename_ or .[[1]] as column reference will return an error.

Hollyanne answered 13/3, 2017 at 17:20 Comment(1)
H
81

As of dplyr 0.7.5, rlang 0.2.1, tidyselect 0.2.4, this simply works:

library(dplyr)

rename(mtcars, ChangedNameAgain = 1)

#                     ChangedNameAgain cyl  disp  hp drat    wt  qsec vs am gear carb
# Mazda RX4                       21.0   6 160.0 110 3.90 2.620 16.46  0  1    4    4
# Mazda RX4 Wag                   21.0   6 160.0 110 3.90 2.875 17.02  0  1    4    4
# Datsun 710                      22.8   4 108.0  93 3.85 2.320 18.61  1  1    4    1
# Hornet 4 Drive                  21.4   6 258.0 110 3.08 3.215 19.44  1  0    3    1
# Hornet Sportabout               18.7   8 360.0 175 3.15 3.440 17.02  0  0    3    2
# ...

Original answer and edits now obsolete:

The logic of rename() is new_name = old_name, so ChangedNameAgain = 1 would make more sense than 1 = ChangedNameAgain.

I would suggest:

mtcars %>% rename_(ChangedNameAgain = names(.)[1])
#                     ChangedNameAgain cyl  disp  hp drat    wt  qsec vs am gear carb
# Mazda RX4                       21.0   6 160.0 110 3.90 2.620 16.46  0  1    4    4
# Mazda RX4 Wag                   21.0   6 160.0 110 3.90 2.875 17.02  0  1    4    4
# Datsun 710                      22.8   4 108.0  93 3.85 2.320 18.61  1  1    4    1
# Hornet 4 Drive                  21.4   6 258.0 110 3.08 3.215 19.44  1  0    3    1
# Hornet Sportabout               18.7   8 360.0 175 3.15 3.440 17.02  0  0    3    2
# Valiant                         18.1   6 225.0 105 2.76 3.460 20.22  1  0    3    1

Edit

I have yet to wrap my head around the new dplyr programming system based on rlang, since versions 0.6/0.7 of dplyr.

The underscore-suffixed version of rename used in my initial answer is now deprecated, and per @jzadra's comment, it didn't work anyway with syntactically problematic names like "foo bar".

Here is my attempt with the new rlang-based Non Standard Evaluation system. Do not hesitate to tell me what I've done wrong, in the comments:

df <- tibble("foo" = 1:2, "bar baz" = letters[1:2])

# # A tibble: 2 x 2
#     foo `bar baz`
#   <int>     <chr>
# 1     1         a
# 2     2         b

First I try directly with rename() but unfortunately I've got an error. It seems to be a FIXME (or is this FIXME unrelated?) in the source code (I'm using dplyr 0.7.4), so it could work in the future:

df %>% rename(qux = !! quo(names(.)[[2]]))

# Error: Expressions are currently not supported in `rename()`

(Edit: the error message now (dplyr 0.7.5) reads Error in UseMethod("rename_") : no applicable method for 'rename_' applied to an object of class "function")

(Update 2018-06-14: df %>% rename(qux = !! quo(names(.)[[2]])) now seems to work, still with dplyr 0.7.5, not sure if an underlying package changed).

Here is a workaround with select that works. It doesn't preserve column order like rename though:

df %>% select(qux = !! quo(names(.)[[2]]), everything())

# # A tibble: 2 x 2
#     qux   foo
#   <chr> <int>
# 1     a     1
# 2     b     2

And if we want to put it in a function, we'd have to slightly modify it with := to allow unquoting on the left hand side. If we want to be robust to inputs like strings and bare variable names, we have to use the "dark magic" (or so says the vignette) of enquo() and quo_name() (honestly I don't fully understand what it does):

rename_col_by_position <- function(df, position, new_name) {
  new_name <- enquo(new_name)
  new_name <- quo_name(new_name)
  select(df, !! new_name := !! quo(names(df)[[position]]), everything())
}

This works with new name as a string:

rename_col_by_position(df, 2, "qux")

# # A tibble: 2 x 2
#     qux   foo
#   <chr> <int>
# 1     a     1
# 2     b     2

This works with new name as a quosure:

rename_col_by_position(df, 2, quo(qux))

# # A tibble: 2 x 2
#     qux   foo
#   <chr> <int>
# 1     a     1
# 2     b     2

This works with new name as a bare name:

rename_col_by_position(df, 2, qux)

# # A tibble: 2 x 2
#     qux   foo
#   <chr> <int>
# 1     a     1
# 2     b     2

And even this works:

rename_col_by_position(df, 2, `qux quux`)

# # A tibble: 2 x 2
#   `qux quux`   foo
#        <chr> <int>
# 1          a     1
# 2          b     2
Hydrogenous answered 13/3, 2017 at 17:26 Comment(2)
This doesn't work if the existing name is something that would require backticks.Stream
Thank you. I must agree that the new rlang has taken the tidyverse away from an understandable grammar, which seems like a huge step backwards to me.Stream
T
16

Here's a couple of alternative solutions that are arguably easier to read because they are not focused around the . reference. select understands column indices, so if you're renaming the first column, you can simply do

mtcars %>% select( RenamedColumn = 1, everything() )

However, the issue with using select is that it will reorder columns if you're renaming a column in the middle. To get around the issue, you have to pre-select the columns to the left of the one you're renaming:

## This will rename the 7th column without changing column order
mtcars %>% select( 1:6, RenamedColumn = 7, everything() )

Another option is to use the new rename_at, which also understand column indices:

## This will also rename the 7th column without changing the order
## Credit for simplifying the second argument: Moody_Mudskipper
mtcars %>% rename_at( 7, ~"RenamedColumn" )

The ~ is needed because rename_at is quite flexible and can accept functions as its second argument. For example, mtcars %>% rename_at( c(2,4), toupper ) will make the names of the second and fourth columns uppercase.

Treasonous answered 5/3, 2018 at 21:45 Comment(3)
rename_at seems to be the best, and most current solution of the lot. Thanks, @artem-solokov !Barbellate
@Artem you can write just mtcars %>% rename_at(1,~"RenamedColumn")Clip
Great suggestion, @Moody_Mudskipper. I edited my answer to incorporate it.Treasonous
G
14

dplyr has superceded rename_at() with rename_with(). You can rename a column by index like this:

library(tidyverse)

mtcars %>% 
  rename_with(.cols = 1, ~"renamed_column")

#>                     renamed_column cyl  disp  hp drat    wt  qsec vs am gear
#> Mazda RX4                    21.0   6 160.0 110 3.90 2.620 16.46  0  1    4
#> Mazda RX4 Wag                21.0   6 160.0 110 3.90 2.875 17.02  0  1    4
#> Datsun 710                   22.8   4 108.0  93 3.85 2.320 18.61  1  1    4
#> Hornet 4 Drive               21.4   6 258.0 110 3.08 3.215 19.44  1  0    3
#> Hornet Sportabout            18.7   8 360.0 175 3.15 3.440 17.02  0  0    3
#> ...

Be sure to include the tilde (~)* before the new column name.

Also note that if you introduce the glue package, you can modify existing column names like this:

library(glue)

mtcars %>% 
  rename_with(.cols = 1, ~glue::glue("renamed_{.}"))
#>                     renamed_mpg cyl  disp  hp drat    wt  qsec vs am gear carb
#> Mazda RX4                  21.0   6 160.0 110 3.90 2.620 16.46  0  1    4    4
#> Mazda RX4 Wag              21.0   6 160.0 110 3.90 2.875 17.02  0  1    4    4
#> Datsun 710                 22.8   4 108.0  93 3.85 2.320 18.61  1  1    4    1
#> Hornet 4 Drive             21.4   6 258.0 110 3.08 3.215 19.44  1  0    3    1
#> Hornet Sportabout          18.7   8 360.0 175 3.15 3.440 17.02  0  0    3    2
#> ...

Applying the above approach to multiple columns is just a matter of passing in the column index number range using a colon (:) or multiple indices in a vector using c(); here's a combination of both:

mtcars %>% 
  rename_with(.cols = c(1:3, 5), ~glue::glue("renamed_{.}"))
#>                     renamed_mpg renamed_cyl renamed_disp  hp renamed_drat    wt
#> Mazda RX4                  21.0           6        160.0 110         3.90 2.620
#> Mazda RX4 Wag              21.0           6        160.0 110         3.90 2.875
#> Datsun 710                 22.8           4        108.0  93         3.85 2.320
#> Hornet 4 Drive             21.4           6        258.0 110         3.08 3.215
#> Hornet Sportabout          18.7           8        360.0 175         3.15 3.440
#> ...

And keep in mind that since the . represents the current column name, you can apply string modification functions to it like this:

mtcars %>% 
  rename_with(.cols = c(1:3), 
              ~glue::glue("renamed_{str_replace(.,'mpg','miles_per_gallon')}"))
#>                     renamed_miles_per_gallon renamed_cyl renamed_disp  hp
#> Mazda RX4                               21.0           6        160.0 110
#> Mazda RX4 Wag                           21.0           6        160.0 110
#> Datsun 710                              22.8           4        108.0  93
#> Hornet 4 Drive                          21.4           6        258.0 110
#> Hornet Sportabout                       18.7           8        360.0 175
#> ...

*You can learn more about the ~ and . NSE function shorthand here.

Glaser answered 13/3, 2021 at 18:53 Comment(0)
E
3

Imho rlang as suggested by @Aurele is too much here.

Solution 1: Use a curly bracket pipe pipe context:

bcMatrix %>% {colnames(.)[1] = "foo"; .}

Solution 2: Or (ab)use the tee operator %>% from magrittr package (installed anyway if dplyr is used) to perform the renaming as a side-effect:

bcMatrix %T>% {colnames(.)[1] = "foo"}

Solution 3: using a simple helper function:

rename_by_pos = function(df, index, new_name){ 
    colnames(df)[index] = new_name 
    df 
}
iris %>% rename_by_pos(2,"foo")
Emanate answered 20/12, 2017 at 13:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.