在 R 中复制不替换的分层随机抽样

Posted

技术标签:

【中文标题】在 R 中复制不替换的分层随机抽样【英文标题】:Replicate Stratified Random Sampling without Replacement in R 【发布时间】:2015-04-22 22:38:42 【问题描述】:

我正在努力创建一个矢量化功能解决方案,该解决方案将允许我复制分层随机抽样,而无需在多次迭代中进行替换。我能够在不替换的情况下进行一次采样,然后从数据集中删除这些行,然后从未采样的观察中重复该过程。不幸的是,我需要多次这样做,这使得这个手动选项变得不可能。

我尝试过使用 replicate() 函数,但是我只能对其进行采样,而无需每次通过替换。它将选择的样本放回数据集中以进行下一次采样。

使用下面的代码,我希望该函数创建 30 个新数据集,这些数据集由“一”和“零”集中的 3 个唯一(以前未采样)行组成。因此,每个新数据集将有 6 个总观测值(3-1 和 3-0)并被命名为独特的东西(即“new_dat1”、“new_dat2”...“new_dat30”)。

如果可能,我希望在不使用 for 循环的情况下实现所有这些,因此首选“应用”系列中的某些内容。

set.seed(123)
dat <- data.frame(Outcome = round(runif(160, 0, 1)))
cust <- data.frame(Cust = rep(c("ABC", "DEF", "GHI"), c(45, 80, 35)))
dat <- cbind(cust, dat)

one <- subset(dat, Outcome == 1)
zero <- subset(dat, Outcome == 0)


# Manual option which is not sufficient    
################################################
# sample 1's and remove choosen obs from "one" dataset
set.seed(123)
index <- sample(1:nrow(one), 3, replace = FALSE)
new_dat1 <- one[index, ]
unused_one <- one[-index, ]

# sample 0's and remove choosen obs from "zero" dataset
set.seed(123)
index <- sample(1:nrow(zero), 3, replace = FALSE)
unused_zero <- zero[-index, ]

# combine the 3-1 and 3-0 samples into the first of 30 "new_datn" sets
new_dat1 <- rbind(new_dat1, zero[index, ])

# repeat, now sampling from "unused_one" and "unused_zero" to create "new_dat2" - "new_dat30"
################################################


# Failed attempt using the replicate() function
################################################
set.seed(123)
one_sample <- replicate(30, one[sample(nrow(one), 3, replace = FALSE), ], simplify = FALSE)
zero_sample <- replicate(30, zero[sample(nrow(zero), 3, replace = FALSE), ], simplify = FALSE)

使这更加复杂的是,我在“dat”集中的 0 和 1 观察总数会不时变化,所以我可能总是有余数要处理。因此,该函数必须能够为每个“new_dat”采样 3 个,直到遇到最终集的余数,无论值如何,该余数都可以进入最终的“new_dat”。

即使我能弄清楚如何解决矢量化函数中的采样问题,我也很难让该函数创建新的数据集并适当地命名它们。

如果有人能为我提供一些帮助,我将不胜感激。感谢您花时间阅读我的帖子。

【问题讨论】:

【参考方案1】:

如果我正确理解了您的需求,这是一种解决方案。

首先只是对整个向量进行采样,也就是说,你只是要对行号进行随机排序:

sample_rows  <- sample(nrow(one))

然后为每个随机分布的行分配一个样本组(每组 3 个元素)。由于元素的数量可能不能被 3 整除,因此扩展向量的长度,使其与行数具有相同的长度。现在用下一组填充 NA(我认为这就是您所说的“最后一组的剩余部分”):

sample_group <- rep(1:(length(sample_rows)%/% 3), each = 3)
length(sample_group) <- length(sample_rows)
sample_group[is.na(sample_group)] <- max(sample_group, na.rm = TRUE) + 1

所以现在您有 24 个 3 样本和 1 个 2 样本,无需替换:

samples <- data.frame(sample_rows, sample_group)

head(samples)
  sample_rows sample_group
1          12            1
2           6            1
3          41            1
4          35            2
5          71            2
6          62            2

tail(samples)
   sample_rows sample_group
69          69           23
70          53           24
71          32           24
72          27           24
73          18           25
74          65           25

我为“一”的向量做了这个,但你可以很容易地为零的向量复制它并将它们组合起来。

PS:您可以使用 split()lapply() 从 data.frame 中提取行。例如:

new_dat <- lapply(split(samples$sample_rows, samples$sample_group), function(x) one[x,])

所以new_dat 是一个包含所有 25 个data.frames 的列表。例如:

new_dat[[8]] # gives you the eigth data.frame

或者:

new_dat[[25]] # gives you the last data.frame

【讨论】:

我认为您的sample_group 创作可以通过0:(nrow(one)-1) %/% 3 之类的东西来简化,否则+1 :-) 对不起,如果我的问题不清楚。基本上我要做的就是从一个由一列中的零和一个观察值组成的数据集开始,然后我想创建 30 个由 3 个随机零观察值和 3 个随机 1 观察值组成的新数据集(共 6 行) .剩下的评论意味着零和一的总数不会总是被 3 整除。在我上面的例子中,有 86 个零开始,这意味着我们应该在每个新集合中放置 2.86 个零。 @Carlos Cinelli 谢谢你们两位的cmets。它比我预期的要简单,但我相信如果有一种方法可以自动进行下一步,它将起作用。现在每个观察都分配给一个样本组,有没有办法通过“sample_group”编号自动将它们拉出来并为每个组编号创建新数据集?那么“new_dat1”,“new_dat2”....“new_dat30”?谢谢! @CarlosCinelli @Brian 是的,你可以把它们拉出来,用一条线做到这一点的一种方法是使用split()lapply()。然后你将有一个包含所有 25 个数据帧的列表。 lapply(split(samples$sample_rows, samples$sample_group), function(x) one[x,])。我会将其添加到答案中。 @CarlosCinelli 完美!这会成功的。感谢您花时间帮助我找到答案。我很感激。我是 Stack Oveflow 的新手,所以很遗憾我不能对您的解决方案投赞成票,但同样感谢您。

以上是关于在 R 中复制不替换的分层随机抽样的主要内容,如果未能解决你的问题,请参考以下文章

定义案例的R(分层)随机抽样

随机森林的分层抽样-Python

常见概率抽样方法及其适用场景总结(简单随机抽样分层抽样整群抽样系统抽样)

train_test_split, 关于随机抽样和分层抽样

随机分组和随机抽样的区别

具有人口平衡的分层随机抽样