如何随机过滤行以获得所需的分组变量比例

How to randomly filter rows to achieve desired proportions of a grouping variable

我有一个带有组变量的数据,我想对行进行采样以在组变量中以特定比例结束。这可能需要筛选行,如以下示例所示。

模拟数据

set.seed(2021)

my_df <-
  data.frame(animal = sample(rep(c("dog", "cat", "rabbit"), times = c(150, 4100, 220))),
           weight = sample(5:25, size = 4470, replace = TRUE))

> head(my_df)
##   animal weight
## 1    cat     11
## 2    cat     24
## 3    cat      9
## 4    cat     20
## 5    cat     11
## 6 rabbit      9

这里我们有关于 4470 只动物的数据,可能是猫、狗或兔子,以及每只动物的体重。

如果我们总结每种动物的比例,我们会得到:

library(dplyr)

my_df %>%
  group_by(animal) %>%
  summarise(n = n()) %>%
  mutate(freq = n / sum(n))

## # A tibble: 3 x 3
##   animal     n   freq
## * <chr>  <int>  <dbl>
## 1 cat     4100 0.917 
## 2 dog      150 0.0336
## 3 rabbit   220 0.0492

我们由此得知,在my_df中,91.7%的数据是猫,4.92%是兔子,3.36%是狗。


所需输出:从数据中抽样行以在 animal 上以其他比例结束
我意识到 my_df 中的数据不能代表我研究的人群,因此我想对行进行抽样以改变比例。

我想最终得到由 70% 的猫、15% 的狗和 15% 的兔子组成的数据。显然,我需要丢弃许多 cat 行才能达到这样的分布。

是否有一种简单的方法可以实现这种随机抽样,以满足分组变量的所需比例?


编辑


澄清一下,在 my_df 中要达到 cat:dog:rabbit 之间的理想比例,不仅需要扔掉猫,还可能扔掉狗和兔子。


编辑 2


在评论中,@Limey 从那里建议了 , which is indeed relevant. However, I've tried applying 解决方案,但没有给出预期的输出。

library(purrr)

group_slice_prop <- c(cat = 0.7, dog = 0.15, rabbit = 0.15)

output <-
  my_df %>% 
  split(.$animal) %>% 
  imap_dfr(~ slice_sample(.x, prop = group_slice_prop[.y]))

我们得到 output 是:

output %>%
  group_by(animal) %>%
  summarise(n = n()) %>%
  mutate(freq = n / sum(n))

## # A tibble: 3 x 3
##   animal     n    freq
## * <chr>  <int>   <dbl>
## 1 cat     2870 0.981  
## 2 dog       22 0.00752
## 3 rabbit    33 0.0113 

但我期待总结 output 并得到:

# A tibble: 3 x 3
  animal     n    freq
* <chr>  <int>   <dbl>
1 cat       ?     0.70  
2 dog       ?     0.15
3 rabbit    ?     0.15 

编辑 3


@AnilGoyal 和@Chris Ruehlemann 都提出了 在这种情况下有效的解决方案,但在某种程度上仅限于我提供的玩具数据。我们本可以考虑其他具有不同的、不太直观的比例的场景,或者组变量中更多的级别需要不同的数学来计算每组的 n。我想避免这种情况。我希望在组变量中指定所需的混合比例,并让代码决定从每个组类别中丢弃多少行以达到该混合。

您可以创建一个小标题,其中包含您在最终数据框中所需的动物名称和目标百分比,并基于它为每只动物创建样本行。

library(dplyr)

tibble(animal = c('cat', 'dog', 'rabbit'), 
       prop = c(0.70, 0.15, 0.15), 
       n = nrow(my_df) * prop) %>%
  left_join(my_df, by = 'animal') %>%
  group_by(animal) %>% 
  sample_n(size = first(n), replace = TRUE) %>%
  ungroup %>%
  select(-prop, -n) -> result

检查比例:

result %>% count(animal) %>% mutate(n = prop.table(n))

# animal     n
#  <chr>  <dbl>
#1 cat    0.700
#2 dog    0.150
#3 rabbit 0.150

根据评论中提到的“有 150 只狗和 220 只兔子”并且狗和兔子应该占 15% 的事实,这是一个逐步解决方案:

cats <- my_df %>%
  filter(animal == "cat") %>%
  sample_n(700)
rabbits <- my_df %>%
  filter(animal == "rabbit") %>%
  sample_n(150)
dogs <- my_df %>%
  filter(animal == "dog")

my_newdf <- rbind(cats, rabbits, dogs)

检查:

my_newdf %>%
  group_by(animal) %>%
  summarise(n = n()) %>%
  mutate(freq = n / sum(n))
# A tibble: 3 x 3
  animal     n  freq
* <chr>  <int> <dbl>
1 cat      700  0.7 
2 dog      150  0.15
3 rabbit   150  0.15

鉴于 EDIT-3

编辑的答案
#desired sample sizes
samp <- tibble(animal = c('cat', 'dog', 'rabbit'), 
       prop = c(0.70, 0.15, 0.15)) 

arrange(count(my_df, animal), n) %>% left_join(samp, by = "animal") %>%
  mutate(n1 = first(n)/first(prop),
         n = prop * n1) %>% select(-prop, -n1) %>%
  right_join(my_df, by = "animal") %>%
  group_split(animal) %>%
  map_df(~sample_n(.x, size = first(n))) %>%
  select(-n)
# A tibble: 1,000 x 2
   animal weight
   <chr>   <int>
 1 cat        19
 2 cat         7
 3 cat        17
 4 cat        11
 5 cat        22
 6 cat         8
 7 cat        22
 8 cat        14
 9 cat        22
10 cat        18
# ... with 990 more rows

在不同的 df 上试试这个

set.seed(123)
my_df <-
  data.frame(animal = sample(rep(c("dog", "cat", "rabbit"), times = c(1500, 4100, 220))),
             weight = sample(5:25, size = 5820, replace = TRUE))

library(tidyverse)
samp <- tibble(animal = c('cat', 'dog', 'rabbit'), 
       prop = c(0.70, 0.15, 0.15)) 

arrange(count(my_df, animal), n) %>% left_join(samp, by = "animal") %>%
  mutate(n1 = first(n)/first(prop),
         n = prop * n1) %>% select(-prop, -n1) %>%
  right_join(my_df, by = "animal") %>%
  group_split(animal) %>%
  map_df(~sample_n(.x, size = first(n))) %>%
  select(-n) -> sampled

library(janitor)  
tabyl(sampled$animal)

 sampled$animal    n   percent
            cat 1026 0.6998636
            dog  220 0.1500682
         rabbit  220 0.1500682