Repeating rows of data.frame in dplyr

I was looking for a similar (but slightly different) solution. Posting here in case it's useful to anyone else.

In my case, I needed a more general solution that allows each letter to be repeated an arbitrary number of times. Here's what I came up with:

library(tidyverse)

df <- data.frame(letters = letters[1:4])
df

> df
  letters
1       a
2       b
3       c
4       d

Let's say I want 2 A's, 3 B's, 2 C's and 4 D's:

df %>% 
  mutate(count = c(2, 3, 2, 4)) %>% 
  group_by(letters) %>% 
  expand(count = seq(1:count))

# A tibble: 11 x 2
# Groups:   letters [4]
   letters count
    <fctr> <int>
 1       a     1
 2       a     2
 3       b     1
 4       b     2
 5       b     3
 6       c     1
 7       c     2
 8       d     1
 9       d     2
10       d     3
11       d     4

If you don't want to keep the count column:

df %>% 
  mutate(count = c(2, 3, 2, 4)) %>% 
  group_by(letters) %>% 
  expand(count = seq(1:count)) %>% 
  select(letters)

# A tibble: 11 x 1
# Groups:   letters [4]
   letters
    <fctr>
 1       a
 2       a
 3       b
 4       b
 5       b
 6       c
 7       c
 8       d
 9       d
10       d
11       d

If you want the count to reflect the number of times each letter is repeated:

df %>% 
  mutate(count = c(2, 3, 2, 4)) %>% 
  group_by(letters) %>% 
  expand(count = seq(1:count)) %>% 
  mutate(count = max(count))

# A tibble: 11 x 2
# Groups:   letters [4]
   letters count
    <fctr> <dbl>
 1       a     2
 2       a     2
 3       b     3
 4       b     3
 5       b     3
 6       c     2
 7       c     2
 8       d     4
 9       d     4
10       d     4
11       d     4

This is rife with peril if the data.frame has other columns (there, I said it!), but the do block will allow you to generate a derived data.frame within a dplyr pipe (though, ceci n'est pas un pipe):

library(dplyr)
df <- data.frame(column = letters[1:4], stringsAsFactors = FALSE)
df %>%
  do( data.frame(column = rep(.$column, each = 4), stringsAsFactors = FALSE) )
#    column
# 1       a
# 2       a
# 3       a
# 4       a
# 5       b
# 6       b
# 7       b
# 8       b
# 9       c
# 10      c
# 11      c
# 12      c
# 13      d
# 14      d
# 15      d
# 16      d

As @Frank suggested, a much better alternative could be

df %>% slice(rep(1:n(), each=4))

Using the uncount function will solve this problem as well. The column count indicates how often a row should be repeated.

library(tidyverse)

df <- tibble(letters = letters[1:4])

df 
# A tibble: 4 x 1
  letters
  <chr>  
1 a      
2 b      
3 c      
4 d 

df %>%
  mutate(count = c(2, 3, 2, 4)) %>%
  uncount(count)

# A tibble: 11 x 1
   letters
   <chr> 
 1 a      
 2 a      
 3 b      
 4 b      
 5 b      
 6 c      
 7 c      
 8 d      
 9 d      
10 d      
11 d  

I did a quick benchmark to show that uncount() is a lot faster than expand()

# for the pipe
library(magrittr)

# create some test data
df_test <- 
  tibble::tibble(
    letter = letters,
    row_count = sample(1:10, size = 26, replace = TRUE)
  )

# benchmark
bench <- microbenchmark::microbenchmark(
  expand = df_test %>%
    dplyr::group_by(letter) %>%
    tidyr::expand(row_count = seq(1:row_count)),
  uncount = df_test %>%
    tidyr::uncount(row_count)
)

# plot the benchmark
ggplot2::autoplot(bench)

Benchmark plot

Tags:

R

Dplyr