LDT
LDT

Reputation: 3088

Use dplyr::rename function in a for loop in R

I am trying to understand how the dplyr::rename function works in a for loop work in R. In principle, I want to rename the column names of multiple data frames coming from another list.

Here is an example that fails, where I want to rename the first column of each dataset with a name coming from the new.names data set.

library(tidyverse)

iris1 <- iris
iris2 <- iris
iris3 <- iris

files <- list(iris1,iris2,iris3)

new.names <- c("change1","change2","change")

If I try this for loop it fails:

test <- list()
for(i in 1:length(files)){
  test[[i]] <- files[[i]] |> 
    dplyr::rename(new.names[i]=1)
}
test

Why is this the case?

Upvotes: 1

Views: 509

Answers (3)

TimTeaFan
TimTeaFan

Reputation: 18561

If you want to use a loop you need to use !! sym() on the names names of the lefthand side as well as the walrus operator :=:

library(tidyverse)

iris1 <- iris
iris2 <- iris
iris3 <- iris

files <- list(iris1,iris2,iris3)

new.names <- c("change1","change2","change")

test <- list()

test <- list()
for(i in 1:length(files)){
  test[[i]] <- files[[i]] |> 
    dplyr::rename(!! sym(new.names[i]) := 1)
}
test |>
  map(head) # for printing
#> [[1]]
#>   change1 Sepal.Width Petal.Length Petal.Width Species
#> 1     5.1         3.5          1.4         0.2  setosa
#> 2     4.9         3.0          1.4         0.2  setosa
#> 3     4.7         3.2          1.3         0.2  setosa
#> 4     4.6         3.1          1.5         0.2  setosa
#> 5     5.0         3.6          1.4         0.2  setosa
#> 6     5.4         3.9          1.7         0.4  setosa
#> 
#> [[2]]
#>   change2 Sepal.Width Petal.Length Petal.Width Species
#> 1     5.1         3.5          1.4         0.2  setosa
#> 2     4.9         3.0          1.4         0.2  setosa
#> 3     4.7         3.2          1.3         0.2  setosa
#> 4     4.6         3.1          1.5         0.2  setosa
#> 5     5.0         3.6          1.4         0.2  setosa
#> 6     5.4         3.9          1.7         0.4  setosa
#> 
#> [[3]]
#>   change Sepal.Width Petal.Length Petal.Width Species
#> 1    5.1         3.5          1.4         0.2  setosa
#> 2    4.9         3.0          1.4         0.2  setosa
#> 3    4.7         3.2          1.3         0.2  setosa
#> 4    4.6         3.1          1.5         0.2  setosa
#> 5    5.0         3.6          1.4         0.2  setosa
#> 6    5.4         3.9          1.7         0.4  setosa

We can also do it without a for loop then we need the new.names list to be a list of lists and we can then use purrr::map2():

library(tidyverse)

iris1 <- iris
iris2 <- iris
iris3 <- iris

files <- list(iris1,iris2,iris3)

new.names <- list(
  list(change1 = 1),
  list(change2 = 1),
  list(change = 1))

map2(files,
     new.names,
     ~ dplyr::rename(.x, !!! .y) %>% 
       head() # for printing
     )

#> [[1]]
#>   change1 Sepal.Width Petal.Length Petal.Width Species
#> 1     5.1         3.5          1.4         0.2  setosa
#> 2     4.9         3.0          1.4         0.2  setosa
#> 3     4.7         3.2          1.3         0.2  setosa
#> 4     4.6         3.1          1.5         0.2  setosa
#> 5     5.0         3.6          1.4         0.2  setosa
#> 6     5.4         3.9          1.7         0.4  setosa
#> 
#> [[2]]
#>   change2 Sepal.Width Petal.Length Petal.Width Species
#> 1     5.1         3.5          1.4         0.2  setosa
#> 2     4.9         3.0          1.4         0.2  setosa
#> 3     4.7         3.2          1.3         0.2  setosa
#> 4     4.6         3.1          1.5         0.2  setosa
#> 5     5.0         3.6          1.4         0.2  setosa
#> 6     5.4         3.9          1.7         0.4  setosa
#> 
#> [[3]]
#>   change Sepal.Width Petal.Length Petal.Width Species
#> 1    5.1         3.5          1.4         0.2  setosa
#> 2    4.9         3.0          1.4         0.2  setosa
#> 3    4.7         3.2          1.3         0.2  setosa
#> 4    4.6         3.1          1.5         0.2  setosa
#> 5    5.0         3.6          1.4         0.2  setosa
#> 6    5.4         3.9          1.7         0.4  setosa

Created on 2022-10-22 with reprex v2.0.2

Why does this work?

In dplyr we can construct calls as lists and then evaluate (or splice) them with !!!. This is what we do here.

new.names conains a list of list. Each sub-list has the new column name as name and the position of the old column as value.

So for one file we could do:

dplyr::rename(files[[1]], !!! new.names[[1]]) |> head()

If new.names would just be a simple list (without sub-lists) then the values wouldn't have a name and we would not be able to construct the call.

Finally we can also use map2() without constructing the call with a list of lists, but instead use a string:

new.names <- c("change1", "change2", "change")

map2(files,
     new.names,
     ~ dplyr::rename(.x, !! sym(.y) := 1) %>% 
       head() # for printing
     )

The logic is the same as in the for loop: we turn the strings into names with sym() evaluate them with !! and use the walrus operatur := instead =.

Upvotes: 2

Panagiotis Togias
Panagiotis Togias

Reputation: 323

You can also use dplyr::rename_with in order to pass dynamically the renaming part as a function like this:

test <- list()
for(i in 1:length(files)){
  test[[i]] <- files[[i]] %>%
    dplyr::rename_with(function(x) { x <- new.names[i]; return(x) }, c(1) )

}
test

Please read renames_with's documentation regarding the parameters for both the specified function and tidy select.

In case you don't care about dplyr::rename, the most simple thing that you can do is:

test <- list()
for(i in 1:length(files)){
  
  test[i] <- files[i]
  
  names(test[[i]])[1] <- new.names[i]
}
test

Upvotes: 1

noriega
noriega

Reputation: 448

This is a different apporach using the function setNames():

test <- list()
for(i in 1:length(files)){
  test[[i]] <- files[[i]] %>%  
    setNames(c(new.names[i], names(files[[i]])[-1]))
}

> test %>% lapply(head)
#[[1]]
#  change1 Sepal.Width Petal.Length Petal.Width Species
#1     5.1         3.5          1.4         0.2  setosa
#2     4.9         3.0          1.4         0.2  setosa
#3     4.7         3.2          1.3         0.2  setosa
#4     4.6         3.1          1.5         0.2  setosa
#5     5.0         3.6          1.4         0.2  setosa
#6     5.4         3.9          1.7         0.4  setosa
#
#[[2]]
#  change2 Sepal.Width Petal.Length Petal.Width Species
#1     5.1         3.5          1.4         0.2  setosa
#2     4.9         3.0          1.4         0.2  setosa
#3     4.7         3.2          1.3         0.2  setosa
#4     4.6         3.1          1.5         0.2  setosa
#5     5.0         3.6          1.4         0.2  setosa
#6     5.4         3.9          1.7         0.4  setosa
#
#[[3]]
#  change Sepal.Width Petal.Length Petal.Width Species
#1    5.1         3.5          1.4         0.2  setosa
#2    4.9         3.0          1.4         0.2  setosa
#3    4.7         3.2          1.3         0.2  setosa
#4    4.6         3.1          1.5         0.2  setosa
#5    5.0         3.6          1.4         0.2  setosa
#6    5.4         3.9          1.7         0.4  setosa

Upvotes: 1

Related Questions