Как автоматически сбалансировать набор данных в `dplyr` с использованием `sample_n` до размера наименьшего класса?

#r #dplyr #sampling

#r #dplyr #выборка

Вопрос:

У меня есть набор данных, подобный:

 df <- tibble(
  id = 1:18,
  class = rep(c(rep(1,3),rep(2,2),3),3),
  var_a = rep(c("a","b"),9)
)

# A tibble: 18 x 3
      id cluster var_a
   <int>   <dbl> <chr>
 1     1       1 a    
 2     2       1 b    
 3     3       1 a    
 4     4       2 b    
 5     5       2 a    
 6     6       3 b    
 7     7       1 a    
 8     8       1 b    
 9     9       1 a    
10    10       2 b    
11    11       2 a    
12    12       3 b    
13    13       1 a    
14    14       1 b    
15    15       1 a    
16    16       2 b    
17    17       2 a    
18    18       3 b 
  

Этот набор данных содержит ряд наблюдений в нескольких классах. Классы не сбалансированы. В приведенном выше примере мы можем видеть, что только 3 наблюдения относятся к классу 3, в то время как есть 6 наблюдений класса 2 и 9 наблюдений класса 1.

Теперь я хочу автоматически сбалансировать этот набор данных, чтобы все классы имели одинаковый размер. Итак, я хочу набор данных из 9 строк, по 3 строки в каждом классе. Я могу использовать sample_n функцию из dplyr для выполнения такой выборки.

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

 min_length <- as.numeric(df %>% 
  group_by(class) %>% 
  summarise(n = n()) %>% 
  ungroup() %>% 
  summarise(min = min(n)))
  

..и затем примените sample_n функцию:

 set.seed(1)
df %>% group_by(cluster) %>% sample_n(min_length)

# A tibble: 9 x 3
# Groups:   cluster [3]
     id cluster var_a
  <int>   <dbl> <chr>
1    15       1 a    
2     7       1 a    
3    13       1 a    
4     4       2 b    
5     5       2 a    
6    17       2 a    
7    18       3 b    
8     6       3 b    
9    12       3 b    
  

Я задавался вопросом, возможно ли это сделать (вычислить наименьший размер класса и затем выполнить выборку) за один раз?

Ответ №1:

Вы можете сделать это за один шаг, но это немного обманывает:

 set.seed(42)
df %>%
  group_by(class) %>%
  sample_n(min(table(df$class))) %>%
  ungroup()
# # A tibble: 9 x 3
#      id class var_a
#   <int> <dbl> <chr>
# 1     1     1 a    
# 2     8     1 b    
# 3    15     1 a    
# 4     4     2 b    
# 5     5     2 a    
# 6    11     2 a    
# 7    12     3 b    
# 8    18     3 b    
# 9     6     3 b    
  

Я говорю «обман», потому что обычно вы не хотели бы ссылаться df$ изнутри канала. Однако, поскольку свойство, которое мы ищем, относится ко всему фрейму, но table функция видит только одну группу за раз, нам нужно немного обойти это.

Можно было бы сделать

 df %>%
  mutate(mn = min(table(class))) %>%
  group_by(class) %>%
  sample_n(mn[1]) %>%
  ungroup()
# # A tibble: 9 x 4
#      id class var_a    mn
#   <int> <dbl> <chr> <int>
# 1    14     1 b         3
# 2    13     1 a         3
# 3     7     1 a         3
# 4     4     2 b         3
# 5    16     2 b         3
# 6     5     2 a         3
# 7    12     3 b         3
# 8    18     3 b         3
# 9     6     3 b         3
  

Хотя я не думаю, что это более элегантно / читаемо.

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

1. tidyverse хорош для многих вещей, но он не может заменить всю базу R (и не должен пытаться, imo).