Повторіть кожен рядок data.frame кількість разів, вказану в стовпці


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

Який найпростіший спосіб розширити кожен рядок перші два стовпчики даних data.frame вище, щоб кожен рядок повторювався кількість разів, вказаних у стовпці 'freq'?

Іншими словами, перейдіть до цього:

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

До цього:

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

Відповіді:


169

Ось одне рішення:

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

Результат:

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

Чудово! Я завжди забуваю, що ви можете використовувати квадратні дужки таким чином. Я продовжую думати про індексацію лише для підмножини чи переупорядкування. У мене було ще одне рішення, яке є менш елегантним і, без сумніву, менш ефективним. Я все-таки можу розмістити повідомлення, щоб інші могли порівняти.
wkmor1

22
Для великих data.frameбільш ефективним є заміна row.names(df)на seq.int(1,nrow(df))або seq_len(nrow(df)).
Марек

Це спрацювало фантастично для великого фрейму даних - 1,5 мільйона рядків, 5 колод пройшли дуже швидко. Дякую!
Гейб

4
Жорсткий код 1: 2 розв'язує рішення цього прикладу, 1: ncol (df) буде працювати для довільного фрейму даних.
vladiim

71

старе питання, нове дієслово в 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
Дякуємо за охайне рішення. Такі рішення зазвичай відповідають критеріям "простого" та читабельного.
Д. Вудс

45

Використання expandRows()з splitstackshapeпакета:

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

Простий синтаксис, дуже швидкий, працює на data.frameабо data.table.

Результат:

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

23

@ рішення neilfws відмінно підходить для data.frames, але не для data.tables, оскільки їм не вистачає row.namesвластивості. Цей підхід працює для обох:

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

Код для data.tableзасобу для чищення:

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

4
ще одна альтернатива:df[rep(seq(.N), freq)][, freq := NULL]
Jaap

ще одна альтернативаdf[rep(1:.N, freq)][, freq:=NULL]
Дейл Кубе

4

У випадку, якщо вам доведеться виконати цю операцію на дуже великих data.frames, я б рекомендував перетворити її в таблицю даних і використовувати наступне, яке повинно працювати набагато швидше:

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

Подивіться, наскільки швидше це рішення:

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

Я отримую повідомлення про помилку: Error in rep(1, freq) : invalid 'times' argument. А враховуючи, що відповідь на це питання вже є, можливо, ви захочете описати, як ваш підхід відрізняється чи коли він кращий, ніж поточна відповідь. Або якщо немає різниці, ви можете замість цього додати коментар до існуючої відповіді.
Сем Фірк

@SamFirke: Дякую за Ваш коментар. Як не дивно, я просто спробував це ще раз, і я не отримую такої помилки. Чи використовуєте ви оригінал dfз питання ОП? Моя відповідь є кращою, оскільки інша відповідь - це зловживання data.tableпакетом за допомогою data.frameсинтаксису, див. FAQ data.table: "Це, як правило, погана практика посилатися на стовпці за номером, а не за назвою".
vonjd

1
Дякую за пояснення. Ваш код працює для мене на зразку, dfрозміщеному ОП, але коли я спробував порівняти це на більшій data.frame, я отримав цю помилку. Я використовував data.frame: 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)) На крихітному data.frame базовий відповідь справляється добре в моєму бенчмаркінгу, він просто не підходить до масштабних фреймів data.frames. Інші три відповіді успішно працювали з цим більшим data.frame.
Сем Фірк

@SamFirke: Це дійсно дивно, воно теж має працювати там, і я не знаю, чому це не так. Ви хочете створити з нього запитання чи я?
vonjd

Гарна ідея. Ти можеш? Я не знаю data.tableсинтаксису, тому мені не слід судити відповіді.
Сем Фірк

4

Ще одна dplyrальтернатива з sliceякої ми повторюємо кожен номер рядка freqраз

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()) частина може бути замінена будь-яким із наведених нижче.

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

Іншою можливістю є використання 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

Одноланкова версія відповіді Вейнда :

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

Створено 2019-05-21 пакетом reprex (v0.2.1)


1

Я знаю, що це не так, але якщо вам потрібно зберегти оригінальний стовпчик частоти, ви можете використовувати інший tidyverseпідхід разом із 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

Створено 2019-12-21 пакетом reprex (v0.3.0)


Або просто використовувати .remove = FALSEвuncount()
Адам
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.