Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to return multiple columns from one function inside `mutate` and allow me to name the output columns?

Tags:

r

dplyr

Consider this code:

library(dplyr)
a = data.frame(a = 1:3)
abc = \(x) data.frame(x/2, x/3)
a %>% 
  mutate(abc(a))

which returns

  a x.2       x.3
1 1 0.5 0.3333333
2 2 1.0 0.6666667
3 3 1.5 1.0000000

but I don't have control over the names as I want to assign them something else.

One way to solve this is to the return type of abc to be named but this is not ideal as I may want to run abc on two sets of variables

a %>% 
  mutate(abc(a), abc(b))

The other way is of course is to rename them after each run of abc but then that could be unstable.

Is there to way to give names to the output of abc? e.g.

a %>% 
  mutate(c("a1","a2") := abc(a), c("b1","b2") := abc(b))

But the above := syntax didn't work.

like image 396
xiaodai Avatar asked Dec 20 '25 10:12

xiaodai


1 Answers

1) Try the .unpack= argument of across . Here abc is the unmodified function in the question.

namefn <- function(prefix, x) paste0(prefix, seq_along(x))
a %>%
  mutate(across(a, abc, .unpack = "{namefn('a', inner)}"),
         across(a, abc, .unpack = "{namefn('b', inner)}"))
##   a  a1        a2  b1        b2
## 1 1 0.5 0.3333333 0.5 0.3333333
## 2 2 1.0 0.6666667 1.0 0.6666667
## 3 3 1.5 1.0000000 1.5 1.0000000

or perhaps this variation:

a %>%  mutate(b = a, across(a:b, abc, .unpack = "{outer}{seq_along(inner)}"))
##   a b  a1        a2  b1        b2
## 1 1 1 0.5 0.3333333 0.5 0.3333333
## 2 2 2 1.0 0.6666667 1.0 0.6666667
## 3 3 3 1.5 1.0000000 1.5 1.0000000

2) If names of the form prefix.1, prefix.2, ... (which are almost the same as the question's names) are suitable then use with/cbind instead of mutate as shown. If there is a pattern to the names that could be programmed into abc2 as well.

a %>%
  with(cbind(., a = unname(abc(a)), b = unname(abc(a))))
##   a a.1       a.2 b.1       b.2
## 1 1 0.5 0.3333333 0.5 0.3333333
## 2 2 1.0 0.6666667 1.0 0.6666667
## 3 3 1.5 1.0000000 1.5 1.0000000

3) Another approach is to create a one line wrapper around abc which adds the names not provided by or incorrectly provided by abc. It can be used in either of the two forms shown below expressing the mutate compactly while allowing abc to remain as is without modification.

abc2 <- function(x, ...) setNames(abc(x), c(...))
a %>% 
  mutate( abc2(a, "a1", "a2"), abc2(a, c("b1", "b2")) )
##   a  a1        a2  b1        b2
## 1 1 0.5 0.3333333 0.5 0.3333333
## 2 2 1.0 0.6666667 1.0 0.6666667
## 3 3 1.5 1.0000000 1.5 1.0000000
like image 51
G. Grothendieck Avatar answered Dec 22 '25 23:12

G. Grothendieck