str_replace inside fct_reorder inside mutate(across())

Say I have this tibble:

df <- tibble::tribble(
  ~how_bright_txt, ~how_bright_num,   ~how_hard_txt, ~how_hard_num, ~how_hot_txt, ~how_hot_num,
  "Not very much",              1L,   "Really hard",            5L,       "Cold",           1L,
       "Somewhat",              2L, "Somewhat hard",            2L, "A bit cold",           2L,
         "Medium",              3L,        "Medium",            3L,     "Medium",           3L,
    "Quite a bit",              4L,    "Quite hard",            4L,  "Quite hot",           4L,
          "A lot",              5L, "Not very hard",            1L, "Really hot",           5L
  )

I want to make new columns named with the first part of the existing column names (minus the _txt or _num prefix) and which take the value of the _txt columns but converting them to factors ordered by the corresponding _num columns.

I can do this by repeating fct_reorder inside mutate for each column, like so:

require(tidyverse)

df %>% 
    mutate(how_bright = fct_reorder(how_bright_txt, -how_bright_num),
           how_hard = fct_reorder(how_hard_txt, -how_hard_num),
           how_hot = fct_reorder(how_hot_txt, -how_hot_num)) %>%
    select(-c(ends_with("_txt"), ends_with("_num")))

But I want to streamline this and use mutate(across()). So I tried doing this:

df %>% 
    mutate(across(ends_with("_txt"), 
         ~ fct_reorder(.x, str_replace(.x, "_txt", "_num")), 
                     .names = '{stringr::str_remove({col}, "_txt")}')) %>%
    select(-c(ends_with("_txt"), ends_with("_num")))

But the ordering of the resulting factors (how_bright, how_hard, how_hot) are incorrect and don’t correspond to the ordering in the original _num columns. I have also tried replacing the str_replace call with a gsub call but I get the same output

Can anyone see what I’m doing wrong?

>Solution :

What you need is cur_column() and get(). cur_column() gives the current column name, i.e. *_txt. After str_replace() is used on it, get() searches the new name (i.e. *_num) in the current dataset and returns its values.

library(tidyverse)

df %>% 
  mutate(across(ends_with("_txt"), 
                ~ fct_reorder(.x, get(str_replace(cur_column(), "_txt", "_num"))),
                .names = "{str_remove(.col, '_txt')}"),
         .keep = "unused")

# # A tibble: 5 × 3
#   how_bright    how_hard      how_hot   
#   <fct>         <fct>         <fct>     
# 1 Not very much Really hard   Cold      
# 2 Somewhat      Somewhat hard A bit cold
# 3 Medium        Medium        Medium    
# 4 Quite a bit   Quite hard    Quite hot 
# 5 A lot         Not very hard Really hot

Leave a Reply