Powtórz każdy wiersz data.frame tyle razy, ile określono w kolumnie

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

Jaki jest najprostszy sposób na rozwinięcie każdego wiersza do pierwszych dwóch kolumn ramki data.frame powyżej, tak aby każdy wiersz był powtarzany tyle razy, ile określono w kolumnie „freq”?

Innymi słowy, przejdź od tego:

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

Do tego:

df.expanded
  var1 var2
1    a    d
2    b    e
3    b    e
4    c    f
5    c    f
6    c    f
wkmor1
źródło

Odpowiedzi:

169

Oto jedno rozwiązanie:

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

Wynik:

    var1 var2
1      a    d
2      b    e
2.1    b    e
3      c    f
3.1    c    f
3.2    c    f
neilfws
źródło
Wspaniały! Zawsze zapominam, że możesz w ten sposób używać nawiasów kwadratowych. Ciągle myślę o indeksowaniu tylko w celu podzbioru lub zmiany kolejności. Miałem inne rozwiązanie, które jest znacznie mniej eleganckie i bez wątpienia mniej wydajne. Mógłbym mimo wszystko opublikować, aby inni mogli porównać.
wkmor1
22
Za duża data.framebardziej efektywna jest zastąpienie row.names(df)z seq.int(1,nrow(df))lub seq_len(nrow(df)).
Marek
To zadziałało fantastycznie dla dużej ramki danych - 1,5 miliona wierszy, 5 kolumn, poszło bardzo szybko. Dzięki!
gabe
4
Koduje 1: 2 rozwiązanie z tego przykładu, 1: ncol (df) będzie działać dla dowolnej ramki danych.
vladiim
71

stare pytanie, nowy czasownik w 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
źródło
2
Dzięki za schludne rozwiązanie. Takie rozwiązania zazwyczaj spełniają kryteria „prostoty” i czytelności.
D. Woods
45

Użyj expandRows()z splitstackshapeopakowania:

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

Prosta składnia, bardzo szybka, działa na data.framelub data.table.

Wynik:

    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
źródło
23

Rozwiązanie @neilfws działa świetnie w przypadku data.frames, ale nie w przypadku data.tables, ponieważ brakuje im row.nameswłaściwości. To podejście działa w obu przypadkach:

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

Kod data.tablejest odrobinę czystszy:

# convert to data.table by reference
setDT(df)
df.expanded <- df[rep(seq(.N), freq), !"freq"]
Max Ghenis
źródło
4
inna alternatywa:df[rep(seq(.N), freq)][, freq := NULL]
Jaap
inna alternatywadf[rep(1:.N, freq)][, freq:=NULL]
Dale Kube
4

W przypadku, gdy musisz wykonać tę operację na bardzo dużych ramkach data.frames, polecam przekonwertowanie go na plik data.table i skorzystanie z następującego, które powinno działać znacznie szybciej:

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

Zobacz, o ile szybsze jest to rozwiązanie:

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
źródło
I pojawia się błąd: Error in rep(1, freq) : invalid 'times' argument. Biorąc pod uwagę, że istnieje już odpowiedź data.table na to pytanie, możesz chcieć opisać, w jaki sposób Twoje podejście jest inne lub kiedy jest lepsze niż bieżąca odpowiedź data.table. Jeśli nie ma większej różnicy, możesz zamiast tego dodać go jako komentarz do istniejącej odpowiedzi.
Sam Firke,
@SamFirke: Dziękuję za komentarz. Dziwne, po prostu spróbowałem ponownie i nie mam takiego błędu. Czy używasz oryginału dfz pytania OP? Moja odpowiedź jest lepsza, ponieważ druga odpowiedź to rodzaj nadużycia data.tablepakietu poprzez użycie data.frameskładni, zobacz często zadawane pytania data.table: „Generalnie złą praktyką jest odnoszenie się do kolumn według numeru, a nie nazwy”.
vonjd
1
Dziękuję za wyjaśnienie. Twój kod działa dla mnie na próbce dfopublikowanej przez OP, ale kiedy próbowałem porównać to z większym data.frame, otrzymałem ten błąd. Data.frame, której użyłem, brzmiała: 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)) W przypadku malutkiego data.frame podstawowa odpowiedź działa dobrze w moim benchmarkingu, po prostu nie skaluje się dobrze do większych data.frames. Pozostałe trzy odpowiedzi działały pomyślnie z tą większą ramką data.frame.
Sam Firke,
@SamFirke: To jest rzeczywiście dziwne, powinno też tam działać i nie wiem, dlaczego tak nie jest. Chcesz stworzyć z tego pytanie, czy powinienem?
vonjd
Dobry pomysł. Czy możesz? Nie znam data.tableskładni, więc nie powinienem oceniać odpowiedzi.
Sam Firke,
4

Inna dplyralternatywa slicepolegająca na tym, że powtarzamy liczbę freqrazy każdy wiersz

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()) część można wymienić na dowolną z poniższych.

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
źródło
2

Inną możliwością jest użycie 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

Jednowierszowa wersja odpowiedzi vonjda :

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

Utworzono 21.05.2019 r. Przez pakiet reprex (v0.2.1)

M--
źródło
1

Wiem, że tak nie jest, ale jeśli chcesz zachować oryginalną kolumnę freq, możesz użyć innego tidyversepodejścia wraz z 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

Utworzony 21.12.2019 przez pakiet reprex (v0.3.0)

rdornas
źródło
Lub po prostu użyj .remove = FALSEwuncount()
Adam