Wiederholen Sie jede Datenzeile so oft, wie in einer Spalte angegeben


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

Was ist der einfachste Weg, um jede Zeile um die ersten beiden Spalten des obigen Datenrahmens zu erweitern, sodass jede Zeile so oft wiederholt wird, wie in der Spalte 'freq' angegeben?

Mit anderen Worten, gehen Sie davon aus:

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

Dazu:

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

Antworten:


168

Hier ist eine Lösung:

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

Ergebnis:

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

Toll! Ich vergesse immer, dass Sie auf diese Weise eckige Klammern verwenden können. Ich denke immer wieder an die Indizierung, nur um sie zu unterteilen oder neu zu ordnen. Ich hatte eine andere Lösung, die weit weniger elegant und zweifellos weniger effizient ist. Ich könnte sowieso posten, damit andere vergleichen können.
wkmor1

22
Für großen data.frameeffizienter ist , zu ersetzen , row.names(df)mit seq.int(1,nrow(df))oder seq_len(nrow(df)).
Marek

Dies funktionierte fantastisch für einen Big-Data-Frame - 1,5 Millionen Zeilen, 5 Spalten, gingen sehr schnell. Vielen Dank!
gabe

4
1: 2-Hardcodes Die Lösung für dieses Beispiel: 1: ncol (df) funktioniert für einen beliebigen Datenrahmen.
Vladiim

70

alte Frage, neues Verb in 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

1
Vielen Dank für eine ordentliche Lösung. Solche Lösungen erfüllen typischerweise die Kriterien "einfach" und lesbar.
D. Woods

44

Verwendung expandRows()aus der splitstackshapePackung:

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

Einfache Syntax, sehr schnell, funktioniert auf data.frameoder data.table.

Ergebnis:

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

23

Die Lösung von @ neilfws funktioniert hervorragend für data.frames, aber nicht für data.tables, da ihnen die row.namesEigenschaft fehlt . Dieser Ansatz funktioniert für beide:

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

Der Code für data.tableist ein bisschen sauberer:

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

4
eine andere Alternative:df[rep(seq(.N), freq)][, freq := NULL]
Jaap

eine andere Alternativedf[rep(1:.N, freq)][, freq:=NULL]
Dale Kube

4

Falls Sie diesen Vorgang für sehr große data.frames ausführen müssen, würde ich empfehlen, ihn in eine data.table zu konvertieren und Folgendes zu verwenden, das viel schneller ausgeführt werden sollte:

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

Sehen Sie, wie viel schneller diese Lösung ist:

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

Ich erhalte eine Fehlermeldung : Error in rep(1, freq) : invalid 'times' argument. Und da es bereits eine Antwort auf diese Frage gibt, möchten Sie möglicherweise beschreiben, wie sich Ihr Ansatz unterscheidet oder wann er besser ist als die aktuelle Antwort auf die Datentabelle. Wenn es keinen großen Unterschied gibt, können Sie ihn stattdessen als Kommentar zur vorhandenen Antwort hinzufügen.
Sam Firke

@ SamFirke: Danke für deinen Kommentar. Seltsam, ich habe es gerade noch einmal versucht und bekomme keinen solchen Fehler. Verwenden Sie das Original dfaus der Frage des OP? Meine Antwort ist besser, weil die andere Antwort darin besteht, das data.tablePaket mithilfe der data.frameSyntax zu missbrauchen. Weitere Informationen finden Sie in den häufig gestellten Fragen data.table: "Es ist im Allgemeinen eine schlechte Praxis, Spalten eher nach Nummer als nach Name zu referenzieren."
vonjd

1
Danke für die Erklärung. Ihr Code funktioniert für mich in dem dfvom OP veröffentlichten Beispiel, aber als ich versuchte, dies mit einem größeren Datenrahmen zu vergleichen, wurde dieser Fehler angezeigt. Der von mir verwendete data.frame war: 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)) Auf dem winzigen data.frame funktioniert die Basisantwort in meinem Benchmarking gut, sie lässt sich einfach nicht gut auf größere data.frames skalieren. Die anderen drei Antworten wurden mit diesem größeren Datenrahmen erfolgreich ausgeführt.
Sam Firke

@ SamFirke: Das ist in der Tat seltsam, es sollte auch dort funktionieren und ich weiß nicht, warum es nicht funktioniert. Möchten Sie daraus eine Frage erstellen oder soll ich?
vonjd

Gute Idee. Können Sie? Ich kenne die data.tableSyntax nicht, daher sollte ich nicht derjenige sein, der die Antworten beurteilt.
Sam Firke

4

Eine weitere dplyrAlternative, bei der slicewir jede Zeile freqmehrmals wiederholen

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()) Teil kann durch eines der folgenden ersetzt werden.

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

Eine andere Möglichkeit ist die Verwendung von 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

Einzeilige Version von vonjds Antwort :

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

Erstellt am 21.05.2019 durch das reprex-Paket (v0.2.1)


1

Ich weiß, dass dies nicht der Fall ist, aber wenn Sie die ursprüngliche Freq-Spalte beibehalten müssen, können Sie einen anderen tidyverseAnsatz verwenden, zusammen mit 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

Erstellt am 21.12.2019 vom reprex-Paket (v0.3.0)


Oder verwenden Sie einfach .remove = FALSEinuncount()
Adam
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.