Сгруппированный неплотный ранг без пропущенных значений - PullRequest
1 голос
/ 09 мая 2020

У меня есть следующий data.frame:

df <- data.frame(date = c(1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3),
                 id   = c(4, 4, 2, 4, 1, 2, 3, 1, 2, 2, 1, 1))

И я хочу добавить новый столбец grp, в котором для каждой даты ранжируются идентификаторы. Связи должны иметь одинаковое значение, но не должно быть пропущенных значений. То есть, если есть два одинаково минимальных значения, они оба должны получить ранг 1, а следующие самые низкие значения должны получить ранг 2.

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

data.frame(date = c(1, 1, 1, 1,     2, 2, 2, 2,     3, 3, 3, 3),
           id   = c(4, 4, 2, 4,     1, 2, 3, 1,     2, 2, 1, 1),
           grp  = c(2, 2, 1, 2,     1, 2, 3, 1,     2, 2, 1, 1))

Я уверен, что есть тривиальный способ сделать это, но я его не нашел: ни одна из опций для tie.method не ведет себя подобным образом (data.table::frank также не помогает, так как он только добавляет плотный ранг).

Я подумал о том, чтобы сделать нормальный ранг, а затем использовать data.table::rleid , но это не сработает, если в тот же день есть повторяющиеся значения, разделенные другими значениями.

Я также подумал о группировке по date и id, а затем с использованием идентификатора группы, но самые низкие значения каждый день должны начинаться с ранга 1, так что это тоже не сработает.

Единственное функциональное решение, которое я нашел, - это создать другую таблицу с уникальным ids в день, а затем присоединиться к этой таблице к этому:

suppressPackageStartupMessages(library(dplyr))

df <- data.frame(date = c(1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3),
                 id   = c(4, 4, 2, 4, 1, 2, 3, 1, 2, 2, 1, 1))

uniques <- df %>%
  group_by(
    date
  ) %>%
  distinct(
    id
  ) %>%
  mutate(
    grp = rank(id)
  )

df <- df %>% left_join(
  unique
) %>% print()
#> Joining, by = c("date", "id")
#>    date id grp
#> 1     1  4   2
#> 2     1  4   2
#> 3     1  2   1
#> 4     1  4   2
#> 5     2  1   1
#> 6     2  2   2
#> 7     2  3   3
#> 8     2  1   1
#> 9     3  2   2
#> 10    3  2   2
#> 11    3  1   1
#> 12    3  1   1

Создано 08.05.2020 с помощью пакета REPEX (v0.3.0)

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

Интересно посмотреть data.table решения, если они доступны, но, к сожалению, решение должно быть в dplyr.

1 Ответ

4 голосов
/ 09 мая 2020

Мы можем использовать dense_rank

library(dplyr)
df %>%
   group_by(date) %>%
   mutate(grp = dense_rank(id))
# A tibble: 12 x 3
# Groups:   date [3]
#   date    id   grp
#   <dbl> <dbl> <int>
# 1     1     4     2
# 2     1     4     2
# 3     1     2     1
# 4     1     4     2
# 5     2     1     1
# 6     2     2     2
# 7     2     3     3
# 8     2     1     1
# 9     3     2     2
#10     3     2     2
#11     3     1     1
#12     3     1     1

Или с frank

library(data.table)
setDT(df)[, grp := frank(id, ties.method = 'dense'), date]
...