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.