☰
pandas sample()随机抽样:核心参数、加权抽样与常见问题详解
2026/10/15 12:01:09 网站建设 项目流程

1. 为什么随机抽样在数据处理中如此重要

做过数据分析的人都有一个共识:真实世界的数据集往往大得让人头疼。几十万行、上百万行的表格,你不可能一行一行去看,更不可能靠肉眼去判断数据分布是否合理。这时候,随机抽样就成了我们手里最实用的一把刀。

pandas里的sample()方法,就是专门干这件事的。它能从 DataFrame 或 Series 中随机抽取指定数量的行或列,帮你快速拿到一个具有代表性的子集。不管你是要做数据探索、模型训练前的数据划分、还是单纯想从海量数据里抽几条出来看看长什么样,sample()都能派上用场。

这篇文章适合谁看?如果你已经会用pandas读写 CSV、做过基本的loc和iloc切片操作,但对sample()的理解还停留在“知道有这么个东西”的阶段,那这篇内容就是为你准备的。我会从参数含义、底层逻辑、实操代码到踩坑经验,把sample()这个看似简单的方法彻底讲透。很多人觉得随机抽样不就是随便抽几条吗,但实际操作中你会发现,随机种子怎么设、权重怎么给、抽完要不要保留索引,每一个细节都可能影响你后续的分析结果。

我见过不少新手在用sample()的时候,抽出来的数据和原始数据对不上号,或者每次运行结果都不一样导致实验无法复现。这些问题其实都有解,关键在于你是否真正理解了每个参数背后的设计意图。接下来的内容,我会按照“设计思路—核心参数—实操演示—问题排查”的顺序,把sample()的方方面面拆开来讲,让你看完就能直接上手用。

2. sample方法的核心参数与设计逻辑拆解

2.1 为什么pandas要单独设计一个sample方法

你可能会想,随机抽样这件事,用numpy的random.choice配合iloc不也能实现吗?为什么pandas还要专门搞一个sample()?

这个问题问得好。早期确实很多人是这么干的,先算出随机索引,再用iloc去取。但这种做法有几个明显的痛点:第一,代码写起来啰嗦,每次都要手动生成索引数组;第二,处理带权重抽样的时候非常麻烦,numpy的random.choice虽然支持p参数,但和 DataFrame 的索引体系配合起来容易出错;第三,抽完之后的索引重置、去重、排序这些后续操作都得自己处理。

pandas的sample()把这些逻辑全部封装好了。它直接作用在 DataFrame 或 Series 对象上,返回的还是一个 DataFrame 或 Series,索引体系保持完整,链式调用非常顺畅。而且它同时支持按行抽和按列抽,支持有放回和无放回,支持按数量抽和按比例抽,支持加权抽样,基本上你能想到的抽样需求它都覆盖了。

从设计哲学上讲,sample()体现的是pandas一贯的“让常见操作变得简洁”的思路。你不需要关心底层是怎么生成随机数的,只需要告诉它“我要抽多少”或者“我要抽百分之几”,剩下的它帮你搞定。

2.2 核心参数逐个拆解

sample()的参数不算多,但每一个都有讲究。我把最常用的几个参数整理成了一张表,方便你对照理解:

参数名类型默认值作用说明
nintNone抽取的行数或列数,与frac二选一
fracfloatNone抽取的比例,比如 0.1 表示抽 10%
replaceboolFalse是否允许有放回抽样
weightsstr or ndarrayNone样本权重,影响被抽中的概率
random_stateint or RandomStateNone随机种子,保证结果可复现
axisint or str00 表示抽行,1 表示抽列
ignore_indexboolFalse是否重置结果的行索引

这里有几个点需要特别注意。n和frac不能同时指定,否则会报错。replace默认是False,也就是说默认是无放回抽样,这意味着你抽出来的样本不会有重复行。如果你设置的n大于总行数,又没开replace=True,那就会直接抛异常。

weights这个参数很多人忽略,但它在实际业务中非常有用。比如你有一份用户数据,想让高价值用户被抽中的概率更大,就可以把用户价值评分作为权重传进去。权重的长度必须和被抽轴的长度一致,而且不能有负值。

random_state是我个人认为最重要的参数之一。不设它的话,每次运行代码抽出来的样本都不一样,这在调试阶段很让人抓狂,在需要复现的实验里更是致命问题。设一个固定的整数,比如random_state=42,就能保证每次抽出来的结果完全一致。

2.3 有放回与无放回的本质区别

无放回抽样,英文叫 sampling without replacement,意思是同一条数据不会被重复抽到。这就像从一副扑克里抽牌,抽出来的牌就放在一边了,不会放回去。这是sample()的默认行为。

有放回抽样,sampling with replacement,则是每抽一条就放回去,下一次还有可能抽到同一条。这就像掷骰子,每次掷都是独立事件。在机器学习里,Bootstrap 自助采样法就是典型的有放回抽样,用来估计统计量的分布。

什么时候该用有放回?当你需要抽的样本量接近甚至超过总数据量时,无放回就做不到了,必须开replace=True。另外在做某些统计模拟的时候,有放回也是必须的。但如果你只是想做数据探索、拿一个子集出来看看,那默认的无放回就够了。

注意:开启replace=True后,抽出来的样本很可能出现重复行,后续如果要做去重统计,记得先处理这个问题。

3. 随机抽样的实操演示与代码详解

3.1 环境准备与示例数据构造

在开始写代码之前,先把环境搭好。你需要的是pandas和numpy,如果还没装,一条命令搞定:

pip install pandas numpy

接下来我构造一个模拟数据集,方便演示各种抽样场景。假设这是一份某电商平台的订单记录,包含订单编号、用户ID、订单金额、商品类别和下单城市五个字段:

import pandas as pd import numpy as np np.random.seed(0) n_rows = 1000 df = pd.DataFrame({ 'order_id': range(10001, 10001 + n_rows), 'user_id': np.random.randint(1000, 9999, n_rows), 'amount': np.round(np.random.uniform(10, 5000, n_rows), 2), 'category': np.random.choice(['电子产品', '服饰', '食品', '家居', '图书'], n_rows), 'city': np.random.choice(['北京', '上海', '广州', '深圳', '杭州', '成都'], n_rows) }) print(df.shape) print(df.head())

这段代码生成了一个 1000 行 5 列的 DataFrame。np.random.seed(0)是为了让数据本身可复现,和后面sample()的random_state是两回事,别搞混了。

3.2 按行抽取指定数量的样本

最基础的用法就是抽 n 行。比如我想从这 1000 条订单里随机抽 10 条出来看看:

sample_10 = df.sample(n=10, random_state=42) print(sample_10)

random_state=42保证了每次运行这段代码,抽出来的都是同样的 10 条记录。你可以把 42 换成任意整数,不同的种子会得到不同的样本。

如果你不关心具体抽多少条,而是想抽一个比例,那就用frac:

sample_frac = df.sample(frac=0.05, random_state=42) print(sample_frac.shape)

frac=0.05表示抽 5%,1000 行的 5% 就是 50 行。注意frac可以是小数,也可以是大于 1 的数(配合replace=True使用),但一般不会这么干。

3.3 按列方向抽取样本

sample()默认的axis=0是抽行,把axis设成 1 就变成抽列了。比如我想从 5 个字段里随机抽 3 个字段出来:

sample_cols = df.sample(n=3, axis=1, random_state=42) print(sample_cols.head())

抽列在实际工作中用得相对少一些,但有一种场景很常见:当你做特征筛选的随机实验时,可能需要随机选几列特征来训练模型,这时候axis=1就派上用场了。

提示:抽列的时候,weights参数同样适用,可以给不同列设置不同的被抽中概率。

3.4 有放回抽样的实操

前面说了,默认是无放回。如果你要抽的样本量超过了总行数,就必须开replace=True:

sample_replace = df.sample(n=1500, replace=True, random_state=42) print(sample_replace.shape) print(sample_replace['order_id'].duplicated().sum())

1000 行的数据抽 1500 行,必然有重复。我特意打印了重复的order_id数量,你会发现确实有不少重复行。这就是有放回抽样的特征。

有放回抽样在构建集成学习模型的训练集时特别常用。比如随机森林的每棵决策树,训练数据就是通过有放回抽样从原始数据集中得到的。理解这一点,对你后续做模型调优会有帮助。

3.5 加权抽样的实现与参数计算

加权抽样是sample()里比较高级的用法。假设我想让订单金额高的记录更容易被抽中,可以这样写:

weights = df['amount'] / df['amount'].sum() weighted_sample = df.sample(n=10, weights=weights, random_state=42) print(weighted_sample[['order_id', 'amount']])

这里我把amount归一化成了概率分布,每条记录被抽中的概率和它的金额成正比。金额越大的订单,被抽中的可能性越高。

权重的计算方式取决于你的业务需求。除了直接用数值本身,你也可以用数值的平方、对数变换等。比如你想让高金额订单的优势更明显,可以用amount ** 2作为权重;如果你想削弱极端值的影响,可以用np.log1p(amount)。

注意:权重数组的长度必须和 DataFrame 的行数完全一致,而且不能包含 NaN 或负值,否则会报错。如果权重之和不为 1,pandas会自动帮你归一化,但为了逻辑清晰,建议手动归一化。

3.6 忽略索引与重置索引的区别

抽样之后,结果的索引是原始数据的索引,不是从 0 开始的连续整数。比如你抽了第 3、17、58 行,结果的索引就是 3、17、58。这在某些场景下会带来麻烦,比如你想用iloc按位置访问的时候。

两种处理方式:

# 方式一:抽样时直接忽略索引 sample_ignore = df.sample(n=5, random_state=42, ignore_index=True) print(sample_ignore.index) # 方式二:抽样后手动重置 sample_reset = df.sample(n=5, random_state=42).reset_index(drop=True) print(sample_reset.index)

两种方式的效果一样,但ignore_index=True更简洁,推荐优先使用。reset_index(drop=True)的好处是你可以选择保留旧索引作为一个新列(把drop设为False),这在需要追溯原始位置的时候很有用。

4. 常见问题与排查技巧实录

4.1 抽样结果每次都不一样怎么办

这是新手最常遇到的问题。原因很简单:没有设置random_state。不设种子的话,pandas每次会从系统时间或其他熵源获取随机数,结果自然不同。

解决方法就是在每次调用sample()时都加上random_state参数。如果你在做一个完整的实验流程,建议在脚本开头定义一个全局的种子变量,所有涉及随机的地方都用它:

SEED = 42 train_sample = df.sample(frac=0.8, random_state=SEED) test_sample = df.drop(train_sample.index)

这样不仅抽样可复现,整个实验流程都能保持一致。

4.2 抽出来的样本量不对是怎么回事

有时候你设了frac=0.1,期望抽 100 行,结果只抽到 99 行或者 101 行。这是因为frac乘以总行数之后如果不是整数,pandas会做四舍五入。1000 的 0.1 是 100,没问题;但如果是 997 行的 0.1,就是 99.7,pandas会取整到 100 或 99,具体取决于版本和舍入规则。

如果你需要精确控制样本量,直接用n参数,别用frac。或者先算好n = int(len(df) * 0.1),再传给n。

4.3 加权抽样报错“Weights must be non-negative”怎么处理

这个报错说明你的权重数组里有负数。检查一下权重列是否有负值,如果有,需要先做处理。常见的做法是把负值截断为 0,或者做一个平移让所有值都变成正数:

weights = df['amount'].copy() weights[weights < 0] = 0 weights = weights / weights.sum()

另外,如果权重列有 NaN,也会导致报错。记得先用fillna(0)处理缺失值。

4.4 大数据集抽样性能优化建议

当 DataFrame 有上百万行的时候,sample()的速度可能会变慢。几个优化思路:

第一,如果只是做数据探索,没必要对全量数据抽样,可以先head(100000)取前十万行再抽。第二,如果要做多次抽样,考虑先把 DataFrame 转成numpy数组,用numpy的随机函数生成索引,再批量取数,这样比反复调用sample()快。第三,weights参数在大数据集上开销较大,如果权重分布比较均匀,可以考虑不用权重,直接等概率抽样。

4.5 常见问题速查表

问题现象可能原因解决方法
每次抽样结果不同未设置random_state添加random_state=固定整数
报错“Cannot take a larger sample than population”n大于总行数且未开replace设replace=True或减小n
报错“Weights must be non-negative”权重含负值或 NaN清洗权重列,确保非负且无缺失
抽样后索引不连续默认保留原始索引设ignore_index=True或reset_index(drop=True)
frac抽样数量与预期不符浮点数舍入改用n参数精确控制
加权抽样结果偏向某类数据权重设置不合理检查权重分布,考虑对数变换或截断

4.6 几个容易踩的坑

第一个坑:在循环里反复调用sample()但不设种子。如果你在做一个多次抽样的模拟实验,每次循环都调用sample()且不设random_state,那每次抽出来的样本都不一样,实验结果就没法复现。正确做法是在循环外设一个种子,或者每次循环用不同的固定种子。

第二个坑:对抽样后的数据做iloc位置索引。因为抽样后的索引是原始索引,不是从 0 开始的连续整数,你用iloc[0]取的是抽样结果的第一行,这没问题;但如果你用loc[0],那取的是原始数据中索引为 0 的那一行,如果它没被抽中,就会报 KeyError。搞清楚loc和iloc的区别,能帮你省下不少调试时间。

第三个坑:加权抽样时权重长度不匹配。比如你从 DataFrame 中筛出了一部分数据,然后想用原始的全量权重去抽样,这就会报长度不匹配的错误。权重数组的长度必须和当前 DataFrame 的行数一致,筛选后要重新计算权重。

第四个坑:忽略了sample()返回的是新对象。sample()不会修改原始 DataFrame,它返回的是一个全新的 DataFrame。如果你想保留抽样结果,记得赋值给一个新变量。我见过有人调用完sample()之后直接打印原始 DataFrame,然后纳闷为什么数据没变。

4.7 一个完整的实战案例

最后我把前面讲的内容串起来,做一个完整的演示:从 1000 条订单中,按金额加权抽取 20% 的样本,要求有放回,结果索引重置,并验证抽样后的金额分布是否合理。

import pandas as pd import numpy as np np.random.seed(0) n_rows = 1000 df = pd.DataFrame({ 'order_id': range(10001, 10001 + n_rows), 'amount': np.round(np.random.uniform(10, 5000, n_rows), 2), 'category': np.random.choice(['电子产品', '服饰', '食品', '家居', '图书'], n_rows), }) weights = df['amount'] / df['amount'].sum() sampled = df.sample(frac=0.2, replace=True, weights=weights, random_state=42, ignore_index=True) print("原始数据金额均值:", df['amount'].mean()) print("抽样数据金额均值:", sampled['amount'].mean()) print("抽样数据行数:", len(sampled)) print("重复订单数:", sampled['order_id'].duplicated().sum())

运行这段代码,你会发现抽样后的金额均值明显高于原始均值,这正是加权抽样起作用的体现。因为金额高的订单被抽中的概率更大,所以样本的金额分布会向右偏移。这个特性在做重要性采样或者聚焦分析高价值样本时非常有用。

反过来,如果你希望抽样后的分布和原始分布尽量一致,那就不要用权重,直接用等概率的无放回抽样。等概率抽样得到的样本,在统计意义上是对原始数据的无偏估计。

我在实际工作中用sample()最多的场景是快速验证数据质量。拿到一份新数据,先抽 100 条出来肉眼过一遍,看看有没有明显的脏数据、格式问题、异常值。这比写一堆校验规则快得多,而且往往能发现规则覆盖不到的问题。另一个高频场景是划分训练集和测试集,虽然sklearn有train_test_split,但有时候我需要更灵活的控制,比如按时间加权抽样,这时候sample()就更合适。

关于random_state的选择,我个人习惯用 42,没什么特别原因,就是习惯。你也可以用 0、1、1234 这些,只要在整个项目中保持一致就行。如果项目涉及多个随机过程,建议给每个过程分配不同的种子,避免随机性之间的相互干扰。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询