资讯动态

Pandas sample方法完全指南:随机抽样、分层抽样与机器学习数据集划分实战

发布时间:2026/10/13 19:13:39 来源:尧图企业网站定制
1. 为什么随机抽样在数据处理中比想象中更重要很多人第一次接触Pandas的sample方法是因为要做机器学习训练集划分。但实际工作中随机抽样的使用频率远超你的预期。比如你拿到一份几十万行的日志数据想快速看看数据长什么样直接head()只能看到前几行而前几行往往有排序规律根本代表不了整体。这时候sample就是你的第一把利器。再比如做A/B测试分析你需要从全量用户中随机抽取一部分做验证做数据质量抽检你要从海量记录中随机挑几百条人工核对做模型训练你要把数据集拆成训练集和测试集。这些场景背后都是同一个需求从数据集中随机获取一个具有代表性的子集。Pandas的sample方法就是专门解决这个问题的。它的核心能力包括按行抽样、按列抽样、指定抽样数量或比例、有放回或无放回抽样、设置随机种子保证可复现、按权重抽样。这些功能覆盖了从简单随机抽样到分层抽样的绝大多数日常需求。这篇文章我会从实际使用场景出发把sample的每个参数都拆开讲清楚包括那些文档里一笔带过但实际很关键的细节。无论你是刚学Pandas的新手还是已经用过sample但总觉得有些地方没搞明白的进阶用户都能从下面这些内容里找到有用的东西。2. sample方法的核心参数逐个拆解2.1 n和frac二选一的数量控制逻辑sample最基础的两个参数是n和frac分别控制抽取的绝对数量和比例。这两个参数不能同时使用否则会直接报错。import pandas as pd import numpy as np # 构造一个示例数据集 np.random.seed(42) df pd.DataFrame({ user_id: range(1, 101), score: np.random.randint(60, 100, 100), city: np.random.choice([北京, 上海, 广州, 深圳], 100) }) # 抽取5行 df.sample(n5) # 抽取20%的行 df.sample(frac0.2)这里有个容易踩的坑当frac计算出的数量不是整数时Pandas会向下取整。比如100行数据frac0.155实际抽取15行而不是16行。如果你需要精确控制数量建议直接用n参数。另一个细节是当n大于数据行数且replaceFalse默认值时会抛出ValueError。这个设计是合理的——无放回抽样不可能抽出比总体更多的样本。但如果你确实需要抽更多就得设置replaceTrue。提示frac1.0配合replaceFalse等价于对数据进行随机重排shuffle这个技巧在打乱数据顺序时非常实用比用sklearn.utils.shuffle更直接。2.2 replace有放回与无放回的实际影响replace参数默认为False表示无放回抽样——同一行不会被重复抽到。设为True时就是有放回抽样同一行可能被抽到多次。什么时候需要用有放回抽样一个典型场景是Bootstrap自助法Bootstrap Aggregating这是集成学习中常用的重采样技术。你需要从原始数据集中有放回地抽取同样大小的样本用来训练多个基模型。另一个场景是当数据量很小但你需要生成大量随机子集时有放回抽样能提供更多组合可能性。# 有放回抽样可能抽到重复行 df.sample(n10, replaceTrue) # 无放回抽样每行最多出现一次 df.sample(n10, replaceFalse)实测下来有放回抽样在数据量较大时比如几万行以上抽出的样本中出现重复行的概率其实不低。以100行抽10行为例有放回情况下至少出现一对重复的概率大约是37%。这个数字很多人直觉上会觉得偏低但这是经典的生日悖论问题。2.3 weights按权重抽样的实现细节weights参数允许你为每一行指定被抽中的相对概率。这个功能在做分层抽样或重要性采样时特别有用。# 按score列的值作为权重分数越高被抽中的概率越大 df.sample(n5, weightsscore) # 也可以用自定义的权重数组 custom_weights np.where(df[city] 北京, 3.0, 1.0) df.sample(n5, weightscustom_weights)使用weights时有几个关键点需要注意。第一权重列中不能有负数否则会报错。第二权重列中如果有NaNPandas会直接抛出异常不会自动忽略。第三权重是相对值而非绝对值你传[1, 1, 1]和传[100, 100, 100]的效果完全一样。还有一个容易被忽略的细节当replaceFalse且weights不为空时Pandas内部使用的是不放回的概率抽样算法每次抽出一行后剩余行的权重会被重新归一化。这意味着权重的影响是动态变化的而不是简单的一次性概率分配。2.4 random_state可复现性的关键random_state参数控制随机数生成器的种子。设置了这个参数后每次运行代码得到的抽样结果完全一致。这在调试和结果验证时至关重要。# 两次抽样结果完全相同 sample_a df.sample(n5, random_state42) sample_b df.sample(n5, random_state42) print(sample_a.equals(sample_b)) # 输出 True我在实际项目中的习惯是在探索性分析阶段不设random_state每次看到不同的随机样本有助于发现数据的多样性但在写正式的分析脚本或机器学习流水线时一定会固定random_state确保结果可复现。注意random_state只保证同一台机器、同一版本Pandas下的结果一致。不同Pandas版本之间即使random_state相同抽样结果也可能不同因为底层随机数生成算法可能发生了变化。2.5 axis按行抽还是按列抽axis参数默认为0表示按行抽样。设为1时按列抽样。按列抽样在实际工作中用得相对少一些但在某些场景下很有价值。# 按列抽样随机选择2列 df.sample(n2, axis1) # 按列抽样随机选择50%的列 df.sample(frac0.5, axis1)按列抽样的一个典型应用是做特征选择的随机子集实验。比如你有200个特征想测试模型在不同特征子集上的表现可以用sample(axis1)快速生成随机特征组合。另一个场景是当DataFrame列数很多、你想快速查看部分列的数据分布时按列抽样比手动指定列名更方便。不过要注意按列抽样时weights参数的行为和按行抽样不同。按列抽样时权重数组的长度必须等于列数而且权重对应的是每一列被选中的概率。3. 抽样结果的索引问题与重置技巧3.1 抽样后索引为什么是乱的sample方法返回的DataFrame会保留原始索引。这意味着如果你从100行中抽了5行结果的索引可能是[23, 7, 91, 45, 12]这样的乱序。这个设计本身没问题但在后续处理中可能带来麻烦。比如你想用iloc按位置访问抽样结果的第一行用loc按索引访问就会出错。再比如你把抽样结果保存到CSV后再读回来索引列会变成普通列容易造成混淆。sampled df.sample(n5, random_state42) print(sampled.index.tolist()) # 类似 [83, 53, 70, 45, 44] # 重置索引dropTrue表示丢弃原索引 sampled_reset sampled.reset_index(dropTrue) print(sampled_reset.index.tolist()) # [0, 1, 2, 3, 4]3.2 reset_index的drop参数该不该设reset_index(dropTrue)和reset_index()的区别在于前者直接丢弃原索引后者把原索引变成一个新列。选择哪个取决于你的需求。如果你只是想让索引连续不关心原始行号用dropTrue。如果你后续还需要追溯每行在原始数据中的位置那就保留原索引列。我在做数据抽检时通常会保留原索引因为核对完抽样数据后需要回到原始数据中定位问题行。# 保留原索引作为新列 sampled_with_original sampled.reset_index() # 新列名默认为index可以自定义 sampled_with_original sampled.reset_index().rename(columns{index: original_row})3.3 链式调用中的索引陷阱一个常见的错误模式是在抽样后直接做条件筛选然后赋值结果触发SettingWithCopyWarning。这个警告的根源是Pandas无法确定你操作的是视图还是副本。# 可能触发警告的写法 sampled df.sample(n10) sampled[sampled[score] 80][score] 100 # 警告 # 安全的写法 sampled df.sample(n10).copy() sampled.loc[sampled[score] 80, score] 100养成在sample之后加.copy()的习惯可以避免很多隐蔽的bug。虽然多了一次内存拷贝但在数据量不是特别大的情况下这点开销完全可以接受。4. 分组抽样与分层抽样的实战方案4.1 groupby配合apply实现分组抽样实际数据往往包含类别列比如用户的城市、产品的品类、实验的分组。直接对全量数据随机抽样可能会让某个小类别在样本中完全消失。这时候就需要分组抽样。# 每个城市抽取2行 grouped_sample df.groupby(city, group_keysFalse).apply( lambda x: x.sample(n2, random_state42) )这里group_keysFalse的作用是避免结果中出现多级索引。如果不设这个参数结果的索引会变成(city, original_index)的MultiIndex形式后续处理会麻烦一些。4.2 按比例分层抽样的实现更常见的需求是按比例抽样——每个组抽取相同比例的行。比如每个城市抽取30%的用户。# 每个城市抽取30% stratified_sample df.groupby(city, group_keysFalse).apply( lambda x: x.sample(frac0.3, random_state42) )但这里有个问题如果某个组只有2行数据frac0.3会抽出0行向下取整。这可能导致某些类别在样本中完全缺失。解决办法是设置一个最小抽样数量。def safe_sample(group, frac0.3, min_n1): n max(int(len(group) * frac), min_n) n min(n, len(group)) # 不能超过组内行数 return group.sample(nn, random_state42) stratified_sample df.groupby(city, group_keysFalse).apply(safe_sample)4.3 固定每组数量的抽样策略有时候你不需要按比例而是希望每个组抽取相同数量的样本。这在类别不平衡的场景下特别有用——让每个类别在样本中拥有相同的代表数量。# 每个城市固定抽5行 balanced_sample df.groupby(city, group_keysFalse).apply( lambda x: x.sample(nmin(5, len(x)), random_state42) )注意这里用了min(5, len(x))来防止某个组行数不足5行时报错。这个细节在实际数据中很关键因为真实数据的类别分布往往不均匀小类别可能只有几行数据。4.4 分层抽样的验证方法抽完样后一定要验证抽样结果是否符合预期。最直接的方法是看各类别的分布。# 原始数据的类别分布 print(df[city].value_counts(normalizeTrue)) # 抽样后的类别分布 print(stratified_sample[city].value_counts(normalizeTrue))如果按比例分层抽样正确执行两边的分布应该非常接近。如果差异较大可能是某个组的样本量太小导致的随机波动也可能是抽样逻辑有问题。5. 抽样在机器学习数据集划分中的具体应用5.1 用sample做训练测试集划分虽然sklearn有train_test_split但用Pandas的sample手动划分也有它的优势——更灵活不依赖额外库而且可以方便地处理时间序列以外的各种数据结构。# 划分训练集和测试集80/20比例 train df.sample(frac0.8, random_state42) test df.drop(train.index) # 验证两个集合没有重叠 assert len(set(train.index) set(test.index)) 0 print(f训练集: {len(train)}行, 测试集: {len(test)}行)这里用drop配合train.index来获取测试集比单独再抽一次更安全——单独抽两次无法保证两个集合互斥。drop方法会返回所有不在train.index中的行天然保证了互斥性。5.2 多折交叉验证的手动实现理解了sample的用法后手动实现K折交叉验证并不复杂。核心思路是先把数据随机打乱然后按位置切分成K份。def manual_kfold(df, k5, random_state42): shuffled df.sample(frac1.0, random_staterandom_state).reset_index(dropTrue) fold_size len(shuffled) // k folds [] for i in range(k): start i * fold_size end start fold_size if i k - 1 else len(shuffled) folds.append(shuffled.iloc[start:end]) return folds folds manual_kfold(df, k5) for i, fold in enumerate(folds): print(f第{i1}折: {len(fold)}行)这个实现虽然不如sklearn的KFold功能全面比如不支持分层但胜在透明可控适合在需要自定义划分逻辑时使用。5.3 处理类别不平衡的抽样策略在分类问题中如果某个类别的样本极少随机划分可能导致测试集中该类别的样本数为零。这时候需要用分层抽样来保证每个折中各类别的比例一致。# 按目标列分层抽样保证每个折中类别比例一致 def stratified_kfold(df, target_col, k5, random_state42): df df.sample(frac1.0, random_staterandom_state).reset_index(dropTrue) df[_fold] -1 for cls in df[target_col].unique(): cls_indices df[df[target_col] cls].index fold_assignments np.array_split(cls_indices, k) for i, indices in enumerate(fold_assignments): df.loc[indices, _fold] i return df df_with_folds stratified_kfold(df, city, k5) print(df_with_folds.groupby([_fold, city]).size().unstack())这个实现的核心逻辑是对每个类别单独做K等分然后把不同类别的同一折合并。这样每个折中各类别的比例就和原始数据一致了。6. 大数据量下的抽样性能与内存考量6.1 sample在大DataFrame上的性能表现sample方法的性能主要取决于抽样数量和数据量。对于无放回抽样Pandas内部使用的是Fisher-Yates洗牌算法的变体时间复杂度是O(n)其中n是抽样数量。这意味着抽10行和抽10000行的耗时差异是线性的。import time big_df pd.DataFrame(np.random.randn(1000000, 10)) # 抽100行 start time.time() big_df.sample(n100) print(f抽100行耗时: {time.time() - start:.4f}秒) # 抽100000行 start time.time() big_df.sample(n100000) print(f抽100000行耗时: {time.time() - start:.4f}秒)实测在百万行数据上抽100行通常在毫秒级别抽10万行也在几十毫秒内完成。这个性能对于绝大多数场景都足够了。6.2 抽样与内存占用的关系sample返回的是一个新的DataFrame会占用额外的内存。抽样比例越大内存占用越接近原始数据。如果原始数据已经占用了大量内存抽样时可能会触发内存不足。一个实用的技巧是如果只需要抽样后的数据可以在抽样后立即删除原始DataFrame并手动触发垃圾回收。import gc sampled big_df.sample(frac0.1) del big_df gc.collect()另一个技巧是先用sample获取索引然后用loc或iloc获取数据。这种方式在只需要部分列时特别有效。# 只取抽样行的部分列 sampled_indices big_df.sample(n1000).index result big_df.loc[sampled_indices, [col1, col2]]6.3 分块抽样的思路当数据量大到无法一次性加载到内存时可以考虑分块抽样。基本思路是先统计总行数然后生成随机行号最后按行号读取。# 假设数据在CSV文件中总行数已知为total_rows total_rows 1000000 sample_size 1000 random_rows sorted(np.random.choice(total_rows, sample_size, replaceFalse)) # 分块读取只保留被抽中的行 chunks [] chunk_size 10000 for i, chunk in enumerate(pd.read_csv(data.csv, chunksizechunk_size)): start_row i * chunk_size end_row start_row chunk_size rows_in_chunk [r - start_row for r in random_rows if start_row r end_row] if rows_in_chunk: chunks.append(chunk.iloc[rows_in_chunk]) result pd.concat(chunks, ignore_indexTrue)这个方案适合处理超出内存的大文件代价是需要遍历整个文件一次。如果文件格式支持随机访问比如Parquet可以直接用pyarrow按行号读取效率会高很多。7. 那些文档里不会写的踩坑经验7.1 抽样后索引重复导致的合并问题如果你对两个DataFrame分别抽样然后想用索引做合并很可能会遇到问题。因为抽样后的索引是原始索引的子集两个抽样结果的索引可能有重叠导致merge时产生笛卡尔积。# 危险操作两个抽样结果按索引合并 sample_a df.sample(n10, random_state1) sample_b df.sample(n10, random_state2) merged sample_a.merge(sample_b, left_indexTrue, right_indexTrue) # 可能产生意外结果正确的做法是在抽样后重置索引或者用明确的列作为合并键。# 安全做法重置索引后用列合并 sample_a df.sample(n10, random_state1).reset_index(dropTrue) sample_b df.sample(n10, random_state2).reset_index(dropTrue) merged sample_a.merge(sample_b, onuser_id)7.2 weights参数与NaN的隐蔽冲突前面提到过weights列不能有NaN但实际数据中NaN往往很隐蔽。比如你从CSV读取数据时某个数值列可能因为格式问题被解析成了NaN而你用这一列做权重时才会发现。# 检查权重列是否有NaN if df[weight_col].isna().any(): print(f权重列有{df[weight_col].isna().sum()}个NaN) # 填充或删除NaN行 df df.dropna(subset[weight_col])更稳妥的做法是在抽样前先做数据清洗确保权重列干净。如果确实需要保留NaN行可以给它们赋一个默认权重。7.3 按列抽样时的数据类型问题按列抽样axis1时如果DataFrame包含不同类型的列比如数值列和字符串列抽样结果是这些列的混合。后续如果要对抽样结果做数值运算需要先筛选出数值列。# 按列抽样后只保留数值列 col_sample df.sample(n3, axis1) numeric_cols col_sample.select_dtypes(include[np.number])另一个坑是按列抽样时weights参数的长度必须等于列数而不是行数。这个细节在文档中很容易被忽略但实际使用时如果搞错了会直接报错。7.4 随机种子在不同Pandas版本间的差异前面提过random_state在不同Pandas版本间可能产生不同结果。这个问题在升级Pandas版本时尤其需要注意。如果你有一个依赖固定随机种子的生产流水线升级Pandas后一定要重新验证抽样结果。一个实用的做法是把抽样结果的索引或关键列的值保存下来作为回归测试的基准。升级后对比新旧结果确认差异是否在可接受范围内。# 保存抽样基准 baseline df.sample(n100, random_state42) baseline.to_csv(sample_baseline.csv, indexFalse) # 升级Pandas后重新抽样并对比 new_sample df.sample(n100, random_state42) old_sample pd.read_csv(sample_baseline.csv) print(f结果是否一致: {new_sample.reset_index(dropTrue).equals(old_sample)})7.5 抽样在时间序列数据上的特殊处理对时间序列数据做随机抽样需要特别小心。随机抽样会打乱时间顺序如果后续分析依赖时间先后关系比如计算滑动平均、做时序预测随机抽样后的数据就不能直接使用。时间序列的抽样通常有两种正确做法一是按时间点抽样比如每隔10个时间点取一个二是按时间窗口抽样比如随机选择几个连续的时间段。这两种方式都能保持时间顺序的完整性。# 按时间窗口抽样随机选择3个连续的时间段 window_size 24 # 每个窗口24个时间点 n_windows 3 total_windows len(df) // window_size start_windows np.random.choice(total_windows, n_windows, replaceFalse) windows [] for start in start_windows: windows.append(df.iloc[start*window_size : (start1)*window_size]) time_sample pd.concat(windows)这种抽样方式在时序异常检测和时序模型验证中很常用能保证每个抽样窗口内的数据保持原有的时间依赖结构。8. 几个可以直接复用的抽样工具函数8.1 通用安全抽样函数把前面提到的各种边界情况都考虑进去封装一个安全抽样函数日常使用直接调用即可。def safe_sample(df, nNone, fracNone, group_colNone, random_state42, min_per_group1): 安全抽样函数支持分组抽样和边界处理 参数: df: 原始DataFrame n: 抽样数量与frac二选一 frac: 抽样比例与n二选一 group_col: 分组列名None表示不分组 random_state: 随机种子 min_per_group: 每组最少抽样数量 if group_col is None: if n is not None: n min(n, len(df)) return df.sample(nn, random_staterandom_state).reset_index(dropTrue) else: return df.sample(fracfrac, random_staterandom_state).reset_index(dropTrue) else: def _sample_group(group): if n is not None: actual_n min(n, len(group)) else: actual_n max(int(len(group) * frac), min_per_group) actual_n min(actual_n, len(group)) return group.sample(nactual_n, random_staterandom_state) return df.groupby(group_col, group_keysFalse).apply(_sample_group).reset_index(dropTrue)这个函数处理了几个常见问题抽样数量不超过总行数、分组抽样时每组至少抽到min_per_group行、结果索引重置。8.2 分层抽样比例验证函数抽完样后快速验证分层比例是否符合预期。def check_stratification(original_df, sampled_df, strat_col): 对比原始数据和抽样数据的分层比例 original_dist original_df[strat_col].value_counts(normalizeTrue).sort_index() sampled_dist sampled_df[strat_col].value_counts(normalizeTrue).sort_index() comparison pd.DataFrame({ 原始比例: original_dist, 抽样比例: sampled_dist, 差异: (sampled_dist - original_dist).abs() }) comparison[差异百分比] (comparison[差异] * 100).round(2).astype(str) % return comparison.sort_values(差异, ascendingFalse)这个函数输出的表格能直观地看到每个类别的比例偏差偏差最大的类别排在最前面方便快速定位问题。8.3 可复现抽样流水线在机器学习项目中把抽样步骤固化成一个可复现的流水线确保每次运行结果一致。class ReproducibleSampler: def __init__(self, random_state42): self.random_state random_state self.sample_indices {} def sample(self, df, name, nNone, fracNone): 抽样并记录索引支持后续复现 if n is not None: result df.sample(nn, random_stateself.random_state) else: result df.sample(fracfrac, random_stateself.random_state) self.sample_indices[name] result.index.tolist() return result def get_indices(self, name): 获取之前抽样的索引 return self.sample_indices.get(name, []) def replay(self, df, name): 用记录的索引复现抽样结果 indices self.get_indices(name) return df.loc[indices]这个类的价值在于抽样时自动记录索引后续可以用replay方法在任何时候复现完全相同的抽样结果不依赖随机种子。这在需要把抽样结果分享给同事或保存到实验记录中时特别有用。9. 抽样结果的持久化与分享9.1 保存抽样结果的最佳实践抽样结果通常需要保存下来供后续使用或分享。保存时建议同时保存抽样索引和抽样参数方便追溯。# 保存抽样结果 sampled df.sample(n1000, random_state42) sampled.to_csv(sampled_data.csv, indexFalse) # 保存抽样元信息 import json meta { original_rows: len(df), sampled_rows: len(sampled), random_state: 42, method: sample, n: 1000 } with open(sample_meta.json, w) as f: json.dump(meta, f, ensure_asciiFalse, indent2)保存元信息的好处是当别人拿到你的抽样数据时能清楚地知道抽样是怎么做的便于验证和复现。9.2 抽样结果的可视化验证对于数值型数据可以用简单的直方图对比原始数据和抽样数据的分布。如果抽样是随机的两个分布应该高度相似。import matplotlib.pyplot as plt fig, axes plt.subplots(1, 2, figsize(12, 4)) df[score].hist(axaxes[0], bins20, alpha0.7) axes[0].set_title(原始数据分布) sampled[score].hist(axaxes[1], bins20, alpha0.7, colororange) axes[1].set_title(抽样数据分布) plt.tight_layout() plt.show()如果两个分布差异明显说明抽样可能存在问题——比如数据本身有排序规律而抽样没有打乱顺序。这种情况下需要检查数据是否在抽样前已经随机打乱。9.3 抽样日志的记录规范在生产环境中每次抽样都应该记录日志。日志内容至少包括抽样时间、数据源、抽样方法、抽样参数、抽样数量、操作人。import logging from datetime import datetime logging.basicConfig(levellogging.INFO, format%(asctime)s - %(message)s) def logged_sample(df, n, random_state42, sourceunknown): result df.sample(nn, random_staterandom_state) logging.info( f抽样完成 | 数据源: {source} | 原始行数: {len(df)} | f抽样行数: {len(result)} | 随机种子: {random_state} ) return result这些日志在排查问题时非常有用。比如发现某次分析的抽样结果异常可以通过日志快速定位到当时的抽样参数然后复现问题。10. 从抽样到全量什么时候不该用sample10.1 数据量本身就不大时如果数据集只有几百行随机抽样的意义不大。这时候直接分析全量数据更合适因为抽样引入的随机性反而可能掩盖真实的模式。一般来说数据量在1000行以下时除非有明确的抽样需求比如模拟小样本场景否则建议直接用全量数据。10.2 需要精确统计量时抽样会引入抽样误差。如果你需要计算精确的统计量比如总销售额、精确的用户留存率抽样结果只能给出估计值。虽然可以通过增大样本量来减小误差但在数据量可接受的情况下直接用全量数据计算更可靠。10.3 数据有强时间依赖时前面提到过时间序列的问题。除了时间序列还有一些数据有空间依赖比如地理数据、网络依赖比如社交网络数据。对这些数据做随机抽样会破坏原有的依赖结构导致分析结果失真。这类数据的抽样需要采用专门的策略比如空间分层抽样、网络抽样等。10.4 需要保证稀有类别覆盖时如果数据中某些类别非常稀有比如占比不到0.1%随机抽样很可能完全漏掉这些类别。这时候需要用分层抽样或者过采样技术来保证稀有类别的覆盖。单纯用sample的随机抽样无法解决这个问题。我在实际工作中遇到过一个案例一份用户行为数据中某个异常行为的占比只有0.05%。用sample(frac0.1)抽样后抽样数据中完全没有这个异常行为的记录。后来改用分层抽样强制保证每个类别至少抽到一定数量才解决了这个问题。11. 一些值得记住的抽样参数组合11.1 快速查看数据概况# 随机看20行了解数据大致情况 df.sample(n20, random_state42)比head()和tail()更能反映数据的真实分布因为head()和tail()可能受数据排序影响。11.2 随机打乱数据顺序# 完全打乱数据顺序 df_shuffled df.sample(frac1.0, random_state42).reset_index(dropTrue)这是最简洁的打乱数据的方法比用np.random.permutation再索引更直观。11.3 按权重抽样模拟真实分布# 让高价值用户有更高概率被抽中 df.sample(n100, weightsdf[user_value], random_state42)适合在需要模拟有偏抽样或做重要性采样时使用。11.4 有放回抽样做Bootstrap# Bootstrap重采样 bootstrap_sample df.sample(nlen(df), replaceTrue, random_state42)这是集成学习中Bagging方法的基础操作每次用不同的随机种子生成不同的Bootstrap样本。11.5 分组抽样保证类别覆盖# 每个类别抽5行保证小类别不被遗漏 df.groupby(category, group_keysFalse).apply( lambda x: x.sample(nmin(5, len(x)), random_state42) )这个组合在处理类别不平衡数据时特别实用能确保每个类别在抽样结果中都有代表。12. 抽样方法的扩展思路12.1 系统抽样与分层抽样的结合系统抽样是按固定间隔抽取样本比如每隔100行抽1行。这种方式实现简单但在数据有周期性规律时可能产生偏差。把系统抽样和分层抽样结合可以在保持简单性的同时减少偏差。# 分层系统抽样每个类别内按固定间隔抽样 def stratified_systematic_sample(df, strat_col, interval10): result [] for cls in df[strat_col].unique(): cls_df df[df[strat_col] cls] sampled cls_df.iloc[::interval] result.append(sampled) return pd.concat(result).reset_index(dropTrue)12.2 聚类抽样当数据天然分群时比如按地区、按门店可以先随机抽取几个群然后对抽中的群做全量分析。这种方式在调查研究中很常见能大幅降低数据收集成本。# 先随机选3个城市然后取这3个城市的全部数据 cities df[city].unique() selected_cities np.random.choice(cities, 3, replaceFalse) cluster_sample df[df[city].isin(selected_cities)]12.3 渐进式抽样在探索性分析中可以采用渐进式抽样先抽少量数据快速了解概况如果发现有趣的方向再扩大样本量。这种方式能节省大量时间。# 第一轮抽100行快速查看 quick_look df.sample(n100, random_state42) # 如果发现需要深入分析第二轮抽1000行 deeper_look df.sample(n1000, random_state42)渐进式抽样的关键是保持随机种子一致这样第二轮抽样会包含第一轮的结果当n增大时避免重复劳动。13. 抽样结果的统计检验13.1 用KS检验验证分布一致性Kolmogorov-Smirnov检验可以判断两个样本是否来自同一分布。抽样后可以用KS检验验证抽样数据与原始数据的分布是否一致。from scipy import stats # 对score列做KS检验 stat, p_value stats.ks_2samp(df[score], sampled[score]) print(fKS统计量: {stat:.4f}, p值: {p_value:.4f}) if p_value 0.05: print(抽样数据与原始数据分布无显著差异) else: print(抽样数据与原始数据分布存在显著差异需要检查抽样方法)13.2 用卡方检验验证类别分布对于类别型数据可以用卡方检验验证抽样后的类别分布是否与原始分布一致。from scipy.stats import chi2_contingency # 构建列联表 original_counts df[city].value_counts() sampled_counts sampled[city].value_counts() contingency pd.DataFrame({original: original_counts, sampled: sampled_counts}) chi2, p_value, dof, expected chi2_contingency(contingency) print(f卡方统计量: {chi2:.4f}, p值: {p_value:.4f})这些统计检验在需要严格验证抽样质量时很有价值尤其是当抽样结果将用于重要决策时。14. 写在最后的一些个人体会sample方法看起来简单但真正用好需要理解它的每个参数背后的逻辑以及在实际数据上可能遇到的各种边界情况。我见过太多人因为忽略了索引重置、权重NaN、分组抽样边界这些问题导致分析结果出现偏差。几个我反复强调的习惯抽样后立即reset_index(dropTrue)除非你有明确理由保留原索引分组抽样时永远用min(n, len(group))保护设置random_state保证可复现抽样后做分布验证。这些习惯看起来琐碎但能帮你避免绝大多数抽样相关的坑。还有一个体会是抽样不是万能的。当数据量本身不大、或者需要精确统计量、或者数据有强依赖结构时直接分析全量数据往往是更好的选择。抽样是一种权衡——用可控的精度损失换取计算效率的提升。理解这个权衡的本质才能在实际工作中做出正确的选择。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑