rowwise() sum with vector of column names in dplyr
Asked Answered
L

5

9

I am once again confused about how to achieve this:

Given this data frame:

df <- tibble(
  foo = c(1,0,1),
  bar = c(1,1,1),
  foobar = c(0,1,1)
)

And this vector:

to_sum <- c("foo", "bar")

I would like to get the row-wise sum of the values in the columns to_sum.

Desired output:

# A tibble: 3 x 4
# Rowwise: 
    foo   bar foobar   sum
  <dbl> <dbl>  <dbl> <dbl>
1     1     1      0     2
2     0     1      1     1
3     1     1      1     2

Typing it out works (obviously).

df %>% rowwise() %>% 
  mutate(
    sum = sum(foo, bar)
  )

This does not:

df %>% rowwise() %>% 
  mutate(
    sum = sum(to_sum)
  )

Which I understand, because if I were to try:

df %>% rowwise() %>% 
  mutate(
    sum = sum("foo", "bar")
  )

How can I compute the row-wise sum from a vector of column names?

Libertine answered 19/7, 2021 at 15:59 Comment(0)
P
7

I think you are looking for rlang::syms to coerce strings to quosures:

library(dplyr)
library(rlang)
df %>% 
  rowwise() %>% 
  mutate(
    sum = sum(!!!syms(to_sum))
  )
#     foo   bar foobar   sum
#   <dbl> <dbl>  <dbl> <dbl>
# 1     1     1      0     2
# 2     0     1      1     1
# 3     1     1      1     2
Pustulant answered 19/7, 2021 at 16:12 Comment(2)
concise and elegant.Anticyclone
This is what I was looking for. I tried it with !!syms() and forgot that I should tripple "unbang" it.Libertine
R
6
library(janitor)
df %>%
  adorn_totals("col",,,"sum",to_sum)

 foo bar foobar sum
   1   1      0   2
   0   1      1   1
   1   1      1   2

Why ,,, ?

If you look at ?adorn_totals, you'll see its arguments:

adorn_totals(dat, where = "row", fill = "-", na.rm = TRUE, name = "Total", ...)

The final one ... is to control column selection. There's unfortunately no way to tell R directly that to_sum should be used for that ... argument, so the ,,, in this answer is telling it to use the default values for the arguments where, fill, and na.rm. At that point, it has values for every argument besides ..., so to_sum gets applied to that.

The topic is discussed further here: Specify the dots argument when calling a tidyselect-using function without needing to specify the preceding arguments

Racial answered 19/7, 2021 at 16:18 Comment(3)
Nice! I wasn't familiar with this ,,, technique. Could you please explain a bit more about this.Anticyclone
Yes, I've just added more to the answer explaining it.Racial
Thanks for this solution. I was not aware of your janitor package. I will certainly look into it.Libertine
A
3

You need to use c_across and any_of. This is how it is intended to be used by the RStudio Team: check out vignette("rowwise", package = "dplyr").

library(dplyr)

df %>% 
  rowwise() %>% 
  mutate(sum = sum(c_across(any_of(to_sum))))

#> # A tibble: 3 x 4
#> # Rowwise: 
#>     foo   bar foobar   sum
#>   <dbl> <dbl>  <dbl> <dbl>
#> 1     1     1      0     2
#> 2     0     1      1     1
#> 3     1     1      1     2

c_across is specific for rowwise operations. any_of is needed to interpret to_sum as a character vector containing column names. It works even without it but it is usually preferred to be used.

You may want to ungroup() at the end to remove the rowwise.

Arborvitae answered 19/7, 2021 at 16:4 Comment(0)
A
3

This might help you:

library(dplyr)
library(purrr)
library(rlang)

df %>%
  bind_cols(parse_exprs(to_sum) %>%
              map_dfc(~ eval_tidy(.x, data = df)) %>%
              rowSums()) %>%
  rename(sum = ...4)

# A tibble: 3 x 4
    foo   bar foobar   sum
  <dbl> <dbl>  <dbl> <dbl>
1     1     1      0     2
2     0     1      1     1
3     1     1      1     2
Anticyclone answered 19/7, 2021 at 16:15 Comment(0)
W
2

You could also consider using rowSums:

df %>% 
   mutate(sum = rowSums(across(all_of(to_sum))))

# A tibble: 3 x 4
    foo   bar foobar   sum
  <dbl> <dbl>  <dbl> <dbl>
1     1     1      0     2
2     0     1      1     1
3     1     1      1     2
Winston answered 19/7, 2021 at 17:8 Comment(1)
Nice solution indeed. I accepted the !!!syms() solution, but I like that this does not need the rowwise() function. ThanksLibertine

© 2022 - 2024 — McMap. All rights reserved.