Ulangi setiap baris data. Membingkai ulang berapa kali ditentukan dalam kolom

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

Apa cara paling sederhana untuk memperluas setiap baris dua kolom pertama dari data.frame di atas, sehingga setiap baris diulang berapa kali ditentukan dalam kolom 'freq'?

Dengan kata lain, pergi dari ini:

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

Untuk ini:

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

Jawaban:

169

Inilah salah satu solusinya:

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

Hasil:

    var1 var2
1      a    d
2      b    e
2.1    b    e
3      c    f
3.1    c    f
3.2    c    f
neilfws
sumber
Bagus! Saya selalu lupa Anda bisa menggunakan tanda kurung dengan cara itu. Saya terus berpikir untuk mengindeks hanya untuk berlangganan atau memesan ulang. Saya punya solusi lain yang jauh kurang elegan dan tidak diragukan lagi kurang efisien. Saya mungkin tetap memposting sehingga orang lain dapat membandingkan.
wkmor1
22
Untuk besar data.framelebih efisien adalah mengganti row.names(df)dengan seq.int(1,nrow(df))atau seq_len(nrow(df)).
Marek
Ini bekerja secara fantastis untuk kerangka data besar - 1,5 juta baris, 5 cols, berjalan sangat cepat. Terima kasih!
gabe
4
Kode keras 1: 2 solusi untuk contoh ini, 1: ncol (df) akan bekerja untuk kerangka data yang sewenang-wenang.
vladiim
71

pertanyaan lama, kata kerja baru dalam 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
sumber
2
Terima kasih atas solusi rapi. Solusi semacam itu biasanya memenuhi kriteria "sederhana" dan mudah dibaca.
D. Woods
45

Gunakan expandRows()dari splitstackshapepaket:

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

Sintaks sederhana, sangat cepat, bekerja pada data.frameatau data.table.

Hasil:

    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
sumber
23

@ neilfws's solusi bekerja sangat baik untuk data.frames, tetapi tidak untuk data.tables karena mereka tidak memiliki row.namesproperti. Pendekatan ini bekerja untuk keduanya:

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

Kode untuk data.tabletad cleaner:

# convert to data.table by reference
setDT(df)
df.expanded <- df[rep(seq(.N), freq), !"freq"]
Max Ghenis
sumber
4
alternatif lain:df[rep(seq(.N), freq)][, freq := NULL]
Jaap
alternatif laindf[rep(1:.N, freq)][, freq:=NULL]
Dale Kube
4

Jika Anda harus melakukan operasi ini pada data.frame yang sangat besar, saya akan merekomendasikan untuk mengubahnya menjadi data.table dan gunakan yang berikut, yang seharusnya berjalan lebih cepat:

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

Lihat seberapa cepat solusi ini:

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
sumber
Saya mendapatkan error: Error in rep(1, freq) : invalid 'times' argument. Dan mengingat sudah ada jawaban data.table untuk pertanyaan ini, Anda mungkin ingin menggambarkan bagaimana pendekatan Anda berbeda atau ketika itu lebih baik daripada jawaban data.table saat ini. Atau jika tidak ada perbedaan besar, Anda bisa menambahkannya sebagai komentar pada jawaban yang ada.
Sam Firke
@SamFirke: Terima kasih atas komentar Anda. Aneh, saya baru mencobanya lagi dan saya tidak mendapatkan kesalahan seperti itu. Apakah Anda menggunakan yang asli dfdari pertanyaan OP? Jawaban saya lebih baik karena jawaban yang lain adalah jenis penyalahgunaan data.tablepaket dengan menggunakan data.framesintaksis, lihat FAQ dari data.table: "Ini adalah praktik yang buruk untuk merujuk pada kolom dengan nomor daripada nama."
vonjd
1
Terima kasih untuk penjelasannya. Kode Anda berfungsi untuk saya pada sampel yang dfdiposting oleh OP, tetapi ketika saya mencoba untuk membandingkan ini pada data.frame yang lebih besar, saya mendapatkan kesalahan itu. Data.frame yang saya gunakan adalah: 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)) Pada data.frame kecil, jawaban dasar tidak baik dalam pembandingan saya, itu hanya skala tidak baik untuk data.frame yang lebih besar. Tiga jawaban lainnya berjalan dengan sukses dengan frame data yang lebih besar ini.
Sam Firke
@ SamFirke: Ini memang aneh, seharusnya bekerja di sana juga dan saya tidak tahu mengapa tidak. Apakah Anda ingin membuat pertanyaan atau haruskah saya melakukannya?
vonjd
Ide bagus. Bisakah kamu? Saya tidak tahu data.tablesintaks, jadi saya tidak boleh menjadi orang yang menilai jawaban.
Sam Firke
4

dplyrAlternatif lain dengan slicetempat kami mengulangi setiap freqkali nomor baris

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()) bagian dapat diganti dengan yang berikut ini.

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
sumber
2

Kemungkinan lain menggunakan 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

Versi satu-baris dari jawaban 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

Dibuat pada 2019-05-21 oleh paket reprex (v0.2.1)

M--
sumber
1

Saya tahu ini bukan masalahnya tetapi jika Anda perlu mempertahankan kolom freq asli, Anda dapat menggunakan tidyversependekatan lain bersama dengan 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

Dibuat pada 2019-12-21 oleh paket reprex (v0.3.0)

rdornas
sumber
Atau cukup gunakan .remove = FALSEdiuncount()
Adam