Répétez chaque ligne de data.frame le nombre de fois spécifié dans une colonne

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

Quelle est la manière la plus simple de développer chaque ligne les deux premières colonnes du data.frame ci-dessus, de sorte que chaque ligne soit répétée le nombre de fois spécifié dans la colonne 'freq'?

En d'autres termes, partez de ceci:

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

Pour ça:

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

Réponses:

169

Voici une solution:

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

Résultat:

    var1 var2
1      a    d
2      b    e
2.1    b    e
3      c    f
3.1    c    f
3.2    c    f
neilfws
la source
Génial! J'oublie toujours que vous pouvez utiliser les crochets de cette façon. Je continue de penser à l'indexation juste pour le sous-ensemble ou la réorganisation. J'avais une autre solution beaucoup moins élégante et sans doute moins efficace. Je pourrais poster de toute façon pour que les autres puissent comparer.
wkmor1
22
Pour les gros, data.frameplus efficace est de remplacer row.names(df)par seq.int(1,nrow(df))ou seq_len(nrow(df)).
Marek
Cela a fonctionné à merveille pour une trame de données volumineuses - 1,5 million de lignes, 5 cols, s'est déroulée très rapidement. Merci!
gabe
4
1: 2 code en dur la solution à cet exemple, 1: ncol (df) fonctionnera pour une trame de données arbitraire.
vladiim
71

ancienne question, nouveau verbe dans 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
la source
2
Merci pour une solution tidyverse. De telles solutions répondent généralement aux critères de «simple» et de lisibilité.
D. Woods
45

Utilisation à expandRows()partir du splitstackshapepackage:

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

La syntaxe simple, très rapide, fonctionne sur data.frameou data.table.

Résultat:

    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
la source
23

La solution de @ neilfws fonctionne très bien pour data.frames, mais pas pour data.tables car ils n'ont pas la row.namespropriété. Cette approche fonctionne pour les deux:

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

Le code pour data.tableest un peu plus propre:

# convert to data.table by reference
setDT(df)
df.expanded <- df[rep(seq(.N), freq), !"freq"]
Max Ghenis
la source
4
autre alternative:df[rep(seq(.N), freq)][, freq := NULL]
Jaap
une autre alternativedf[rep(1:.N, freq)][, freq:=NULL]
Dale Kube
4

Si vous devez effectuer cette opération sur des data.frames très volumineux, je vous recommande de le convertir en data.table et d'utiliser ce qui suit, qui devrait fonctionner beaucoup plus rapidement:

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

Découvrez à quel point cette solution est plus rapide:

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
la source
Je reçois une erreur: Error in rep(1, freq) : invalid 'times' argument. Et étant donné qu'il existe déjà une réponse data.table à cette question, vous voudrez peut-être décrire en quoi votre approche est différente ou quand elle est meilleure que la réponse data.table actuelle. Ou s'il n'y a pas de différence majeure, vous pouvez l'ajouter en tant que commentaire à la réponse existante à la place.
Sam Firke
@SamFirke: Merci pour votre commentaire. Étrange, je viens de réessayer et je n'obtiens aucune erreur de ce genre. Utilisez-vous l'original dfde la question du PO? Ma réponse est meilleure parce que l'autre réponse est en quelque sorte une mauvaise utilisation du data.tablepackage en utilisant la data.framesyntaxe, voir la FAQ de data.table: "C'est généralement une mauvaise pratique de faire référence aux colonnes par numéro plutôt que par nom."
vonjd
1
Merci pour l'explication. Votre code fonctionne pour moi sur l'exemple dfpublié par l'OP, mais lorsque j'ai essayé de le comparer sur un data.frame plus grand, j'ai eu cette erreur. Le data.frame que j'ai utilisé était le suivant: 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)) sur le minuscule data.frame, la réponse de base fonctionne bien dans mon analyse comparative, elle ne s'adapte tout simplement pas à de plus grandes data.frames. Les trois autres réponses se sont déroulées avec succès avec ce data.frame plus grand.
Sam Firke
@SamFirke: C'est en effet étrange, ça devrait marcher là aussi et je ne sais pas pourquoi ça ne marche pas. Voulez-vous créer une question à partir de celui-ci ou dois-je?
vonjd
Bonne idée. Peut tu? Je ne connais pas la data.tablesyntaxe, donc je ne devrais pas être celui qui juge les réponses.
Sam Firke
4

Une autre dplyralternative avec slicelaquelle nous répétons chaque numéro de ligne freqfois

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 pièce peut être remplacée par l'un des éléments suivants.

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
la source
2

Une autre possibilité consiste à utiliser 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

Version une ligne de la réponse 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

Créé le 21/05/2019 par le package reprex (v0.2.1)

M--
la source
1

Je sais que ce n'est pas le cas, mais si vous devez conserver la colonne de fréquence d'origine, vous pouvez utiliser une autre tidyverseapproche avec 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

Créé le 21/12/2019 par le package reprex (v0.3.0)

rdornas
la source
Ou simplement utiliser .remove = FALSEdansuncount()
Adam