apply `mutate_at` for two columns at the same time?

Viewed 54

How would I call a function like mutate_at for two columns at the same time?

If for example I had a df with consistent columns names and I want to find the difference I could do something like:

library(dplyr)

df <-
tibble(x_1 = 1:5,
       x_2 = 1:5,
       y_1 = 6:10,
       y_2 = 6:10)

df %>% 
  mutate(x_dif = x_1 - x_2,
         y_dif = y_1 - y_2)

It is easy to do this manually, but I'd like to apply a function to both columns in a scalable fashion. I'm conceptually thinking of this as two mutate_at's where I would do vars(x_1, y_1) and vars(x_2, y_2) at the same time in one call, but I don't think this is the right approach.

2 Answers

I tried to do it via dplyr but it comes out very cumbersome. However, in base R this is a one-liner, i.e.

Reduce(`-`, split.default(df, sub('.*_','',names(df))))

Here is also the dplyr version.

library(dplyr)
library(tidyr)

df %>% 
 pivot_longer(everything()) %>% 
 mutate(name = sub('_.*', '', name)) %>% 
 group_by(name, grp = with(rle(name), rep(seq_along(lengths), lengths))) %>% 
 summarise(new = diff(value)) %>% 
 ungroup() %>% 
 select(-grp) %>% 
 pivot_wider(names_from = name, values_from = new, values_fn = list(new = list)) %>% 
 unnest()

You can tidy the output as you like in both cases.

Probably not your ideal solution but this how I would approach it.

library(purrr)
library(dplyr)

cols_nums <- unique(gsub(pattern = "[A-z]", "", names(df))) %>% 
  map(~names(df)[grepl(., names(df))])

map2_df(cols_nums[2], cols_nums[1], function(x, y) {
  out <- df[,x]-df[,y]
  names(out) <- paste0(names(out), "_diff")
  out
})

# x_2_diff y_2_diff
# 1        0        0
# 2        0        0
# 3        0        0
# 4        0        0
# 5        0        0
Related