Repita cada linha de data.frame o número de vezes especificado em uma coluna

150
df <- data.frame(var1 = c('a', 'b', 'c'), var2 = c('d', 'e', 'f'),
                 freq = 1:3)

Qual é a maneira mais simples de expandir cada linha das duas primeiras colunas do data.frame acima, para que cada linha seja repetida o número de vezes especificado na coluna 'freq'?

Em outras palavras, vá a partir disso:

df
  var1 var2 freq
1    a    d    1
2    b    e    2
3    c    f    3

Para isso:

df.expanded
  var1 var2
1    a    d
2    b    e
3    b    e
4    c    f
5    c    f
6    c    f
wkmor1
fonte

Respostas:

169

Aqui está uma solução:

df.expanded <- df[rep(row.names(df), df$freq), 1:2]

Resultado:

    var1 var2
1      a    d
2      b    e
2.1    b    e
3      c    f
3.1    c    f
3.2    c    f
neilfws
fonte
Ótimo! Eu sempre esqueço que você pode usar colchetes dessa maneira. Continuo pensando em indexar apenas para subconjuntos ou reordenações. Eu tinha outra solução que é muito menos elegante e sem dúvida menos eficiente. Eu posso postar de qualquer maneira para que outros possam comparar.
Wworor1
22
Para grandes, data.framemais eficiente é substituir row.names(df)por seq.int(1,nrow(df))ou seq_len(nrow(df)).
Marek
Isso funcionou de maneira fantástica para um grande quadro de dados - 1,5 milhão de linhas, 5 cols, foi muito rápido. Obrigado!
gabe
4
1: 2 codifica a solução para este exemplo, 1: ncol (df) funcionará para um quadro de dados arbitrário.
Vladiim 30/08/19
71

pergunta antiga, novo verbo em tidyverse:

library(tidyr) # version >= 0.8.0
df <- data.frame(var1=c('a', 'b', 'c'), var2=c('d', 'e', 'f'), freq=1:3)
df %>% 
  uncount(freq)

    var1 var2
1      a    d
2      b    e
2.1    b    e
3      c    f
3.1    c    f
3.2    c    f
einar
fonte
2
Obrigado por uma solução arrumada. Tais soluções normalmente atendem aos critérios de "simples" e legíveis.
D. Woods
45

Use a expandRows()partir do splitstackshapepacote:

library(splitstackshape)
expandRows(df, "freq")

Sintaxe simples, muito rápida, funciona em data.frameou data.table.

Resultado:

    var1 var2
1      a    d
2      b    e
2.1    b    e
3      c    f
3.1    c    f
3.2    c    f
Sam Firke
fonte
23

A solução do @ neilfws funciona muito bem para data.frames, mas não para data.tables, pois eles não possuem a row.namespropriedade. Essa abordagem funciona para ambos:

df.expanded <- df[rep(seq(nrow(df)), df$freq), 1:2]

O código para data.tableé um pouco mais limpo:

# convert to data.table by reference
setDT(df)
df.expanded <- df[rep(seq(.N), freq), !"freq"]
Max Ghenis
fonte
4
outra alternativa:df[rep(seq(.N), freq)][, freq := NULL]
Jaap
outra alternativadf[rep(1:.N, freq)][, freq:=NULL]
Dale Kube
4

Caso você precise fazer essa operação em data.frames muito grandes, recomendo convertê-la em uma data.table e use o seguinte, que deve ser executado muito mais rapidamente:

library(data.table)
dt <- data.table(df)
dt.expanded <- dt[ ,list(freq=rep(1,freq)),by=c("var1","var2")]
dt.expanded[ ,freq := NULL]
dt.expanded

Veja o quão mais rápida é essa solução:

df <- data.frame(var1=1:2e3, var2=1:2e3, freq=1:2e3)
system.time(df.exp <- df[rep(row.names(df), df$freq), 1:2])
##    user  system elapsed 
##    4.57    0.00    4.56
dt <- data.table(df)
system.time(dt.expanded <- dt[ ,list(freq=rep(1,freq)),by=c("var1","var2")])
##    user  system elapsed 
##    0.05    0.01    0.06
vonjd
fonte
Eu recebo um erro: Error in rep(1, freq) : invalid 'times' argument. E, como já existe uma resposta da tabela de dados para essa pergunta, você pode descrever como sua abordagem é diferente ou quando é melhor que a resposta atual da tabela de dados. Ou, se não houver uma grande diferença, você poderá adicioná-lo como um comentário à resposta existente.
Sam Firke 07/07
@ SamFirke: Obrigado pelo seu comentário. Estranho, tentei novamente e não recebo esse erro. Você usa o original dfda pergunta do OP? Minha resposta é melhor, porque a outra resposta está meio que abusando do data.tablepacote usando a data.framesintaxe, consulte as Perguntas frequentes de data.table: "Geralmente é uma prática ruim se referir às colunas por número e não por nome".
vonjd 07/07/2015
1
Obrigada pelo esclarecimento. Seu código funciona para mim no exemplo dfpostado pelo OP, mas quando tentei comparar isso em um data.frame maior, recebi esse erro. O data.frame que usei foi: set.seed(1) dfbig <- data.frame(var1=sample(letters, 1000, replace = TRUE), var2=sample(LETTERS, 1000, replace = TRUE), freq=sample(1:10, 1000, replace = TRUE)) No minúsculo data.frame, a resposta básica funciona bem nos meus benchmarking, mas não se adapta bem a data.frames maiores. As outras três respostas foram bem-sucedidas com esse data.frame maior.
Sam Firke 07/07
@ SamFirke: Isso é realmente estranho, deve funcionar lá também e não sei por que não funciona. Deseja criar uma pergunta ou devo?
vonjd 07/07/2015
Boa ideia. Você pode? Como não conheço data.tablesintaxe, não deveria ser eu quem julgaria as respostas.
Sam Firke 07/07
4

Outra dplyralternativa com a slicequal repetimos o número de cada linha freqvezes

library(dplyr)

df %>%  
  slice(rep(seq_len(n()), freq)) %>% 
  select(-freq)

#  var1 var2
#1    a    d
#2    b    e
#3    b    e
#4    c    f
#5    c    f
#6    c    f

seq_len(n()) A peça pode ser substituída por qualquer um dos seguintes.

df %>% slice(rep(1:nrow(df), freq)) %>% select(-freq)
#Or
df %>% slice(rep(row_number(), freq)) %>% select(-freq)
#Or
df %>% slice(rep(seq_len(nrow(.)), freq)) %>% select(-freq)
Ronak Shah
fonte
2

Outra possibilidade é usar tidyr::expand:

library(dplyr)
library(tidyr)

df %>% group_by_at(vars(-freq)) %>% expand(temp = 1:freq) %>% select(-temp)
#> # A tibble: 6 x 2
#> # Groups:   var1, var2 [3]
#>   var1  var2 
#>   <fct> <fct>
#> 1 a     d    
#> 2 b     e    
#> 3 b     e    
#> 4 c     f    
#> 5 c     f    
#> 6 c     f

Versão de uma linha da resposta de vonjd :

library(data.table)

setDT(df)[ ,list(freq=rep(1,freq)),by=c("var1","var2")][ ,freq := NULL][]
#>    var1 var2
#> 1:    a    d
#> 2:    b    e
#> 3:    b    e
#> 4:    c    f
#> 5:    c    f
#> 6:    c    f

Criado em 2019-05-21 pelo pacote reprex (v0.2.1)

M--
fonte
1

Sei que esse não é o caso, mas se você precisar manter a coluna freq original, poderá usar outra tidyverseabordagem junto com rep:

library(purrr)

df <- data.frame(var1 = c('a', 'b', 'c'), var2 = c('d', 'e', 'f'), freq = 1:3)

df %>% 
  map_df(., rep, .$freq)
#> # A tibble: 6 x 3
#>   var1  var2   freq
#>   <fct> <fct> <int>
#> 1 a     d         1
#> 2 b     e         2
#> 3 b     e         2
#> 4 c     f         3
#> 5 c     f         3
#> 6 c     f         3

Criado em 2019-12-21 pelo pacote reprex (v0.3.0)

rdornas
fonte
Ou simplesmente usar .remove = FALSEemuncount()
Adam