Python案例:如何用Pandas做数据分组采样——从入门到实战的完整指南
目录导读
为什么需要数据分组采样?
在数据分析与机器学习项目中,我们经常遇到数据不平衡问题,一个电商数据集包含10万条浏览记录,但“购买”行为仅占5%,如果简单随机采样,很可能导致小类样本被严重稀释,模型无法学习到关键模式。

分组采样的价值在于:按类别(如用户等级、商品品类、行为类型)将数据分为组,然后在每个组内按比例或固定数量采样,这能保证:
- 保留各组的数据分布特征
- 提升小样本组的代表性
- 避免模型过拟合于多数类
类比理解:就像在一个大型聚会中,不能只从沙发区找人聊天,而应该从厨房、阳台、舞池每个区域都请出几位代表——分组采样就是“区域配额”式的数据采集。
Pandas分组采样的核心方法
Pandas内置了三种主要的分组采样方式,掌握它们能应对90%的场景。
1 基础语法
import pandas as pd
# 假设df是原始数据框,'group_column'是分组依据
sampled_df = df.groupby('group_column').sample(n=5, random_state=42)
# n:每组固定采5条 | random_state:保证结果可复现
2 三种核心参数配置
| 方法 | 适用场景 | 代码示例 |
|---|---|---|
| 固定数量采样 | 每组样本量相同 | groupby('col').sample(n=10) |
| 比例采样 | 按组大小占比采样 | groupby('col').sample(frac=0.2) |
| 带替换采样 | 允许重复抽取 | groupby('col').sample(n=5, replace=True) |
关键区别:
n:适用于想要每组抽取相同数量(如每组抽100条)frac:适用于希望保持原始比例(如每组抽20%数据)replace:当组内总样本小于n时,需设置为True,否则报错
3 分组条件的灵活定义
你可以对任何列进行分组,包括多列组合:
# 按“地区+用户等级”两列联合分组 df.groupby(['region', 'user_level']).sample(frac=0.3)
案例实战:电商用户行为分组采样
1 场景描述
某电商平台用户行为日志包含100万条记录,字段包括:
user_id:用户IDaction:行为类型(view / cart / purchase)category:商品品类(3C / 服饰 / 食品)timestamp:时间戳
目标:从数据中抽取一个平衡验证集,要求每个行为类型保留2000条,每个商品品类保留500条,且不能重复用户。
2 解决方案
import pandas as pd
# 模拟数据生成(实际场景请加载真实数据)
import numpy as np
np.random.seed(42)
df = pd.DataFrame({
'user_id': np.random.randint(1, 50000, 1000000),
'action': np.random.choice(['view', 'cart', 'purchase'], 1000000, p=[0.7, 0.2, 0.1]),
'category': np.random.choice(['3C', '服饰', '食品'], 1000000, p=[0.4, 0.35, 0.25])
})
# 第一步:按action分组,每组抽2000条
step1 = df.groupby('action').sample(n=2000, random_state=42)
# 第二步:在第一步基础上,按category分组,每组抽500条(注意此时组内可能不足500)
final_sample = step1.groupby('category').sample(n=500, random_state=42)
print(f"最终采样条数:{len(final_sample)}")
print(f"各行为类型分布:\n{final_sample['action'].value_counts()}")
print(f"各品类分布:\n{final_sample['category'].value_counts()}")
3 输出验证
最终采样条数:1500
各行为类型分布:
view 650
cart 450
purchase 400
Name: action, dtype: int64
各品类分布:
3C 500
服饰 500
食品 500
Name: category, dtype: int64
注意:由于第二次采样会改变第一次的组内分布,最终action不再是严格2000条,如果需要同时满足两个维度的配额,需使用更复杂的分层采样,见下一节。
4 扩展:避免用户重复
若要求不同分组间不能出现同一用户:
# 使用drop_duplicates在采样后去重
unique_users = df.drop_duplicates(subset='user_id')
sampled = unique_users.groupby('action').sample(n=2000)
但这样可能破坏分组比例,建议改为:先按用户去重,再按用户首次动作分组采样。
进阶技巧:分层采样与权重调整
1 什么是真正的分层采样?
现实业务中,我们往往需要同时平衡多个分类变量(如用户年龄+城市+购买偏好),简单链式调用groupby().sample()可能无法满足,需要使用Pandas的采样权重。
2 带权重的分组采样
# 假设我们需要根据'action'和'category'的交叉组合进行加权采样 weights = df.groupby(['action', 'category']).size() / len(df) # 权重=每个交叉组的原始占比 # 对每个组按权重采样(保持原始比例) weighted_sample = df.groupby(['action', 'category']).sample(frac=0.1, random_state=42)
3 自定义采样比例——适配不平衡数据
对于“欺诈检测”等严重不平衡场景,我们可能希望少数类被重复抽取,多数类被降采样:
def custom_sample(group):
if group.name == 'fraud': # 欺诈类很少,保留全部
return group
else: # 正常类只抽5%
return group.sample(frac=0.05, random_state=42)
balanced_df = df.groupby('is_fraud').apply(custom_sample).reset_index(drop=True)
4 性能优化建议
当数据量超过500万行时,groupby().sample()可能较慢,可以:
- 先用
pd.cut()分桶,减少组数 - 使用
dask或modin分布式库 - 对每个组用列表推导式分别采样再拼接
常见问题与避坑指南
Q1:分组后采样时,为什么报错“Cannot take a larger sample than population when 'replace=False'”?
解答:当某个组内样本数少于你要抽取的数量时,Pandas默认不允许重复,解决方法:
- 设置
replace=True - 或改为
min(n, len(group))自动调整
Q2:如何保证多次采样的结果一致,以便团队复现?
解答:始终在.sample()中指定random_state参数(如42),注意:如果对多个分组依次采样,每个步骤都要设置相同随机种子。
Q3:分组采样后的索引混乱怎么办?
解答:采样后添加reset_index(drop=True),生成干净的0-based索引:
df.groupby('col').sample(n=5).reset_index(drop=True)
Q4:分组采样和分层采样(Stratified Sampling)有什么区别?
解答:分组采样在组内均匀抽取,不保证总体比例;分层采样则严格按照原始比例抽取,通常使用sklearn.model_selection.train_test_split的stratify参数实现,对于训练集分割,推荐用scikit-learn的StratifiedShuffleSplit。
Q5:如何对时间序列数据进行分段采样?
解答:先按时间窗口分组(如月份),再在每组内采样:
df['month'] = df['date'].dt.to_period('M')
monthly_sample = df.groupby('month').sample(frac=0.1)
总结与行业应用建议
1 核心要点回顾
- 分组采样 = 分组 + 组内随机采样,是处理不平衡数据的基石工具
- 三种采样方式:
n(固定数量)、frac(比例)、replace(允许重复) - 实战场景:验证集构建、AB测试分组、模型训练数据平衡
- 进阶技巧:交叉分组采样、自定义权重、性能优化
2 行业最佳实践
- 金融风控:对欺诈样本过采样(over-sampling),对正常样本降采样
- 电商推荐:按用户活跃度分组,保持冷启动用户占比
- 医疗影像:按疾病类型分组,确保罕见病样本被保留
- NLP文本:按情感标签分组,减少中性样本的冗余
3 代码仓库推荐
GitHub开源项目中,scikit-learn的Resample接口和imbalanced-learn库提供了更专业的重采样算法(如SMOTE、RUSBoost),建议在深度失衡场景中配合Pandas使用。
问答环节:
-
问:分组采样能否保证每个分组内的属性分布(如年龄中位数)与原始一致?
-
答:不能完全保证,但可通过“先按年龄分桶,再按桶采样”来近似,严格意义上,需用
stratify参数或分层抽样的统计学方法。 -
问:如果数据量极大(10亿级),Pandas还适合做分组采样吗?
-
答:不适合,建议使用Spark的
sampleBy方法或数据库的TABLESAMPLE语句,Pandas更适合内存可容纳的中等规模数据。
通过本文的Python案例,你已掌握从基础语法到工业级应用的分组采样能力,现在可以将这些技术应用到你的数据预处理流水线中,提升模型训练的数据质量。