how to apply this function columnwise with cross() in tidyverse?

I need to create new columns based on existing ones using a function in tidyverse. I planned to use across() because it allows dynamic renaming of new variables which is increasingly important in my case and spares a lot of time especially if you have many variables in your data to modify. The function below could not be applied column-wise as expected, it behaves strangely and by changing the values of the P argument I got unexpected output each time, particularly when I set some values to 1, as if the function was applied element-wise but not column-wise.

I wonder how could this code be written in a more efficient way to achieve the above goal, with efficient I mean, shorter, faster, and neater.

Reprex

set.seed (123)
df <- tibble(id = 1:10,
               rosa = runif(10, min = 20.8, max = 36.5),
               lila = runif(10, min = 17, max = 37),
               blaue = runif(10, min = 23.3, max = 32.7))
df[c (2, 5, 8), c (2:4)] <- NA

Code

myfun <- function(x, P = 2, na.rm = FALSE){
    P ^ (min (x, na.rm = na.rm) - x)
}

P <- c(2, 1.5, 1.1) # fiddle with numbers here and see the output each time changes 
names <- c ("rosa", "lila", "blaue")
df %>%
    select(!!names) %>%
    mutate(across(.cols = !!names,
                  .fns = ~myfun(.x, P, na.rm = TRUE),
                  .names = "{.col}_P"))

Output

   + # A tibble: 10 × 6
    rosa  lila blaue    rosa_P     lila_P  blaue_P
   <dbl> <dbl> <dbl>     <dbl>      <dbl>    <dbl>
 1  25.3  36.1  31.7  0.0718    0.0000526  0.00793
 2  NA    NA    NA   NA        NA         NA      
 3  27.2  30.6  29.3  0.581     0.439      0.643  
 4  34.7  28.5  32.6  0.000110  0.0108     0.00401
 5  NA    NA    NA   NA        NA         NA      
 6  21.5  35.0  30.0  1         0.288      0.605  
 7  29.1  21.9  28.4  0.00524   1          0.0753 
 8  NA    NA    NA   NA        NA         NA      
 9  29.5  23.6  26.0  0.469     0.856      0.881  
10  28.0  36.1  24.7  0.0114    0.0000543  1      
Warning messages:
1: Problem while computing `..1 = across(...)`.
ℹ longer object length is not a multiple of shorter object length 
2: Problem while computing `..1 = across(...)`.
ℹ longer object length is not a multiple of shorter object length 
3: Problem while computing `..1 = across(...)`.
ℹ longer object length is not a multiple of shorter object length 

Expected Output

df %>%
select(!!names) %>%
mutate(rosa_P =  2^(min (rosa, na.rm = TRUE) - rosa)) %>%
mutate(lila_P =  1.5^(min (lila, na.rm = TRUE) - lila)) %>%
mutate(blaue_P = 1.1^(min (blaue, na.rm = TRUE) - blaue))

   + # A tibble: 10 × 6
    rosa  lila blaue    rosa_P   lila_P blaue_P
   <dbl> <dbl> <dbl>     <dbl>    <dbl>   <dbl>
 1  25.3  36.1  31.7  0.0718    0.00314   0.514
 2  NA    NA    NA   NA        NA        NA    
 3  27.2  30.6  29.3  0.0192    0.0302    0.643
 4  34.7  28.5  32.6  0.000110  0.0708    0.468
 5  NA    NA    NA   NA        NA        NA    
 6  21.5  35.0  30.0  1         0.00498   0.605
 7  29.1  21.9  28.4  0.00524   1         0.701
 8  NA    NA    NA   NA        NA        NA    
 9  29.5  23.6  26.0  0.00407   0.515     0.881
10  28.0  36.1  24.7  0.0114    0.00320   1    

>Solution :

Your problem is the P vector as across won’t understand which number belongs to what call but will pass all three numbers to myfun. Instead, you could name it, and use cur_column().

It might also be safer to use all_of/any_of instead of !!, and to use another name for the vector of names than names as it’s also a base function. This might lead to confusion.

library(dplyr)

P <- c(rosa = 2, lila = 1.5, blaue = 1.1)
colournames <- names(P) #c("rosa", "lila", "blaue")

df |>
  #select(all_of(colournames)) |>
  mutate(across(all_of(colournames),
                ~ P[cur_column()] ^ (min(., na.rm = TRUE) - .), # ~ my_fun(., P[cur_column()], na.rm = TRUE)
                .names = "{.col}_P"))

Output:

# A tibble: 10 × 6
    rosa  lila blaue    rosa_P   lila_P blaue_P
   <dbl> <dbl> <dbl>     <dbl>    <dbl>   <dbl>
 1  25.3  36.1  31.7  0.0718    0.00314   0.514
 2  NA    NA    NA   NA        NA        NA    
 3  27.2  30.6  29.3  0.0192    0.0302    0.643
 4  34.7  28.5  32.6  0.000110  0.0708    0.468
 5  NA    NA    NA   NA        NA        NA    
 6  21.5  35.0  30.0  1         0.00498   0.605
 7  29.1  21.9  28.4  0.00524   1         0.701
 8  NA    NA    NA   NA        NA        NA    
 9  29.5  23.6  26.0  0.00407   0.515     0.881
10  28.0  36.1  24.7  0.0114    0.00320   1    

Update, changed as OP example changed.

Leave a Reply