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

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

¡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

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

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"]

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

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)

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)


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)


O simplemente usar .remove = FALSEenuncount()
Adam
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.