R: `split`, сохраняющий естественный порядок факторов

split всегда будет упорядочивать лексикографически. Могут быть ситуации, когда лучше сохранить естественный порядок. Всегда можно реализовать ручную функцию, но есть ли базовое R-решение, которое это делает?

Воспроизводимый пример:

Input:

  Date.of.Inclusion Securities.Included Securities.Excluded yearmon
1        2013-04-01          INDUSINDBK             SIEMENS  4 2013
2        2013-04-01                NMDC               WIPRO  4 2013
3        2012-09-28               LUPIN                SAIL  9 2012
4        2012-09-28          ULTRACEMCO                STER  9 2012
5        2012-04-27          ASIANPAINT                RCOM  4 2012
6        2012-04-27          BANKBARODA              RPOWER  4 2012

split вывод:

R> split(nifty.dat, nifty.dat$yearmon)
$`4 2012`
  Date.of.Inclusion Securities.Included Securities.Excluded yearmon
5        2012-04-27          ASIANPAINT                RCOM  4 2012
6        2012-04-27          BANKBARODA              RPOWER  4 2012

$`4 2013`
  Date.of.Inclusion Securities.Included Securities.Excluded yearmon
1        2013-04-01          INDUSINDBK             SIEMENS  4 2013
2        2013-04-01                NMDC               WIPRO  4 2013

$`9 2012`
  Date.of.Inclusion Securities.Included Securities.Excluded yearmon
3        2012-09-28               LUPIN                SAIL  9 2012
4        2012-09-28          ULTRACEMCO                STER  9 2012

Обратите внимание, что yearmon уже отсортирован в определенном порядке, который мне понравится. Это можно принять за задание, потому что вопрос немного неверно задан, если это не выполняется.

Требуемый вывод:

$`4 2013`
  Date.of.Inclusion Securities.Included Securities.Excluded yearmon
1        2013-04-01          INDUSINDBK             SIEMENS  4 2013
2        2013-04-01                NMDC               WIPRO  4 2013

$`9 2012`
  Date.of.Inclusion Securities.Included Securities.Excluded yearmon
3        2012-09-28               LUPIN                SAIL  9 2012
4        2012-09-28          ULTRACEMCO                STER  9 2012

$`4 2012`
  Date.of.Inclusion Securities.Included Securities.Excluded yearmon
5        2012-04-27          ASIANPAINT                RCOM  4 2012
6        2012-04-27          BANKBARODA              RPOWER  4 2012

Спасибо.

PS: Я знаю, что есть лучшие способы создать yearmon, чтобы сохранить этот порядок, но я ищу универсальное решение.

Ответ 1

split преобразует аргумент f (второй) в факторы, если он еще не один. Итак, если вы хотите, чтобы заказ был сохранен, поместите столбец самостоятельно с нужным уровнем. То есть:

df$yearmon <- factor(df$yearmon, levels=unique(df$yearmon))
# now split
split(df, df$yearmon)
# $`4_2013`
#   Date.of.Inclusion Securities.Included Securities.Excluded yearmon
# 1        2013-04-01          INDUSINDBK             SIEMENS  4_2013
# 2        2013-04-01                NMDC               WIPRO  4_2013

# $`9_2012`
#   Date.of.Inclusion Securities.Included Securities.Excluded yearmon
# 3        2012-09-28               LUPIN                SAIL  9_2012
# 4        2012-09-28          ULTRACEMCO                STER  9_2012

# $`4_2012`
#   Date.of.Inclusion Securities.Included Securities.Excluded yearmon
# 5        2012-04-27          ASIANPAINT                RCOM  4_2012
# 6        2012-04-27          BANKBARODA              RPOWER  4_2012

Но не используйте split. Вместо этого используйте data.table:

Как правило, split имеет тенденцию быть ужасно медленным по мере увеличения уровней. Итак, я бы предложил использовать data.table для подмножества в список. Я бы предположил, что это будет намного быстрее!

require(data.table)
dt <- data.table(df)
dt[, grp := .GRP, by = yearmon]
setkey(dt, grp)
o2 <- dt[, list(list(.SD)), by = grp]$V1

Бенчмаркинг по огромным данным:

set.seed(45)
dates <- seq(as.Date("1900-01-01"), as.Date("2013-12-31"), by = "days")
ym <- do.call(paste, c(expand.grid(1:500, 1900:2013), sep="_"))

df <- data.frame(x1 = sample(dates, 1e4, TRUE), 
                 x2 = sample(letters, 1e4, TRUE), 
                 x3 = sample(10, 1e4, TRUE), 
                 yearmon = sample(ym, 1e4, TRUE), 
      stringsAsFactors=FALSE)

require(data.table)
dt <- data.table(df)

f1 <- function(dt) {
    dt[, grp := .GRP, by = yearmon]
    setkey(dt, grp)

    o1 <- dt[, list(list(.SD)), by=grp]$V1
}

f2 <- function(df) {
    df$yearmon <- factor(df$yearmon, levels=unique(df$yearmon))
    o2 <- split(df, df$yearmon)
}

require(microbenchmark)
microbenchmark(o1 <- f1(dt), o2 <- f2(df), times = 10)

# Unit: milliseconds
         expr        min         lq     median        uq      max neval
#  o1 <- f1(dt)   43.72995   43.85035   45.20087  715.1292 1071.976    10
#  o2 <- f2(df) 4485.34205 4916.13633 5210.88376 5763.1667 6912.741    10

Обратите внимание, что решение из o1 будет белым списком. Но вы можете установить имена просто, выполнив names(o1) <- unique(dt$yearmon)