Repita cada fila de data.frame el número de veces especificado en una columna

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

¿Cuál es la forma más simple de expandir cada fila las dos primeras columnas del data.frame anterior, para que cada fila se repita el número de veces especificado en la columna 'freq'?

En otras palabras, pasa de esto:

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

A esto:

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

Respuestas:

169

Aquí hay una solución:

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
fuente
¡Excelente! Siempre olvido que puedes usar corchetes de esa manera. Sigo pensando en indexar solo por subconjunto o reordenamiento. Tenía otra solución que es mucho menos elegante y sin duda menos eficiente. Podría publicar de todos modos para que otros puedan comparar.
wkmor1
22
Para grandes data.framemás eficiente es reemplazar row.names(df)con seq.int(1,nrow(df))o seq_len(nrow(df)).
Marek
Esto funcionó fantásticamente para un gran marco de datos: 1,5 millones de filas, 5 cols, fueron muy rápidas. ¡Gracias!
gabe
44
1: 2 codifica la solución de este ejemplo, 1: ncol (df) funcionará para un marco de datos arbitrario.
vladiim
71

vieja pregunta, nuevo verbo en 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
fuente
2
Gracias por una solución tidyverse. Dichas soluciones suelen cumplir los criterios de "simple" y legible.
D. Woods
45

Uso expandRows()del splitstackshapepaquete:

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

Sintaxis simple, muy rápida, funciona en data.frameo 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
fuente
23

La solución de @neilfws funciona muy bien para data.frames, pero no para data.tables ya que carecen de la row.namespropiedad. Este enfoque funciona para ambos:

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

El código para data.tablees un poco más limpio:

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

En caso de que tenga que hacer esta operación en data.frames muy grandes, recomendaría convertirlo en data.table y usar lo siguiente, que debería ejecutarse mucho más rápido:

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

Vea qué tan rápida es esta solución:

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
fuente
Me aparece un error: Error in rep(1, freq) : invalid 'times' argument. Y dado que ya hay una respuesta data.table a esta pregunta, es posible que desee describir cómo su enfoque es diferente o cuándo es mejor que la respuesta data.table actual. O si no hay una diferencia importante, puede agregarlo como un comentario a la respuesta existente.
Sam Firke
@SamFirke: Gracias por tu comentario. Extraño, lo intenté de nuevo y no obtengo tal error. ¿Utiliza el original dfde la pregunta del OP? Mi respuesta es mejor porque la otra respuesta es mal uso del data.tablepaquete mediante el uso de la data.framesintaxis, consulte las preguntas frecuentes de data.table: "Generalmente es una mala práctica referirse a las columnas por número en lugar de por nombre".
vonjd
1
Gracias por la explicación. Su código funciona para mí en la muestra dfpublicada por el OP, pero cuando intenté comparar esto en un marco de datos más grande, obtuve ese error. El data.frame que utilicé fue: 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)) en el pequeño data.frame, la respuesta base funciona bien en mi evaluación comparativa, simplemente no escala bien a data.frames más grandes. Las otras tres respuestas se ejecutaron con éxito con este marco de datos más grande.
Sam Firke
@SamFirke: Esto es realmente extraño, también debería funcionar allí y no sé por qué no. ¿Quieres crear una pregunta a partir de eso o debo?
vonjd
Buena idea. ¿Puedes? No sé la data.tablesintaxis, así que no debería ser yo quien juzgue las respuestas.
Sam Firke
4

Otra dplyralternativa con slicedonde repetimos cada número de fila freqveces

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()) parte se puede reemplazar con cualquiera de los siguientes.

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
fuente
2

Otra posibilidad es 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

Versión de una sola línea de la respuesta 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

Creado el 21/05/2019 por el paquete reprex (v0.2.1)

METRO--
fuente
1

Sé que este no es el caso, pero si necesita mantener la columna de frecuencia original, puede usar otro tidyverseenfoque junto con 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

Creado en 2019-12-21 por el paquete reprex (v0.3.0)

rdornas
fuente
O simplemente usar .remove = FALSEenuncount()
Adam