rename_with doesn't work in purrr::map2 - Error "! the ... list contains fewer than 2 elements"
Asked Answered
S

3

6

Another seemingly easy task where purrr::map2 is giving me a hard time.

I have a list of data frames and I have a vector of column names. I now want to rename a certain column in each of the data frames with the respective new column name. So I wanted to simply loop through it with map2, but am getting an error. What am I doing wrong?

new_names <- c("test1", "test2")

df_list <- list(data.frame(mtcars[, 1]),
                data.frame(mtcars[, 2]))

map2(.x = df_list,
     .y = new_names,
     .f = ~.x |> 
       rename_with(.cols = everything(),
                   .fn   = ~.y))

# Error in `map2()`:
# ℹ In index: 1.
# Caused by error in `.fn()`:
# ! the ... list contains fewer than 2 elements
# Run `rlang::last_error()` to see where the error occurred.

I also tried different variants of .y, e.g. !!.y, paste0(sym(.y)) etc. in all possible combinations - no success.

Expected output would be the same list as the input, just that the first data frame has now a column "test1" and the second data frame a column "test2".

Stalag answered 6/3, 2023 at 8:0 Comment(0)
A
6

You use anonymous functions with purrr-style lambda for both .f of map2 and .fn of rename_with, and they conflict with each other. You need to explicitly specify an argument name, i.e. change .fn = ~ .y to .fn = \(nms) .y.

map2(.x = df_list,
     .y = new_names,
     .f = ~ .x |> 
       rename_with(.cols = everything(),
                   .fn   = \(nms) .y))
Acus answered 6/3, 2023 at 8:36 Comment(3)
I think it works also without \(nms) see starja's answer :)Silverplate
@Silverplate No, @starja re-write new_names as a list of functions. But I do not.Acus
Ah true! Now I understand why it is necessary to explicitly specify an argument name, thank you :)Silverplate
M
5

You have 2 issues:

  • rename_with expects a function, not a vector
  • the error you get is from using ~.y, while only .y works. I guess it's due to hiccups in the evaluation from purrr/dplyr

You can use your code if you provide a list of functions as inputs:

new_names <- list(function(x) {"test1"}, function(x) {"test2"})

df_list <- list(data.frame(mtcars[, 1]),
                data.frame(mtcars[, 2]))

map2(.x = df_list,
     .y = new_names,
     .f = ~.x %>%  
       rename_with(.cols = everything(),
                   .fn   = .y))

Alternatively, I'd just use colnames:

new_names <- c("test1", "test2")

df_list <- list(data.frame(mtcars[, 1]),
                data.frame(mtcars[, 2]))

map2(.x = df_list,
     .y = new_names,
     .f = function(df, new_name) {
       colnames(df)[1] <- new_name
       df
     })
Menander answered 6/3, 2023 at 8:14 Comment(2)
Re rename_with expecting a function...yes, but it expects the function to return a character vector, so passing a character vector with ~my_vector should be no problem. I think the problem is rather what @Darren Tsai described, i.e. the confusion of the names of the function argument by nesting map2 and rename_with. But interesting approach with passing the names as functions.Stalag
I see, I think you are right. Yes, it's an interesting approach, but I'd say rather cumbersome to have to type out the functions.Menander
A
4

We can use numbers on the right hand side of rename() to specify which column number we want to rename, so I would just use map2() and rename() (instead of rename_with).

library(purrr)
library(dplyr)

map2(df_list,
     new_names,
     \(df, col) rename(df, !! col := 1)
     )

#> [[1]]
#>    test1
#> 1   21.0
#> 2   21.0
#> 3   22.8
#> 4   21.4
#> 5   18.7
#> ...
#> 
#> [[2]]
#>    test2
#> 1      6
#> 2      6
#> 3      4
#> 4      6
#> 5      8
#> ...

Data from OP

new_names <- c("test1", "test2")

df_list <- list(data.frame(mtcars[, 1]),
                data.frame(mtcars[, 2]))

Created on 2023-03-06 by the reprex package (v2.0.1)

Aloysius answered 6/3, 2023 at 8:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.