rowwise() yields error with mean() and sum()

Viewed 190

I'm trying to get the mean() and sum() for certain columns across rows. This code will produce the dataset:

library(tidyverse)

test_data <- tibble(part_id = 1:5,
                      a_1 = c("a", "b", "c", "d", "a"),
                      a_2 = c("b", NA, "b", "a", "d"),
                      a_3 = c("b", "b", "d", "d", "a"))


test_data <- test_data %>%
  mutate_at(vars(a_1, a_2), .funs = list(scored = ~case_when(
    . == "a" | . == "b" ~ 1,
    . == "c" ~ 0,
    . == "d" ~ -100)))

If I try to use rowSums() or rowMeans(), I get the correct answer:

library(tidyverse)

test_data <- test_data %>%
  mutate(a_total = rowSums(dplyr::select(., contains("scored")), na.rm = TRUE),
         a_mean = rowMeans(dplyr::select(., contains("scored")), na.rm = TRUE))

But, if try using rowwise() followed by sum() or mean(), it doesn't work:

library(tidyverse)

test_data <- test_data %>%
  rowwise() %>%
  mutate(a_total = base::sum(dplyr::select(., contains("scored")), na.rm = TRUE),
         a_mean = base::mean(dplyr::select(., contains("scored")), na.rm = TRUE)) %>%
  ungroup()

For sum(), it gives the total sum, effectively ignoring rowwise(), and for mean(), all answers are NA and I get this warning for each row:

Warning messages:
1: In mean.default(dplyr::select(., contains("scored")), na.rm = TRUE) :
  argument is not numeric or logical: returning NA

I also tried this modification by including the c() function, as you would if you were to list each column. That resulted in the following error:

library(tidyverse)

test_data <- test_data %>%
  rowwise() %>%
  mutate(a_total = base::sum(c(dplyr::select(., contains("scored"))), na.rm = TRUE),
         a_mean = base::mean(c(dplyr::select(., contains("scored"))), na.rm = TRUE)) %>%
  ungroup()

Error in base::sum(c(dplyr::select(., contains("scored"))), na.rm = TRUE) : 
  invalid 'type' (list) of argument

How could I make this work with rowwise()? Why is this behaving so differently than typical and than rowSums() or rowMeans()?

I appreciate any insight!

2 Answers

The issue is that rowwise is doing a grouping by row, and sum, mean etc work on vectors. It is essentially applying on a single row data.frame. By wrapping with unlist, it is converted from data.frame to a vector

library(dplyr)
test_data <- test_data %>%
                  rowwise() %>%
                  mutate(a_total = base::sum(unlist(dplyr::select(., 
                               contains("scored")), recursive = FALSE), na.rm = TRUE),
                         a_mean = base::mean(unlist(dplyr::select(., 
                               contains("scored")), recursive = FALSE), na.rm = TRUE)) %>%
                   ungroup()

Or use pmap

library(purrr)
test_data  %>%
   mutate(a_total = pmap_dbl(select(., contains("scored")),
                    ~ sum(c(...), na.rm = TRUE)),
          a_mean =  pmap_dbl(select(., contains("scored")),
                    ~ mean(c(...), na.rm = TRUE)))

Here's another approach if you want to stick with rowwise() that uses {rlang} to capture the variables you want to sum and average:

library(dplyr)

test_data <- tibble(part_id = 1:5,
                    a_1 = c("a", "b", "c", "d", "a"),
                    a_2 = c("b", NA, "b", "a", "d"),
                    a_3 = c("b", "b", "d", "d", "a"))


test_data <- test_data %>%
  mutate_at(vars(a_1, a_2), .funs = list(scored = ~case_when(
    . == "a" | . == "b" ~ 1,
    . == "c" ~ 0,
    . == "d" ~ -100)))


# Get the names of the variables you want
vars <- test_data %>% select(contains("scored")) %>% names()

# Use `rlang` so that `dplyr` will recognize the variable names
test_data %>%
  rowwise() %>%
  mutate(a_sum = sum(c(!!!rlang::syms(vars)), na.rm = TRUE),
         a_mean = mean(c(!!!rlang::syms(vars)), na.rm = TRUE)) %>% 
  ungroup()
#> # A tibble: 5 x 8
#>   part_id a_1   a_2   a_3   a_1_scored a_2_scored a_sum a_mean
#>     <int> <chr> <chr> <chr>      <dbl>      <dbl> <dbl>  <dbl>
#> 1       1 a     b     b              1          1     2    1  
#> 2       2 b     <NA>  b              1         NA     1    1  
#> 3       3 c     b     d              0          1     1    0.5
#> 4       4 d     a     d           -100          1   -99  -49.5
#> 5       5 a     d     a              1       -100   -99  -49.5

Created on 2020-04-05 by the reprex package (v0.3.0)

Related