# group_map() in dplyr 0.8.1 (still in development)

The release of dplyr v0.8.0 brought some new group functionality - described in the release notes.

Since then, romain francois has worked on some changes to the group operation in this pull request #4251, and things have changed a bit.

The group operations which have just been modified from 0.8.0 and ready for release whenever v0.8.1 comes out are

• group_split() - split a data.frame by the grouping columns into a list
• group_data() - a data.frame of just the grouping variables and the corresponding row indices for the members of the group in the original data.frame.
• group_keys() - identical to group_data() except lacking that .rows (row indicies) data
• group_map() - map a function over each group and return a list
• group_modify() - map a function over each group and return a single data.frame

This is a short post to convince myself I understand what group_map() does.

## group_map() diagram

At its heart group_map() is going the equivalent of a purrr::map2().

The two lists that it is mapping over are:

1. the list of data.frames split by the grouping variables
2. the list of all combinations of the grouping variables

The following diagram hopefully illustrates how I think of group_map()

In the rest of this post I’ll break down the process step-by-step.

## Test Data

A very simple data.frame to use as the test data. Note: There is an empty factor level in the type variable!

test_df <- tibble(
type  = factor(c('a', 'a', 'b', 'b', 'b'), levels = c('a', 'b', 'c')),
value = c(1, 2, 3, 4, 5)
)

test_df
# A tibble: 5 x 2
type  value
<fct> <dbl>
1 a         1
2 a         2
3 b         3
4 b         4
5 b         5

## group_split()

group_split() splits the data into a list of data.frames

• one data.frame for each combination of grouping variables (as specified by group_by())
• use .drop = FALSE in the call to group_by() if you want to keep empty factor levels.
• because keep = FALSE was specified in group_split(), the grouping column is not kept in the split data.frames
data_list <- test_df %>%
group_by(type, .drop = FALSE) %>%
group_split(keep = FALSE)

data_list
[[1]]
# A tibble: 2 x 1
value
<dbl>
1     1
2     2

[[2]]
# A tibble: 3 x 1
value
<dbl>
1     3
2     4
3     5

[[3]]
# A tibble: 0 x 1
# … with 1 variable: value <dbl>

attr(,"ptype")
# A tibble: 0 x 1
# … with 1 variable: value <dbl>

## group_keys()

group_keys() returns a data.frame with just the grouping columns - keeping only one row for each combination of grouping variables.

Transpose the result to get a list of values - one list entry for each row.

Note: I used to use a transpose() here, but because it drops factor levels, I’ve changed this to a pmap(list) instead which preserves factor levels. (thanks to jennybryan)

groups_list <- test_df %>%
group_by(type, .drop = FALSE) %>%
group_keys() %>%
pmap(list)

groups_list
[[1]]
[[1]]$type [1] a Levels: a b c [[2]] [[2]]$type
[1] b
Levels: a b c

[[3]]
[[3]]$type [1] c Levels: a b c ## Calling map2 over the split data.frames and the group information for each split data.frame my_func <- function(.x, .y) { glue("Group '{.y$type}' has {nrow(.x)} rows")
}

map2(data_list, groups_list, my_func)
[[1]]
Group 'a' has 2 rows

[[2]]
Group 'b' has 3 rows

[[3]]
Group 'c' has 0 rows

## group_map() is a shortcut for all the above!

group_map() does all this for you - at a high level you can think of it as doing the following:

• splits the data.frame by the grouping variables
• creates a list of all combinations of grouping variables
• calls map2()
test_df %>%
group_by(type, .drop = FALSE) %>%
group_map(my_func)
[[1]]
Group 'a' has 2 rows

[[2]]
Group 'b' has 3 rows

[[3]]
Group 'c' has 0 rows