Повторите каждую строку 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
wkmor1
источник

Ответы:

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
neilfws
источник
Большой! Я всегда забываю, что вы можете использовать квадратные скобки таким образом. Я продолжаю думать об индексации только для поднабора или переупорядочения. У меня было другое решение, которое гораздо менее элегантно и, несомненно, менее эффективно. Я мог бы опубликовать в любом случае, чтобы другие могли сравнить.
wkmor1
22
Для больших data.frameболее эффективным является замена row.names(df)на seq.int(1,nrow(df))или seq_len(nrow(df)).
Марек
Это сработало фантастически для большого фрейма данных - 1,5 миллиона строк, 5 столбцов, прошли очень быстро. Спасибо!
gabe
4
1: 2 жестко кодирует решение этого примера, 1: ncol (df) будет работать для произвольного кадра данных.
владиим
71

старый вопрос, новый глагол в тидиверсе:

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]
Яап
другая альтернативаdf[rep(1:.N, freq)][, freq:=NULL]
Дейл
4

Если вам нужно выполнить эту операцию с очень большими data.frames, я бы порекомендовал преобразовать ее в data.table и использовать следующее, которое должно работать намного быстрее:

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
vonjd
источник
Я получаю сообщение об ошибке: Error in rep(1, freq) : invalid 'times' argument. И, учитывая, что на этот вопрос уже есть ответ data.table, вы можете описать, чем отличается ваш подход или когда он лучше, чем текущий ответ data.table. Или, если нет большой разницы, вы можете добавить его в качестве комментария к существующему ответу.
Сэм Фирке
@SamFirke: Спасибо за ваш комментарий. Странно, я просто попробовал еще раз и не получаю такой ошибки. Используете ли вы оригинал dfиз вопроса ОП? Мой ответ лучше, потому что другой ответ является своего рода неправильным использованием data.tableпакета с использованием data.frameсинтаксиса, см. Раздел часто задаваемых вопросов data.table: «Обычно плохая практика - ссылаться на столбцы по номеру, а не по имени».
vonjd
1
Спасибо за объяснение. Ваш код работает для меня на примере, dfопубликованном OP, но когда я попытался сравнить его с большим массивом данных. Я получил эту ошибку. 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 пакетом представлением (v0.2.1)

M--
источник
1

Я знаю, что это не так, но если вам нужно сохранить исходный столбец freq, вы можете использовать другой 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 с помощью пакета представительства (v0.3.0)

rdornas
источник
Или просто использовать .remove = FALSEвuncount()
Адам