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

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

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

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

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

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

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)

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)


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)


Lub po prostu użyj .remove = FALSEwuncount()
Adam
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.