Как создать последовательный номер группы

#r

#r #r-faq

Вопрос:

У меня есть фрейм данных ( all_data ), в котором у меня есть список сайтов (от 1 … до n) и их оценки, например

   site  score
     1    10
     1    11  
     1    12
     4    10 
     4    11
     4    11
     8    9
     8    8
     8    7
 

Я хочу создать столбец, который нумерует каждый уровень сайта в числовом порядке, например, счетчик. В примере сайты (1, 4 и 8) будут иметь соответствующий счетчик от 1 до 3 в столбце «номер»:

 site  score number
     1    10    1
     1    11    1 
     1    12    1 
     4    10    2
     4    11    2
     4    11    2
     8    9     3
     8    8     3 
     8    7     3
 

Я уверен, что это должно быть легко решено, но я еще не нашел способ.

Комментарии:

1. я полагаю, что это факторная конструкция в R.

Ответ №1:

Попробуйте Data$number <- as.numeric(as.factor(Data$site))

В качестве примечания: разница между решением me и @Chase, с одной стороны, и решением @DWin, с другой, заключается в порядке чисел. Оба as.factor и factor автоматически сортируют уровни, тогда как в решении @DWin этого не происходит :

 Dat <- data.frame(site = rep(c(1,8,4), each = 3), score = runif(9))

Dat$number <- as.numeric(factor(Dat$site))
Dat$sitenum <- match(Dat$site, unique(Dat$site) ) 
 

Дает

 > Dat
  site     score number sitenum
1    1 0.7377561      1       1
2    1 0.3131139      1       1
3    1 0.7862290      1       1
4    8 0.4480387      3       2
5    8 0.3873210      3       2
6    8 0.8778102      3       2
7    4 0.6916340      2       3
8    4 0.3033787      2       3
9    4 0.6552808      2       3
 

Комментарии:

1. при использовании as.factor() автоматически ли сортируются уровни?

2. @Brandon : Действительно, также при использовании factor . Решение, которое не выполняет сортировку, — это решение DWin . Добавлен пример к вопросу.

3. Просто добавьте сюда примечание, в то время как оба метода (фактор и совпадение, уникальный) работают, последний масштабируется в зависимости от размера данных

Ответ №2:

Два других варианта:

1) С помощью .GRP функции из data.table пакета:

 library(data.table)
setDT(dat)[, num := .GRP, by = site]
 

в примере набора данных, приведенном ниже, это приводит к:

 > dat
    site      score num
 1:    1 0.14945795   1
 2:    1 0.60035697   1
 3:    1 0.94643075   1
 4:    8 0.68835336   2
 5:    8 0.50553372   2
 6:    8 0.37293624   2
 7:    4 0.33580504   3
 8:    4 0.04825135   3
 9:    4 0.61894754   3
10:    8 0.96144729   2
11:    8 0.65496051   2
12:    8 0.51029199   2
 

2) Используя group_indices функцию из dplyr :

 dat$num <- group_indices(dat, site)
 

или когда вы хотите обойти нестандартную оценку:

 library(dplyr)
dat %>% 
  mutate(num = group_indices_(dat, .dots = c('site')))
 

что приводит к:

    site      score num
1     1 0.42480366   1
2     1 0.98736177   1
3     1 0.35766187   1
4     8 0.06243182   3
5     8 0.55617002   3
6     8 0.20304632   3
7     4 0.90855921   2
8     4 0.25215078   2
9     4 0.44981251   2
10    8 0.60288270   3
11    8 0.46946587   3
12    8 0.44941782   3
 

Как видно, dplyr дает другой порядок номеров групп.


Если вам нужен другой номер при каждом изменении группы, есть несколько других вариантов:

1) с основанием R:

 # option 1:
dat$num <- cumsum(c(TRUE, head(dat$site, -1) != tail(dat$site, -1)))

# option 2:
x <- rle(dat$site)$lengths
dat$num <- rep(seq_along(x), times=x)
 

2) с data.table помощью пакета:

 library(data.table)
setDT(dat)[, num := rleid(site)]
 

все это приводит к:

 > dat
   site      score num
1     1 0.80817855   1
2     1 0.07881334   1
3     1 0.60092828   1
4     8 0.71477988   2
5     8 0.51384565   2
6     8 0.72011650   2
7     4 0.74994627   3
8     4 0.09564052   3
9     4 0.39782587   3
10    8 0.29446540   4
11    8 0.61725367   4
12    8 0.97427413   4
 

Используемые данные:

 dat <- data.frame(site = rep(c(1,8,4,8), each = 3), score = runif(12))
 

Комментарии:

1. group_indices_() устарел. Какие-либо последствия?

Ответ №3:

В новом dplyr 1.0.0 мы можем использовать cur_group_id() , который присваивает группе уникальный числовой идентификатор.

 library(dplyr)
df %>% group_by(site) %>% mutate(number = cur_group_id())

#  site score number
#  <int> <int>  <int>
#1     1    10      1
#2     1    11      1
#3     1    12      1
#4     4    10      2
#5     4    11      2
#6     4    11      2
#7     8     9      3
#8     8     8      3
#9     8     7      3
 

данные

 df <- structure(list(site = c(1L, 1L, 1L, 4L, 4L, 4L, 8L, 8L, 8L), 
score = c(10L, 11L, 12L, 10L, 11L, 11L, 9L, 8L, 7L)), 
class = "data.frame", row.names = c(NA, -9L))
 

Ответ №4:

Это должно быть достаточно эффективным и понятным:

 Dat$sitenum <- match(Dat$site, unique(Dat$site))  
 

Ответ №5:

Используя данные из @Jaap, другая dplyr возможность использования dense_rank() может быть:

 dat %>%
 mutate(ID = dense_rank(site))

   site     score ID
1     1 0.1884490  1
2     1 0.1087422  1
3     1 0.7438149  1
4     8 0.1150771  3
5     8 0.9978203  3
6     8 0.7781222  3
7     4 0.4081830  2
8     4 0.2782333  2
9     4 0.9566959  2
10    8 0.2545320  3
11    8 0.1201062  3
12    8 0.5449901  3
 

Или rleid() аналогичный dplyr подход, при котором сначала упорядочиваются данные:

 dat %>%
 arrange(site) %>%
 mutate(ID = with(rle(site), rep(seq_along(lengths), lengths)))

   site     score ID
1     1 0.1884490  1
2     1 0.1087422  1
3     1 0.7438149  1
4     4 0.4081830  2
5     4 0.2782333  2
6     4 0.9566959  2
7     8 0.1150771  3
8     8 0.9978203  3
9     8 0.7781222  3
10    8 0.2545320  3
11    8 0.1201062  3
12    8 0.5449901  3
 

Или с помощью duplicated() и cumsum() :

 df %>%
 mutate(ID = cumsum(!duplicated(site)))
 

То же самое с base R :

 df$ID <- with(rle(df$site), rep(seq_along(lengths), lengths))
 

Или:

 df$ID <- cumsum(!duplicated(df$site))
 

Ответ №6:

Вы можете превратить site в фактор, а затем вернуть числовые или целочисленные значения этого фактора:

 dat <- data.frame(site = rep(c(1,4,8), each = 3), score = runif(9))
dat$number <- as.integer(factor(dat$site))
dat

  site     score number
1    1 0.5305773      1
2    1 0.9367732      1
3    1 0.1831554      1
4    4 0.4068128      2
5    4 0.3438962      2
6    4 0.8123883      2
7    8 0.9122846      3
8    8 0.2949260      3
9    8 0.6771526      3
 

Ответ №7:

Другое решение с использованием data.table пакета.

Пример с более полным набором данных, предоставленным Jaap:

 setDT(dat)[, number := frank(site, ties.method = "dense")]
dat
    site     score number
 1:    1 0.3107920      1
 2:    1 0.3640102      1
 3:    1 0.1715318      1
 4:    8 0.7247535      3
 5:    8 0.1263025      3
 6:    8 0.4657868      3
 7:    4 0.6915818      2
 8:    4 0.3558270      2
 9:    4 0.3376173      2
10:    8 0.7934963      3
11:    8 0.9641918      3
12:    8 0.9832120      3
 

Ответ №8:

Другой способ сделать это. Я думаю, это легко получить, даже если вы мало знаете о R:

 library(dplyr)
df <- data.frame('site' = c(1, 1, 1, 4, 4, 4, 8, 8, 8))
df <- mutate(df, 'number' = cumsum(site != lag(site, default=-1)))
 

Ответ №9:

Если вы хотите сохранить существующие столбцы и назначить обратно в тот же фрейм данных…

 my_df <- my_df %>%
    select(everything()) %>% 
    group_by(geo) %>% 
    mutate(geo_id = cur_group_id())
 

И вы можете сделать несколько столбцов таким образом…

 my_df <- my_df %>%
    select(everything()) %>% 
    group_by(geo) %>% 
    mutate(geo_id = cur_group_id()) %>% 
    group_by(state) %>% 
    mutate(state_id = cur_group_id()) %>% 
    group_by(name) %>% 
    mutate(name_id = cur_group_id())
 

Ответ №10:

Мне тоже недавно понадобилось решение этой проблемы. Не нашел эту тему, запустил мою и был перенаправлен сюда (спасибо). Приятно видеть много решений, но для меня (и я считаю, что это хорошая практика) важно масштабируемое решение. Следовательно, мы провели сравнительный анализ нескольких решений, приведенных ниже.

 df <- data.table(country = rep(c('a', 'b', 'b', 'c', 'c', 'c'), 1e7)
                 )

a <-
microbenchmark(factor = {df[, group_id := as.integer(factor(country))]}
               , unique_match = df[, group_id := match(country, unique(country))]
               , rle = df[ , group_id := with(rle(country), rep(seq_along(lengths), lengths))]
               , dup_cumsum = df[,  group_id := cumsum(!duplicated(country))]
               , frank = df[, group_id := frank(country, ties.method = "dense")]
               , GRP = df[, group_id := .GRP, country]
               , rleid = df[, group_id := rleid(country)]
               , cumsum_head_tail = df[, group_id := cumsum(c(TRUE, head(country, -1) != tail(country, -1)))]
               , times = 50
               )
autoplot(a)
 

Тест 50 раз

Казалось бы, подиум удерживается data.table .
Тем не менее, было здорово узнать об альтернативах, например cumsum(!duplicated(country)) . Какая головоломка!

Комментарии:

1. Пожалуйста, обратите внимание, что результат может отличаться между решениями на основе «длины выполнения» и другими. Например. x = c(2, 2, 4, 4, 2) ; data.table::rleid(x) ; as.numeric(as.factor(x))

2. @Henrik вы правы. Я заглянул в rleid справку. Похоже, это не параметр для случаев, когда вектор не упорядочен. Правильно ли я говорю rleid , что вектор должен быть отсортирован заранее?

3. Ну, суть функций длины выполнения заключается в отслеживании последовательных запусков. Например. если вы хотите провести различие между двумя запусками по 2 в моем крошечном примере, очевидно, что вам не следует сначала сортировать вектор. Выбор функции зависит от вашей цели.

Ответ №11:

Если номера site столбца были неупорядоченными, мы могли бы использовать as_factor() в сочетании с fct_inorder() из forcats пакета:

 library(tibble)
library(dplyr)
library(forcats)
all_data_unordered <- tibble(site  = c(1,1,1,8,8,8,4,4,4),
                             score = c(10,11,12,10,11,11,9,8,7))

all_data_unordered |> 
  mutate(number = as_factor(site) |> fct_inorder() |> as.integer())
#> # A tibble: 9 × 3
#>    site score number
#>   <dbl> <dbl>  <int>
#> 1     1    10      1
#> 2     1    11      1
#> 3     1    12      1
#> 4     8    10      2
#> 5     8    11      2
#> 6     8    11      2
#> 7     4     9      3
#> 8     4     8      3
#> 9     4     7      3
 

Создано 2021-11-05 пакетом reprex (v2.0.1)