从原始帖子的内容中不清楚它提出的问题是否是如何根据一组分组变量的组合生成分层随机样本?分层随机样本是合适的在这种情况下采用这种方法,因为它确保分组变量的每个组合在采样数据帧中按比例表示。
tidyverse 解决方案
由于问题不包含minimal reproducible example,我们将生成一些数据并说明如何对其进行拆分或分组,然后随机抽样每个子组。
首先,我们重置随机数生成器的种子并构建一个包含 10,000 行产品的数据框,其中 50% 的产品在售,70% 的产品在库存中。
set.seed(1053807)
df <- data.frame(
productType = rep(c("Bed","Mouse","Table","Cellphone","Laptop","Car","Chair","Blanket",
"Sofa","Bicycle"),1000),
offer = ifelse(runif(10000) > .5,"Y","N"),
inventory = ifelse(runif(10000) > .3,"Y","N"),
price = rnorm(10000,200,10)
)
鉴于原始帖子中的三个分组变量,df 对象包含 productType、offer 和 inventory 的 40 个唯一组合。
原始代码尝试使用dplyr 包对数据进行采样。它非常接近一个可行的解决方案。为了对样本进行分层,我们使用group_by()将数据按拆分变量分组,然后对分组数据使用sample_frac()函数生成分层样本。
library(dplyr)
df %>%
group_by(productType,offer,inventory) %>%
sample_frac(0.5) -> sampledData
验证结果
来自 10,000 行数据框的 50% 样本应该有大约 5,000 个观察值。
> nrow(sampledData)
[1] 5001
到目前为止,一切都很好。
然后我们可以通过计算样本每个层中的行数来验证结果,并将它们与输入数据框中每个子组的原始计数进行比较。
# check results
originalCounts <- df %>%
group_by(productType,offer,inventory) %>%
summarise(OriginalCount = n())
sampledData %>%
group_by(productType,offer,inventory) %>%
summarise(SampledCount = n()) %>%
full_join(originalCounts,.) %>%
mutate(SampledPct = round(SampledCount / OriginalCount * 100,2))
...和输出:
# A tibble: 40 x 6
# Groups: productType, offer [20]
productType offer inventory OriginalCount SampledCount SampledPct
<chr> <chr> <chr> <int> <int> <dbl>
1 Bed N N 161 80 49.7
2 Bed N Y 371 186 50.1
3 Bed Y N 132 66 50
4 Bed Y Y 336 168 50
5 Bicycle N N 154 77 50
6 Bicycle N Y 349 174 49.9
7 Bicycle Y N 147 74 50.3
8 Bicycle Y Y 350 175 50
9 Blanket N N 134 67 50
10 Blanket N Y 349 174 49.9
# … with 30 more rows
通过检查数据,我们发现具有偶数个观测值的数据帧会产生精确的 50% 样本,而具有奇数个观测值的数据帧会略高于或低于 50%。
Base R 解决方案
我们也可以使用 Base R 来解决这个问题。这种方法使用原始帖子中的产品类型、报价和库存三个变量,根据这些变量的值组合将数据分成子组,随机抽取从每个子集中采样,并将结果组合到一个数据帧中。
首先,我们为随机数生成器设置种子并构建一个包含 10,000 行产品的数据框,其中 50% 的产品在售,70% 的产品在库存中。
set.seed(1053807)
df <- data.frame(
productType = rep(c("Bed","Mouse","Table","Cellphone","Laptop","Car","Chair","Blanket",
"Sofa","Bicycle"),1000),
offer = ifelse(runif(10000) > .5,"Y","N"),
inventory = ifelse(runif(10000) > .3,"Y","N"),
price = rnorm(10000,200,10)
)
由于我们要分别对产品、报价和库存的每个组合进行采样,因此我们创建了一个组合拆分变量,然后使用它来拆分数据。
splitvar <- paste(df$productType,df$offer,df$inventory,sep="-")
dfList <- split(df,splitvar)
给定 10 种产品的输入数据框参数、2 级报价 (Y / N) 和 2 级库存 (Y / N),这将创建一个包含 40 个数据框的dfList 对象,每个都有不同数量的观察。
然后我们使用lapply()随机选择每个数据帧的大约50%,使用每个数据帧的行数来驱动sample()函数。
sampledDataList <- lapply(dfList,function(x){
x[sample(nrow(x),size = round(.5 * nrow(x))),]
})
此时sampledDataList 对象是一个包含 40 个数据帧的列表,每个数据帧大约有 50% 的行作为原始列表。
为了创建最终的数据框,我们使用do.call(),如下所示。
sampledData <- do.call(rbind,sampledDataList)
当我们检查结果数据框中的观察数量时,我们发现它大约是原始数据大小 (10,000) 的 50%。
> # this should be approximately 5,000 rows
> nrow(sampledData)
[1] 5001
我们可以使用以下代码进一步验证每个数据帧大约是 50% 的样本。
# verify sample percentage by stratum
stratum <- names(sampledDataList)
OriginalCount <- sapply(dfList,nrow)
SampledCount <- sapply(sampledDataList,nrow)
SamplePct <- round(SampledCount / OriginalCount * 100,2)
head(data.frame(stratum,OriginalCount,SampledCount,SamplePct,row.names = NULL),10)
...和输出:
> head(data.frame(stratum,OriginalCount,SampledCount,SamplePct,row.names = NULL),10)
stratum OriginalCount SampledCount SamplePct
1 Bed-N-N 161 80 49.69
2 Bed-N-Y 371 186 50.13
3 Bed-Y-N 132 66 50.00
4 Bed-Y-Y 336 168 50.00
5 Bicycle-N-N 154 77 50.00
6 Bicycle-N-Y 349 174 49.86
7 Bicycle-Y-N 147 74 50.34
8 Bicycle-Y-Y 350 175 50.00
9 Blanket-N-N 134 67 50.00
10 Blanket-N-Y 349 174 49.86
与dplyr 解决方案的情况一样,我们看到具有奇数行的层在原始数据的准确的 50% 中采样多一个或少一个。