'Add 'total' row for each group in a column in df

I have a dataframe where the column size can be grouped. When the dataframe is arranged by size, I would like to show the totals of each column for each group as a row in the df.

library(dplyr)
dat <- data.frame(size = c("S", "XS", "L", "M", "L", "L", "XS"), 
           category = c("shirt", "shirt", "shirt", "shirt", "pants", "hat", "pants"),
           store1 = c(22, 3, 52, 10, 5, 37, 21),
           store2 = c(43, 13, 2, 24, 6, 12, 40))

dat %>%
  arrange(size) 

  size category store1 store2
1    L    shirt     52      2
2    L    pants      5      6
3    L      hat     37     12
4    M    shirt     10     24
5    S    shirt     22     43
6   XS    shirt      3     13
7   XS    pants     21     40

I would like to get something like this

  size category store1 store2
1     L    shirt     52      2
2     L    pants      5      6
3     L      hat     37     12
4        Total L     94     20
5     M    shirt     10     24
6        Total M     10     24
7     S    shirt     22     43
8        Total S     22     43
9    XS    shirt      3     13
10   XS    pants     21     40
11      Total XS     24     53

Any suggestions are appreciated!



Solution 1:[1]

library( tidyverse )
library( janitor )

dat %>%
  dplyr::arrange( size ) %>%
  split( .[,"size"] ) %>%
  purrr::map_df(., janitor::adorn_totals)

#  size category store1 store2
#     L    shirt     52      2
#     L    pants      5      6
#     L      hat     37     12
# Total        -     94     20
#     M    shirt     10     24
# Total        -     10     24
#     S    shirt     22     43
# Total        -     22     43
#    XS    shirt      3     13
#    XS    pants     21     40
# Total        -     24     53

Solution 2:[2]

Note that the request is not tidy data. For this reason, one might want to store the summary statistics version in a different object. That aside, the following creates an object with store totals, and another object where a new category "total" describes store totals for that size.

store_totals <- dat %>%
    group_by(size) %>%
    summarise(store1 = sum(store1),
              store2 = sum(store2))
newdat <- add_row(.data = dat,
        store_totals) %>%
    arrange(size)
newdat$category[is.na(newdat$category)] <- "total"

output of newdat:

 size category store1 store2
1     L    shirt     52      2
2     L    pants      5      6
3     L      hat     37     12
4     L    total     94     20
5     M    shirt     10     24
6     M    total     10     24
7     S    shirt     22     43
8     S    total     22     43
9    XS    shirt      3     13
10   XS    pants     21     40
11   XS    total     24     53

Solution 3:[3]

Using group_modify bind the summary row to the end of the group data and then in the final mutate fix up the size and category fields on the total rows. We use data.frame at the end since the output looks a bit nicer with it but that could be omitted. This could be simplified slightly if the precise form of output shown in the question were not needed. This only uses dplyr.

dat %>% 
  group_by(size) %>%
  group_modify(~ bind_rows(., summarize(., across(where(is.numeric), sum)))) %>%
  ungroup %>%
  mutate(category = coalesce(category, paste("Total", size)),
         size = if_else(startsWith(category, "Total"), "", size)) %>%
  data.frame

giving:

   size category store1 store2
1     L    shirt     52      2
2     L    pants      5      6
3     L      hat     37     12
4        Total L     94     20
5     M    shirt     10     24
6        Total M     10     24
7     S    shirt     22     43
8        Total S     22     43
9    XS    shirt      3     13
10   XS    pants     21     40
11      Total XS     24     53

Solution 4:[4]

Another option would be using nest, especially if you want to filter the data afterward (after size and/or category)

library(dplyr)
library(janitor)
library(tidyr)
library(purrr)

dat %>% 
  arrange(size) %>% 
  group_by(size) %>% 
  nest() %>%
  mutate(data = map(data, ~ .x %>% adorn_totals("row", name = "Total"))) %>%
  unnest(cols = c(data))

# A tibble: 11 x 4
# Groups:   size [4]
   size  category store1 store2
   <chr> <chr>     <dbl>  <dbl>
 1 L     shirt        52      2
 2 L     pants         5      6
 3 L     hat          37     12
 4 L     Total        94     20
 5 M     shirt        10     24
 6 M     Total        10     24
 7 S     shirt        22     43
 8 S     Total        22     43
 9 XS    shirt         3     13
10 XS    pants        21     40
11 XS    Total        24     53

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Wimpel
Solution 2
Solution 3
Solution 4 desval