What is the tidy equivalent of using `sweep` across rows?

Viewed 102

Say I have a set of columns, named Intercept, 1, 2, 3 in my example, and a set of coefficients, named c0 through c3.

xs<-seq(.1,1,.1)
X <- cbind(Intercept=1, "1"=xs, "2"=xs^2, "3"=xs^3)
coefs <- c(c0=10, c1=2, c2=.5, c3=-1)

I want to multiply each column of X by the the corresponding coefficient.

sweep(
  X,     # x: the data array
  2,     # MARGIN: 2, to sweep across rows
  coefs, # STATS: just the array of coefficients
  `*`)   # FUN: the function to use is multiplication

This gives what I want.

But if I had my data as a tibble (tidyX <- as_tibble(X)), what is the tidy way of doing this?

tidyX %>% ... ?

It seems simple, and I imagine it involves dplyr::rowwise(), perhaps, but I don't see the idiomatic way of doing this.

3 Answers

Ah, just as soon as I finally post, I found an answer.

tidyX %>% 
  rowwise() %>% 
  mutate(across() * coefs)

I still find this syntax nonintuitive, but that does just what I'm looking for.

If the column names are the same, we may loop across the columns of 'tidyX' and use the column name (cur_column()) to extract the corresponding column of 'coefs'. But, here the column name is different, so use match to get the column index, extract ([[) the column/element (if it is a named vector) from 'coefs' and multiply

library(dplyr)
tidyX %>% 
   mutate(across(everything(), 
         ~ .x * coefs[[match(cur_column(), names(tidyX))]]))

Or an easier option is map2 (from purrr) to loop over the corresponding columns of both datasets and multiply. If we want the output as a tibble/data.frame, use _dfc

library(purrr)
map2_df(tidyX, coefs, `*`)
# A tibble: 10 × 4
   Intercept   `1`   `2`    `3`
       <dbl> <dbl> <dbl>  <dbl>
 1        10   0.2 0.005 -0.001
 2        10   0.4 0.02  -0.008
 3        10   0.6 0.045 -0.027
 4        10   0.8 0.08  -0.064
 5        10   1   0.125 -0.125
 6        10   1.2 0.18  -0.216
 7        10   1.4 0.245 -0.343
 8        10   1.6 0.32  -0.512
 9        10   1.8 0.405 -0.729
10        10   2   0.5   -1    

Here is an alternative way using pivoting:

library(dplyr)
library(tidyr)

coefs <- c(c0=10, c1=2, c2=.5, c3=-1)

X %>%
  as_tibble() %>% 
  mutate(row = row_number()) %>% 
  pivot_longer(
    -row
  ) %>% 
  mutate(value = value*coefs) %>%
  pivot_wider(
    names_from = name,
    values_from = value
  ) %>% 
  select(-row)
   Intercept   `1`   `2`    `3`
       <dbl> <dbl> <dbl>  <dbl>
 1        10   0.2 0.005 -0.001
 2        10   0.4 0.02  -0.008
 3        10   0.6 0.045 -0.027
 4        10   0.8 0.08  -0.064
 5        10   1   0.125 -0.125
 6        10   1.2 0.18  -0.216
 7        10   1.4 0.245 -0.343
 8        10   1.6 0.32  -0.512
 9        10   1.8 0.405 -0.729
10        10   2   0.5   -1 
Related