You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Pandas的多层加权Bootstrap抽样问题

Pandas中实现多类别统一Country集合的Bootstrap抽样

需求说明

  • 对包含Country、Fruit(及Vegetable)列的数据集执行Bootstrap抽样
  • 核心要求:Country与Fruit/Vegetable独立均匀随机抽样
  • 额外约束:同一Bootstrap迭代中,Fruit和Vegetable的抽样Country集合必须完全一致,且避免使用嵌套循环

递进抽样策略

针对需求设计了4种逐步接近目标的抽样方案:

1. 每个Country均匀抽取M个Fruit

对每个Country独立均匀抽取指定数量的Fruit,不涉及Country层面的Bootstrap抽样。

sampled_fruits = df.groupby('Country').sample(n=M, replace=True, random_state=rng)

2. 每个Country按Fruit出现次数反比抽取M个Fruit

在每个Country内,按Fruit全局出现次数的反比权重抽取样本,解决稀有Fruit抽样不足的问题。

df2 = df.join(fruit_weights, on='Fruit')  # 为原数据添加权重列
sampled_fruits = df2.groupby('Country').sample(
    n=M,
    replace=True,
    random_state=rng,
    weights='fruit_weights',
)

3. 整体均匀抽取(平均)M个Fruit,对Country进行Bootstrap抽样

先对Country进行Bootstrap抽样,再基于抽样结果抽取Fruit,实现Country层面的Bootstrap,但Fruit抽样为均匀分布。

sampled_fruits = pd.concat(
    {
        s: df.sample(
            n=df.index.nunique(),  # 抽样数量等于Country的唯一数量
            weights=country_weights,
            replace=True,
            random_state=rng,
        )
        for s in range(M)
    },
    names=['sample', 'Country'],
).reset_index('sample')

4. 按Fruit出现次数反比抽取(平均)M个Fruit,同时对Country进行Bootstrap抽样

结合Country的Bootstrap抽样和Fruit的反比权重抽样,接近目标但存在多类别抽样时Country集合不一致的问题。

df4 = df.join(fruit_weights, on='Fruit')

# 按Country归一化Fruit权重,避免影响Country权重的作用
df4['fruit_weights'] = df4.fruit_weights.groupby('Country').transform(lambda x: x / x.sum())

df4 = df4.join(country_weights, on='Country')

# 计算联合权重
weight_cols = [c for c in df4.columns if '_weights' in c]
weights = df4[weight_cols].prod(axis=1)
df4 = df4.drop(columns=weight_cols)

sampled_fruits = pd.concat(
    {
        s: df4.sample(
            n=df.index.nunique(),
            weights=weights,
            replace=True,
            random_state=rng,
        )
        for s in range(M)
    },
    names=['sample', 'Country'],
).reset_index('sample')

现有方案的问题

策略4已接近需求,但当同时对Vegetable(其类别数量、按Country划分的分布均与Fruit不同)进行抽样时,同一Bootstrap迭代中,Fruit和Vegetable的抽样Country集合不一致——因为每个类别单独执行了Country的Bootstrap抽样,导致迭代间的Country集合不匹配。

优化方案:统一Country集合的多类别Bootstrap抽样

核心思路

先预先生成所有Bootstrap迭代的Country抽样集合,再基于该集合分别对Fruit和Vegetable进行抽样,确保同一迭代的Country集合完全一致,同时避免嵌套循环。

代码实现

扩展示例数据(包含Vegetable列)

import pandas as pd
import numpy as np

# 原Fruit数据集
df_fruit = pd.DataFrame(
    np.array([
        ['USA', 'USA', 'Canada', 'Canada', 'Canada', 'Canada', 'Canada', 'Mexico', 'Mexico', 'Mexico', 'Mexico', 'Brazil', 'Brazil', 'Brazil', 'Brazil', 'UK', 'UK', 'France', 'France', 'Germany', 'Germany', 'Germany', 'Germany', 'Germany', 'Italy', 'Italy', 'Spain', 'Spain', 'Spain', 'Spain', 'Spain'],
        ['Pineapple', 'Apple', 'Pineapple', 'Apple', 'Cherry', 'Watermelon', 'Orange', 'Apple', 'Banana', 'Cherry', 'Orange', 'Watermelon', 'Banana', 'Apple', 'Blueberry', 'Cherry', 'Apple', 'Banana', 'Blueberry', 'Banana', 'Apple', 'Cherry', 'Blueberry', 'Pineapple', 'Pineapple', 'Watermelon', 'Pineapple', 'Watermelon', 'Apple', 'Orange', 'Blueberry'],
    ]).T,
        columns=['Country', 'Fruit'],
).set_index('Country')
df_fruit['other_fruit_cols'] = '...'

# 新增Vegetable数据集(模拟与Fruit不同的分布)
df_veg = pd.DataFrame(
    np.array([
        ['USA', 'Canada', 'Canada', 'Mexico', 'Mexico', 'Mexico', 'Brazil', 'Brazil', 'UK', 'UK', 'UK', 'France', 'Germany', 'Germany', 'Germany', 'Italy', 'Spain', 'Spain', 'Spain'],
        ['Carrot', 'Potato', 'Broccoli', 'Carrot', 'Potato', 'Broccoli', 'Carrot', 'Potato', 'Carrot', 'Potato', 'Broccoli', 'Potato', 'Carrot', 'Potato', 'Broccoli', 'Carrot', 'Potato', 'Broccoli', 'Carrot'],
    ]).T,
        columns=['Country', 'Vegetable'],
).set_index('Country')
df_veg['other_veg_cols'] = '...'

配置参数

M = 10  # Bootstrap迭代次数
NUM_COUNTRIES_PER_SAMPLE = df_fruit.index.nunique()  # 每次抽样的Country数量
rng = np.random.default_rng(seed=123)  # 随机种子确保可复现

# 生成Fruit和Vegetable的权重(反比于出现次数)
fruit_weights = 1 / df_fruit.groupby('Fruit').size().rename('fruit_weights')
fruit_weights /= fruit_weights.sum()

veg_weights = 1 / df_veg.groupby('Vegetable').size().rename('veg_weights')
veg_weights /= veg_weights.sum()

# Country权重(反比于出现次数)
country_weights = 1 / pd.concat([df_fruit.index, df_veg.index]).value_counts().rename('country_weights')
country_weights /= country_weights.sum()

预生成统一的Country抽样集合

# 生成M次迭代的Country抽样结果,形状为(M, NUM_COUNTRIES_PER_SAMPLE)
sampled_countries = rng.choice(
    country_weights.index,
    size=(M, NUM_COUNTRIES_PER_SAMPLE),
    p=country_weights.values,
    replace=True
)

# 转换为DataFrame,标记迭代编号(sample_id)和Country
country_samples_df = pd.DataFrame({
    'sample_id': np.repeat(range(M), NUM_COUNTRIES_PER_SAMPLE),
    'Country': sampled_countries.flatten()
}).set_index(['sample_id', 'Country'])

基于统一Country集合抽取Fruit和Vegetable

# 处理Fruit数据集:添加权重,合并Country抽样集合,按迭代和Country抽样
df_fruit_weighted = df_fruit.join(fruit_weights, on='Fruit')
# 按Country归一化Fruit权重
df_fruit_weighted['fruit_weights'] = df_fruit_weighted['fruit_weights'].groupby('Country').transform(lambda x: x/x.sum())

# 合并预生成的Country抽样集合,确保每个迭代的Country都对应到Fruit数据
fruit_samples = country_samples_df.join(df_fruit_weighted, on='Country')
# 按sample_id和Country分组,每组抽取1个Fruit(可根据需求调整数量)
fruit_sampled_final = fruit_samples.groupby(['sample_id', 'Country']).sample(
    n=1,
    weights='fruit_weights',
    replace=True,
    random_state=rng
).reset_index('Country')

# 处理Vegetable数据集,逻辑完全一致
df_veg_weighted = df_veg.join(veg_weights, on='Vegetable')
df_veg_weighted['veg_weights'] = df_veg_weighted['veg_weights'].groupby('Country').transform(lambda x: x/x.sum())

veg_samples = country_samples_df.join(df_veg_weighted, on='Country')
veg_sampled_final = veg_samples.groupby(['sample_id', 'Country']).sample(
    n=1,
    weights='veg_weights',
    replace=True,
    random_state=rng
).reset_index('Country')

验证一致性

# 检查同一迭代的Country集合是否一致
assert all(fruit_sampled_final.groupby('sample_id').apply(lambda x: set(x.index)) == veg_sampled_final.groupby('sample_id').apply(lambda x: set(x.index)))

内容的提问来源于stack exchange,提问作者Filip

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 15:15:01