如何随机过滤行以获得所需的分组变量比例
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
我有一个带有组变量的数据,我想对行进行采样以在组变量中以特定比例结束。这可能需要筛选行,如以下示例所示。
模拟数据
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 从那里建议了
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