Ripeti ogni riga di data.frame il numero di volte specificato in una colonna


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

Qual è il modo più semplice per espandere ogni riga le prime due colonne del data.frame sopra, in modo che ogni riga venga ripetuta il numero di volte specificato nella colonna 'freq'?

In altre parole, vai da questo:

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

A questa:

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

Risposte:


169

Ecco una soluzione:

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

Risultato:

    var1 var2
1      a    d
2      b    e
2.1    b    e
3      c    f
3.1    c    f
3.2    c    f

Grande! Dimentico sempre che puoi usare le parentesi quadre in quel modo. Continuo a pensare all'indicizzazione solo per il subsetting o il riordino. Ho avuto un'altra soluzione che è molto meno elegante e senza dubbio meno efficiente. Potrei pubblicare comunque in modo che altri possano confrontare.
wkmor1,

22
Per grande data.framepiù efficiente è sostituire row.names(df)con seq.int(1,nrow(df))o seq_len(nrow(df)).
Marek,

Questo ha funzionato in modo fantastico per un frame di dati di grandi dimensioni: 1,5 milioni di righe, 5 col, sono state molto veloci. Grazie!
Gabe,

4
1: 2 hardcode la soluzione a questo esempio, 1: ncol (df) funzionerà per un frame di dati arbitrario.
vladiim,

71

vecchia domanda, nuovo verbo in ordine:

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

2
Grazie per una soluzione ordinata. Tali soluzioni in genere soddisfano i criteri di "semplice" e leggibile.
D. Woods,

45

Utilizzare expandRows()dal splitstackshapepacchetto:

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

Sintassi semplice, molto veloce, funziona su data.frameo data.table.

Risultato:

    var1 var2
1      a    d
2      b    e
2.1    b    e
3      c    f
3.1    c    f
3.2    c    f

23

La soluzione di @ neilfws funziona alla grande per data.frames, ma non per data.tables poiché manca la row.namesproprietà. Questo approccio funziona per entrambi:

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

Il codice per data.tableè un po 'più pulito:

# convert to data.table by reference
setDT(df)
df.expanded <- df[rep(seq(.N), freq), !"freq"]

4
un'altra alternativa:df[rep(seq(.N), freq)][, freq := NULL]
Jaap

un'altra alternativadf[rep(1:.N, freq)][, freq:=NULL]
Dale Kube l'

4

Nel caso in cui sia necessario eseguire questa operazione su data.frames molto grandi, consiglierei di convertirlo in un data.table e utilizzare quanto segue, che dovrebbe essere eseguito molto più velocemente:

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

Scopri quanto è più veloce questa soluzione:

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

Ottengo un errore: Error in rep(1, freq) : invalid 'times' argument. E dato che esiste già una risposta data.table a questa domanda, potresti voler descrivere come il tuo approccio è diverso o quando è migliore della risposta data.table corrente. Oppure, se non c'è una grande differenza, puoi invece aggiungerlo come commento alla risposta esistente.
Sam Firke

@SamFirke: grazie per il tuo commento. Strano, l'ho appena provato di nuovo e non ricevo questo errore. Usi l'originale dfdalla domanda del PO? La mia risposta è migliore perché l'altra risposta è un po 'di uso improprio del data.tablepacchetto utilizzando la data.framesintassi, vedere le FAQ di data.table: "In genere è una cattiva pratica fare riferimento alle colonne per numero anziché per nome".
vonjd,

1
Grazie per la spiegazione. Il tuo codice funziona per me sull'esempio dfpubblicato dall'OP, ma quando ho provato a confrontarlo su un data.frame più grande ho riscontrato quell'errore. Il data.frame che ho usato era: 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)) sul piccolo data.frame, la risposta di base fa bene nel mio benchmarking, semplicemente non si adatta bene a data.frames più grandi. Le altre tre risposte sono state eseguite correttamente con questo data.frame più grande.
Sam Firke,

@SamFirke: Questo è davvero strano, dovrebbe funzionare anche lì e non so perché non lo faccia. Vuoi creare una domanda da esso o dovrei?
vonjd,

Buona idea. Puoi? Non conosco la data.tablesintassi, quindi non dovrei essere io a giudicare le risposte.
Sam Firke,

4

Un'altra dplyralternativa con slicecui ripetiamo ogni riga il numero di freqvolte

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()) la parte può essere sostituita con una delle seguenti.

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)

2

Un'altra possibilità sta usando 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

Versione di una riga della risposta di 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

Creato il 21/05/2019 dal pacchetto reprex (v0.2.1)


1

So che non è così, ma se è necessario mantenere la colonna freq originale, è possibile utilizzare un altro tidyverseapproccio insieme a 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

Creato il 21-12-2019 dal pacchetto reprex (v0.3.0)


O semplicemente usa .remove = FALSEinuncount()
Adam
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.