sample函数不支持分层抽样,因其仅进行简单随机抽样;分层抽样需先按字段分组(如pandas的groupby+sample或sklearn的train_test_split+stratify),再各组独立抽样并合并。

sample函数本身不支持分层抽样
sample() 是 random 模块里针对序列(如列表、元组)的简单随机抽样函数,它只认“一维扁平结构”,没有分组、标签或条件逻辑。你传给它一个列表,它就从中无差别地挑几个元素;想按类别比例抽,它完全不知道“类别”在哪——这不是它该干的活。
常见错误现象:有人把不同类别的数据拼成一个大列表,再用 random.sample() 抽,结果某类全没抽到,或比例严重失衡。这不是抽得不好,是方法用错了。
- 分层抽样必须先按某个字段(比如
"category"或"label")分组 - 每组内独立调用
sample()(或df.sample()),并控制数量(固定数 or 比例) - 最后把各组样本合并
pandas DataFrame中用groupby + sample实现分层抽样
这是最常用、最直观的做法。假设你有一个 pd.DataFrame 叫 df,其中列 "group" 表示分层依据:
import pandas as pd
# 按 group 列分层,每组抽 5 行
stratified_sample = df.groupby("group", group_keys=False).apply(lambda x: x.sample(5))
<h1>或按比例抽(比如每组抽 20%)</h1><p>stratified_sample = df.groupby("group", group_keys=False).apply(lambda x: x.sample(frac=0.2))</p>
注意点:
-
group_keys=False很关键,否则结果索引会多出一层 group 名,后续处理容易出错 -
frac和n不能同时设;frac小于 1 时自动转为整数向下取整,若某组只有 3 行却设frac=0.5,可能抽不到(因为 3×0.5=1.5 → 取整为 1,没问题);但设frac=0.1就会报ValueError: Cannot take a larger sample than population when 'replace=False' - 如果某组行数少于你要抽的数量,会直接报错;加
replace=True可放回抽样,但通常分层抽样要求不放回,所以建议先用df["group"].value_counts()检查各层基数
用scikit-learn的train_test_split做带分层的划分
如果你的目标是训练集/测试集划分(比如分类任务),train_test_split 的 stratify 参数就是专为此设计的:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(
X, y,
test_size=0.3,
stratify=y, # y 是标签数组,确保训练/测试中各类比例一致
random_state=42
)
这个方法底层自动按 y 的唯一值分层,并在每层内独立划分。它不返回原始索引,也不保留 DataFrame 结构,适合建模流程,但不适合需要保留原始行结构或自定义每层样本量的场景。
-
stratify必须是一维数组,长度和X行数一致;如果是 DataFrame,需传y=df["label"] - 如果
y中某类样本数太少(比如只有 1 个),而test_size=0.3导致测试集至少需要 1 个该类样本,它仍能保证分层,但可能让该类在训练集中为 0 —— 这时会报ValueError: The least populated class in y has only 1 member,需手动过滤或合并稀有类
手动分组+sample时容易忽略的索引问题
用 groupby().apply(sample) 后,结果默认保留原始索引(即抽出来的行还带着原来的行号)。这在后续 merge 或对齐时很方便,但如果你打算把结果当新表反复操作,可能会遇到重复索引或排序混乱的问题:
- 抽样后立刻重置索引:
stratified_sample.reset_index(drop=True) - 如果原数据索引很重要(比如时间序列 ID),别用
reset_index,改用stratified_sample.index显式保存 - 更隐蔽的坑:
df.groupby(...).apply(...)在某些 pandas 版本中,若某组为空或sample()返回空 DataFrame,整个结果可能变成None或报错;建议加min_count=1或提前过滤掉空组
分层抽样的核心不在“怎么调函数”,而在“哪一列定义层”“每层要多少”“层间是否允许样本量差异”——这些业务逻辑决定了代码里是写 n=10 还是 frac=0.1,也决定了你得先检查分布再动手写 groupby。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











