adding default values to item x group pairs that don't have a value (df %>% spread %>% gather seems strange)

There is a new function complete in the development version of tidyr that does this.

df1 %>% complete(itemid, groupid, fill = list(value = 0))
##    itemid groupid value
## 1       1     one     3
## 2       1     two     0
## 3       2     one     2
## 4       2     two     0
## 5       3     one     1
## 6       3     two     0
## 7       4     one     0
## 8       4     two     2
## 9       5     one     0
## 10      5     two     3
## 11      6     one    22
## 12      6     two     1

One possibility is to use expand from tidyr. This approach is very similar to the expand.grid idea of @akrun (it actually uses expand.grid internally). I used the dplyr package for the housekeeping after joining the expanded data with the original data.

This approach is longer than the spread/gather approach. Personally I find it a bit more clear what is going on. In my (rather small) benchmark test, spread/gather performed slightly better than expand/join.

# expand first
expand(df1, itemid, groupid) %>% 
  # then join back to data
  left_join(df1, by = c("itemid", "groupid")) %>%
  # because there is no fill argument in join
  mutate(value = ifelse(is.na(value), 0, value)) %>%
  # rearange
  arrange(groupid, itemid)