Reputation: 3088
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
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
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
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