抽样是数据分析的地基:随机抽样原理与4种实战方法

发布时间:2026/7/12 14:17:54
抽样是数据分析的地基:随机抽样原理与4种实战方法 1. 什么是抽样它为什么是数据分析的“地基”而不是“可选项”你手头有一堆数据想搞清楚学生早餐吃不吃得上热饭会不会影响期末考试成绩想验证新上线的APP按钮颜色改了之后用户点击率是不是真提高了甚至只是想估算一下公司楼下那家奶茶店每天到底卖多少杯珍珠奶茶——这些事儿听起来都挺具体但背后藏着一个最根本、最容易被忽略的问题你手里的数据到底能不能代表你想研究的那个“全体”这就是抽样要解决的核心问题。它不是统计学课本里一个干巴巴的定义而是你所有分析结论能否站得住脚的第一道门槛。我带过不少刚入行的数据分析师他们能写出漂亮的回归模型、调得动复杂的神经网络但一问“你这个样本是怎么选出来的”十有八九会卡壳。结果呢模型跑出来R²高达0.95结论却荒谬得让人哭笑不得——因为那个“0.95”是建立在一座沙堡上的。抽样就是给你这座分析大厦打地基的过程。地基歪了上面盖得再漂亮风一吹就倒。它重要到什么程度这么说吧在经典线性回归OLS的六大核心假设里“随机抽样”排在第二位紧挨着“模型是线性的”这个最基础的前提。为什么因为只有当你手里的样本是随机从总体里抓出来的你才能用这几十个、几百个观测值去“猜”出成千上万、甚至上亿人的行为规律。这不是玄学这是数学上保证你估计值“无偏”的硬性条件。如果你跳过这一步或者随便从Excel里拉一列数据就开始建模那你后续所有的技术操作无论多炫酷本质上都是在给一个错误的前提做精美的注解。这篇文章我就用一个老手的视角把抽样这件事掰开揉碎不讲虚的只讲你明天上班、后天跑实验、下周写报告时真正用得上的东西。它适合所有需要和数据打交道的人刚毕业的实习生、想转行的数据新人、业务部门天天要看报表的同事甚至是已经能独立建模的工程师。只要你希望自己的结论能被老板信、被客户认、被自己心安理得地写进PPT里那就得先把这个“地基”夯得结结实实。2. 抽样设计与思路拆解为什么“随机”二字重如千钧2.1 从“苹果对苹果”到“大海捞针”抽样的底层逻辑我们先回到那个最朴素的疑问为什么不能把所有人、所有数据都拿过来分析答案很简单成本、时间、可行性。想象一下你要评估全国高中生的数学平均分。理论上最完美的方案是把全国所有高中生的期末试卷都收上来一张张批改然后算出一个精确到小数点后六位的均值。这叫“普查”。但现实是光是试卷的印刷、分发、回收、扫描、阅卷、录入可能就要耗掉一个省级教育局三年的预算和全部人力。更别说等你算出结果这批学生可能都大学毕业了。所以我们必须妥协必须“以小见大”。但这个“小”绝不是随便挑几个。它必须是一个“缩微版”的总体一个能在统计意义上“复刻”总体特征的“镜像”。这就是抽样的核心目标用最小的代价获取一个能最大程度代表总体的子集。这个目标直接决定了我们整个分析的天花板。你抽样方法选错了后面所有高深的算法都只是在加速通往错误答案的路程。我见过最典型的反面案例是一家电商公司想优化首页推荐。他们的数据分析师直接从当天的订单数据库里随机抽取了10000条记录。看起来很“随机”对吧但问题在于这个数据库本身只包含“已经下单”的用户。那些打开APP看了两眼就退出、或者加购后没付款的沉默用户完全被排除在外了。这个样本本质上只代表了“购买力强、决策快”的那一小撮人而忽略了占比更大、行为更复杂的“犹豫型”用户。用这个样本训练出来的推荐模型上线后发现首页推荐的商品越来越窄越来越“讨好”老用户新用户的跳出率反而飙升了。这就是典型的“样本偏差”Sampling Bias它比模型过拟合可怕得多因为它从根上就错了。2.2 “随机抽样”不是“随便抽样”四种主流方法的实战抉择很多人以为“随机抽样”就是在Excel里按F9刷新一下或者用Python的random.sample()函数随便点几下。这其实是个巨大的误解。“随机”在这里是一个严格的统计学概念指的是总体中的每一个个体被选入样本的概率是已知且相等的。这个定义直接引出了几种最常用、也最值得你掌握的抽样方法。选择哪一种不是看哪个代码写起来最短而是要看你的数据结构、你的研究目标以及你手头能拿到的“地图”。第一种简单随机抽样Simple Random Sampling, SRS这是教科书里的“标准答案”也是所有其他方法的基础。它的操作就像从一个装满编号乒乓球的箱子里闭着眼睛一次抓出N个球。在现实中就是给总体里的每个个体编一个唯一的ID号然后用随机数生成器比如R的sample()或Python的numpy.random.choice()从中无放回地抽取。它的最大优点是理论最干净推断最直接。所有经典的统计公式比如均值的标准误、置信区间的计算都是基于SRS推导出来的。但它的致命弱点是**“不接地气”**。如果总体规模巨大比如全国14亿人口你得先给每个人编一个号这本身就是一项浩大的工程。而且它完全不考虑总体内部的结构。比如你用SRS从全国抽1000人理论上有可能抽到的1000人全来自同一个省、甚至同一个村。虽然概率极小但这种“极端不均衡”会让你的样本代表性大打折扣。所以SRS最适合的场景是总体规模不大10万、内部结构相对均匀、且你能轻松获得完整名单。比如一家有800名员工的科技公司想调研大家对公司新福利政策的看法SRS就是最简单、最可靠的选择。第二种分层抽样Stratified Sampling这是SRS的“升级版”也是我在实际项目中用得最多的方法。它的核心思想是先“分门别类”再“门内随机”。你根据一个或多个关键特征称为“分层变量”把总体划分为若干个互不重叠、穷尽全部的“层”Stratum。比如研究全国居民消费习惯你可以按“省份”分层研究某款游戏的用户留存你可以按“注册渠道”App Store、华为应用市场、官网下载分层研究学生GPA你可以按“年级”和“专业”交叉分层。分完层之后再在每一层内部独立地进行简单随机抽样。分层抽样的威力在于它能强制保证样本在关键维度上的结构与总体完全一致。还是那个全国1000人的例子如果你按31个省份分层并按各省人口比例分配样本量比如广东占全国人口11%就抽110人那么最终的样本就天然地反映了全国的地域分布。这极大地降低了因偶然性导致的样本失衡风险。更重要的是它能显著提高估计精度。因为层内的个体通常比层间更相似层内的变异小了你用同样大小的样本就能得到更精确的总体估计。当然它的代价是需要你事先掌握总体的分层信息也就是那张“地图”。如果你连全国各省的人口数都不知道那分层就无从谈起。第三种整群抽样Cluster Sampling当“地图”不仅难画而且画出来也贵得离谱时整群抽样就派上用场了。它的思路是把总体划分成若干个“群”Cluster然后随机抽取几个群对抽中的群里的所有个体进行调查。这里的“群”通常是地理上或行政上天然存在的单位比如一个村庄、一所学校、一个街区、一个工厂车间。整群抽样的最大优势是成本极低、执行极快。你不需要知道村里每户人家的门牌号只需要知道村里有100户然后把这100户全访一遍比你为了SRS而跑遍全县100个分散的村子要省下90%的交通和时间成本。但它也有明显的短板精度通常低于SRS和分层抽样。因为同一个群里的个体往往具有高度的相似性比如同村的人收入水平、生活习惯相近这导致群内变异小而群间变异大。你的样本多样性就取决于你抽到了哪几个“群”。为了弥补这个缺陷实践中常采用“两阶段抽样”第一阶段随机抽群第二阶段在抽中的群里再进行一次SRS。这就像先随机选中几栋楼再在每栋楼里随机敲几户的门。第四种系统抽样Systematic Sampling这是一种操作上最“傻瓜式”的方法特别适合处理大型、有序的数据库。它的步骤是1确定样本量n2计算抽样间隔k N/nN为总体大小3在1到k之间随机选一个起始点r4然后依次选取第r, rk, r2k, r3k...个个体。比如你有一个100万行的用户日志表想抽10000行那么k100你随机选一个1-100之间的数比如选了47那你就取第47、147、247...直到第999947行。系统抽样的好处是简单、高效、不易出错。它避免了SRS中可能出现的“重复抽样”虽然概率小或“遗漏抽样”的麻烦。但它的风险在于如果总体数据本身存在某种周期性或趋势系统抽样会完美地“捕获”并放大这种模式从而引入严重偏差。比如你的日志表是按时间顺序排列的而用户行为在一天24小时内有明显的潮汐规律早高峰、午休、晚高峰。如果你的抽样间隔k恰好是24的倍数那你抽出来的就全是“早高峰”的用户或者全是“午休”的用户样本就彻底失真了。所以系统抽样前务必先对数据的排序逻辑和潜在模式做一个快速诊断。2.3 OLS回归中的“随机抽样”假设它到底在保护什么回到原文提到的OLS六大假设为什么“随机抽样”Assumption 2如此关键这里需要一点深入的解释因为它直接关系到你每天都在跑的回归结果是否可信。OLS的目标是找到一条直线让这条直线到所有数据点的垂直距离即残差的平方和最小。这个“最小二乘”的解有一个非常优美的数学性质在满足所有假设的前提下它是最优线性无偏估计量BLUE。其中“无偏”Unbiased意味着如果你能无限次地从同一个总体里用同样的方法抽取样本然后每次都计算出一个斜率β̂那么所有这些β̂的平均值会无限接近于真实的总体斜率β。换句话说你的估计没有系统性偏差它不会总是高估也不会总是低估长期来看是“准”的。而“随机抽样”这个假设正是保障“无偏性”的基石。它确保了你的样本不是某个特定子群体的“代言人”而是总体的一个公平“缩影”。一旦这个假设被破坏比如你只调查了北上广深的用户来代表全国网民或者只分析了A/B测试中点击了广告的用户来评估广告效果那么你的β̂就会产生系统性偏差。它可能稳定地、持续地高估了广告的真实转化率或者低估了某项政策对基层民众的影响。这种偏差是任何模型调参、特征工程都无法修正的。它就像你用一把尺子去量东西结果这把尺子本身就被厂家做短了1厘米。你量得再认真、再多次结果永远都少1厘米。所以每次你准备跑一个回归之前不妨先停下来严肃地问自己一句“我手里的这组X和Y真的是从我想研究的那个‘世界’里随机抓出来的吗”这个问题的答案比你选择用statsmodels还是scikit-learn重要一万倍。3. 核心细节解析与实操要点从理论到落地的“防坑指南”3.1 样本量不是越多越好而是“够用就好”新手最容易陷入的误区就是认为“样本量越大越好”。看到别人用100万条数据自己就想着凑1000万条。这不仅是资源浪费有时甚至是灾难的开始。样本量的确定是一场在“精度”、“成本”和“可行性”三者之间的精密平衡。一个过于庞大的样本可能会让你的计算变得无比缓慢甚至超出内存限制而一个过小的样本则会让你的结论像雾里看花毫无说服力。那么怎么才算“够用”这里有两个最实用、也最常被我用到的计算框架。框架一估计总体均值所需的样本量这是最基础、也最直观的场景。比如你想估计公司全体员工的平均通勤时间要求误差不超过5分钟置信水平为95%即我们有95%的把握说真实均值落在我们估计的区间内。计算公式如下n (Z_α/2 * σ / E)²其中Z_α/2是标准正态分布的临界值对于95%置信水平它是1.96σ是总体标准差这个值你通常不知道但可以用历史数据、小范围预调研或者一个合理的经验估计值来代替。比如根据常识通勤时间的标准差大概在30分钟左右E是你允许的最大误差这里是5分钟。代入计算n (1.96 * 30 / 5)² ≈ (11.76)² ≈ 138.3。所以你需要至少139名员工的通勤数据。注意这个公式假设你是在进行简单随机抽样并且总体规模N远大于n即N 20n。如果N比较小比如公司总共才200人那还需要用一个“有限总体校正因子”FPC来调整最终样本量会略小一些。框架二A/B测试中检测最小可测效应MDE所需的样本量这是互联网产品同学最常遇到的场景。你想知道把按钮颜色从蓝色改成绿色能否将点击率CTR从5%提升到5.5%即提升0.5个百分点。你希望有80%的统计功效Power来检测出这个差异并且控制第一类错误假阳性在5%以内。这时就需要用到更复杂的功效分析Power Analysis。幸运的是我们不需要手动解方程有现成的工具。我最常用的是Python的statsmodels.stats.power模块。下面是一段可以直接运行的代码from statsmodels.stats.power import zt_ind_solve_power from statsmodels.stats.proportion import proportion_effectsize # 已知参数 baseline_ctr 0.05 # 基线点击率 mde 0.005 # 最小可测效应绝对值 alpha 0.05 # 显著性水平 power 0.8 # 统计功效 # 计算效应量Cohens h这是z-test的标准输入 effect_size proportion_effectsize(baseline_ctr, baseline_ctr mde) # 求解每组所需样本量 sample_size_per_group zt_ind_solve_power( effect_sizeeffect_size, alphaalpha, powerpower, ratio1.0 # A组和B组样本量相等 ) print(f每组所需最小样本量: {int(sample_size_per_group)}) print(f总样本量: {int(2 * sample_size_per_group)})运行这段代码你会得到一个数字大约每组需要15,000个用户总共30,000个。这意味着如果你的流量不够强行跑一个只跑了10,000用户的A/B测试即使你看到了绿色按钮的CTR更高这个结果也很可能是随机波动造成的不具有统计意义。这就是“样本量不足”带来的假阳性风险。反之如果你的流量巨大每天都有50万用户那也没必要非等到30,000个用户才看结果。你可以设置一个“期中分析”Interim Analysis的规则比如每累积5,000个用户就检查一次但要使用更严格的显著性阈值比如p0.01以控制整体的I类错误率。这需要一点统计学知识但能极大提升你的实验效率。提示样本量计算不是一锤定音的魔法。它依赖于你对σ或baseline_ctr的估计。如果这些估计本身不准算出来的n也会不准。所以我的建议是先用一个保守的、稍大的估计值算出一个n然后在这个基础上结合你的实际资源做一个“向下取舍”。比如计算结果是139但你只能联系到120人那就120人但要在报告里明确说明“本研究基于120份有效问卷其统计功效为XX%对小于YY%的效应可能无法检出。”3.2 随机化的“灵魂”set.seed()不是装饰而是你的“时间机器”无论是R还是Pythonset.seed()或numpy.random.seed()这个函数常常被初学者当成一个可有可无的“仪式感”操作。他们觉得反正都是随机设不设种子结果不都一样吗这种想法极其危险。set.seed()的本质是为你启动了一个“确定性的随机数生成器”。它把一个随机数生成算法的内部状态固定在一个已知的起点上。这样只要你用同一个种子运行同一段随机抽样代码得到的结果就绝对、完全、100%相同。这有什么用它的价值体现在三个生死攸关的环节第一可复现性Reproducibility—— 你的科学底线。想象一下你在周五下午跑通了一个完美的模型准确率98%兴奋地给老板做了汇报。周一早上老板说“这个结果太棒了你再跑一遍把中间过程的截图发给我我要存档。” 你信心满满地打开脚本CtrlEnter……结果模型准确率变成了92%。老板的脸色瞬间就变了。这就是没有set.seed()的后果。你的分析失去了最基本的科学属性可复现。在严谨的数据工作中任何无法被他人或未来的你自己精确复现的结果都不具备讨论价值。set.seed(123)就是你在代码里埋下的一个“锚点”它保证了无论谁、在何时、在哪台机器上运行你的代码只要种子一样结果就一样。第二调试与排查—— 你的“时光倒流”能力。当你的模型突然崩了或者某个指标异常波动时最有效的排查方式就是“回到过去”。你把set.seed()放在数据预处理的最开头然后逐步运行代码每一步都保存中间结果。如果在某一步之后结果开始不对你就知道问题出在哪里。如果没有这个种子每一次运行都是全新的、不可预测的“混沌”你就像在迷雾中开车永远不知道下一个弯道会撞上什么。第三协作与交接—— 你的“信任凭证”。当你把一个项目交接给同事或者开源一个分析脚本时set.seed()是你递给对方的一把“钥匙”。它告诉对方“放心你照着我的步骤来一定能得到和我一模一样的结果。” 这消除了无数因环境差异不同版本的Python、不同的操作系统带来的“玄学Bug”让协作变得无比顺畅。注意set.seed()必须放在所有随机操作之前并且在整个分析流程中只设置一次。如果你在代码的不同地方反复设置不同的种子那只会制造更多的混乱。我习惯把它放在脚本的第二行紧跟着import语句之后作为一个无声的、但至关重要的承诺。3.3 Excel里的“抽样”不只是一个菜单而是一场数据质量的考验很多业务同学尤其是非技术背景的同事他们的主要战场就是Excel。原文提到了Excel的“数据分析工具包”这确实是一个入口但它背后隐藏着一个巨大的认知陷阱Excel的“抽样”功能本质上只是一个“随机行抽取”工具它完全不关心你的数据结构、不理解你的业务逻辑、也不提供任何关于抽样方法论的指导。它就像一把瑞士军刀里的小剪刀——有用但仅限于最基础的场景。当你在Excel里点击“数据分析”-“抽样”时它会弹出一个对话框让你选择“周期抽样”或“随机抽样”并输入一个“样本数”。如果你选择了“随机抽样”它会真的随机挑选指定数量的行。但问题来了它随机抽的是“行”而不是“个体”。如果你的数据表里一行代表一个“订单”而一个用户可能下了10个订单那么Excel抽出来的100行很可能只代表了50个用户因为有50个用户各下了2单。你的样本就从“用户”层面降维到了“订单”层面。这会导致严重的“自相关”Autocorrelation问题因为同一个用户的多个订单其行为是高度相关的。用这种样本算出来的用户平均消费额其标准误会被严重低估置信区间会窄得离谱让你产生一种“结果非常精确”的幻觉而实际上它可能错得离谱。所以在Excel里做抽样最关键的前置动作是数据清洗和结构化。在点击那个“抽样”按钮之前你必须确保你的数据表是“扁平化”的。每一行必须唯一地、无歧义地代表一个你想要研究的“分析单元”Analytical Unit。如果你想研究用户那就必须先用PivotTable或Remove Duplicates把每个用户聚合为一行包含其关键指标如总消费、平均停留时长、最后登录日期等。你已经添加了唯一的标识符。在数据表的第一列手动添加一列名为ID的序号从1开始一直填到最后一行。这是为了确保Excel的随机抽样是基于一个清晰、稳定的索引而不是基于它自己可能变化的“行号”。你已经备份了原始数据。Excel的“抽样”功能会直接在原工作表上创建一个新工作表里面是抽出来的样本。这是一个不可逆的操作除非你有备份。所以务必在操作前右键点击原始工作表标签选择“移动或复制”勾选“建立副本”然后再对副本进行操作。实操心得我几乎从不依赖Excel内置的“抽样”功能。我更喜欢用一个更透明、更可控的方法在数据旁边新增一列输入公式RAND()。这个函数会为每一行生成一个0到1之间的随机数。然后我对这一列进行升序排序。排序后最上面的N行就是你的随机样本。这种方法的好处是RAND()函数是“易失性”的它会在每次Excel重新计算时刷新这正好可以让你快速生成多个不同的随机样本用于做稳健性检验Robustness Check。比如你可以生成10个不同的1000行样本分别跑回归看看核心系数的波动范围有多大。这才是Excel里做抽样的正确姿势。4. 实操过程与核心环节实现手把手带你完成一次完整的抽样分析4.1 场景设定为一所新学校评估“免费午餐计划”的真实影响让我们把前面所有的理论放进一个具体的、有血有肉的场景里来一次完整的实操演练。这个场景就源自原文中那个“新校长”的例子但我会把它做得更真实、更复杂也更贴近你日常工作的挑战。背景你刚刚被任命为一所拥有3200名学生的公立高中的新校长。学校去年开始推行一项“免费营养午餐计划”旨在改善低收入家庭学生的健康状况和学业表现。现在学年结束上级教育部门要求你提交一份评估报告说明这项计划是否真的提升了学生的GPA平均绩点。你手头有一份全校学生的数据库包含以下字段student_id: 学生唯一IDgrade_level: 年级9, 10, 11, 12gender: 性别M, Fethnicity: 族裔Asian, Black, Hispanic, White, Otherfree_lunch_eligible: 是否符合免费午餐资格True/False—— 这是你的核心干预变量Treatment Variablegpa_fall: 去年秋季学期GPAgpa_spring: 今年春季学期GPAattendance_rate: 出勤率%核心挑战你不能直接比较“符合资格”和“不符合资格”这两组学生的GPA变化因为这存在严重的“选择偏差”。符合资格的学生往往家庭经济状况更差、父母教育水平更低、社区环境更不利这些因素本身就可能导致GPA偏低。你观察到的GPA差异到底是午餐计划的效果还是这些混杂因素Confounding Factors的作用这就是为什么我们需要抽样和严谨的因果推断设计。4.2 步骤一定义总体与抽样框架——画出你的“作战地图”在动手之前我们必须先明确我们要研究的“总体”是谁是全校3200名学生还是仅仅指“有资格享受免费午餐”的那部分学生答案是总体是“所有可能受到该午餐计划影响的学生”即全校所有3200名学生。因为我们最终要回答的问题是“这项计划对本校学生整体的学业表现产生了怎样的影响” 这是一个关于“政策效果”的宏观问题。接下来我们需要构建“抽样框架”Sampling Frame也就是那张“地图”。这张地图必须包含总体中每一个个体的、可用于抽样的唯一标识。在我们的数据库里student_id就是这个标识。但这里有个陷阱student_id本身是匿名的它不包含任何关于学生是否“可接触”、“可访问”的信息。在现实中抽样框架可能是一份包含了学生姓名、班级、班主任联系方式的花名册。但在本次分析中我们假设这份数据库是完整、准确、且可以直接用于抽样的。因此我们的抽样框架就是这3200行数据。4.3 步骤二选择抽样方法与确定样本量——一场精准的“外科手术”基于我们的场景简单随机抽样SRS显然不合适。因为全校学生在年级、性别、族裔上的分布是不均衡的一个纯随机的样本可能抽到的12年级学生过多而9年级学生过少导致样本结构失真。分层抽样Stratified Sampling是最佳选择。我们可以按两个关键维度分层年级Grade Level这是最重要的分层变量因为不同年级的课程难度、评价标准、GPA计算方式都不同直接影响结果的可比性。免费午餐资格Free Lunch Eligible这是我们的核心干预变量我们必须保证在每一层内都能有足够的“处理组”EligibleTrue和“对照组”EligibleFalse样本以便进行组内比较。因此我们将总体划分为4 (年级) x 2 (资格) 8个层。接下来我们需要确定每一层应该抽多少人。这里我们采用“按比例分配”Proportional Allocation即每一层的样本量等于该层在总体中所占的比例乘以总样本量。那么总样本量n是多少我们回到A/B测试的思维。我们想检测的是GPA的最小可测效应MDE。根据教育学文献一个被认为具有实际意义的GPA提升大约是0.1个点例如从2.8提升到2.9。我们设定基线GPAFall GPA的标准差σ ≈ 0.5这是一个合理的经验估计值置信水平95%Z1.96允许误差E 0.05我们希望估计的GPA提升值误差不超过0.05代入均值估计的公式n (1.96 * 0.5 / 0.05)² (19.6)² ≈ 384。所以我们决定抽取一个总样本量为400的样本向上取整留点余量。现在我们来计算每一层的样本量。假设数据库显示各层人数如下为简化此处给出虚构但合理的数据年级资格人数占比分配样本量400*占比9True2006.25%259False40012.5%5010True2206.875%27.5 → 2810False38011.875%47.5 → 4811True2407.5%3011False36011.25%4512True2608.125%32.5 → 3312False34010.625%42.5 → 43总计3200100%4004.4 步骤三在Python中执行分层随机抽样——代码即生产力现在我们把上面的设计变成一行行可执行的代码。这里我将展示一个既清晰又健壮的实现方式它不仅能完成抽样还能自动处理分层、校验样本量并生成一份详细的抽样报告。import pandas as pd import numpy as np # 1. 加载数据假设数据文件名为 school_data.csv df pd.read_csv(school_data.csv) # 2. 设置全局随机种子保证可复现性 np.random.seed(42) # 使用一个有意义的数字比如你的生日或项目启动日 # 3. 定义分层变量 strata_vars [grade_level, free_lunch_eligible] # 4. 创建一个字典存储我们预先计算好的各层样本量 # 这个字典的key是分层变量的组合元组value是该层的样本量 target_sample_sizes { (9, True): 25, (9, False): 50, (10, True): 28, (10, False): 48, (11, True): 30, (11, False): 45, (12, True): 33, (12, False): 43 } # 5. 执行分层抽样 # groupby分层然后对每一层应用sample()函数 sampled_df df.groupby(strata_vars, group_keysFalse).apply( lambda x: x.sample(ntarget_sample_sizes.get((x.name[0], x.name[1]), 0), random_state42, # 再次设置种子确保groupby内的随机性也一致 replaceFalse) ) # 6. 验证抽样结果 print( 抽样结果验证 ) print(f原始数据总行数: {len(df)}) print(f抽样后数据总行数: {len(sampled_df)}) print(f抽样比例: {len(sampled_df)/len(df)*100:.2f}%) # 按层统计实际抽到的样本量并与目标对比 actual_counts sampled_df.groupby(strata_vars).size().reset_index(nameactual_count) actual_counts[target_count] actual_counts.apply( lambda row: target_sample_sizes.get((row[grade_level], row[free_lunch_eligible]), 0), axis1 ) actual_counts[difference] actual_counts[actual_count] - actual_counts[target_count] print(\n各层抽样详情:) print(actual_counts.to_string(indexFalse)) # 7. 保存结果 sampled_df.to_csv(school_sample_400.csv, indexFalse) print(f\n抽样完成样本已保存至 school_sample_400.csv)这段代码的精妙之处在于groupby(..., group_keysFalse)group_keysFalse参数非常重要它告诉Pandas不要在结果的索引中保留分层的层级信息让最终的sampled_df保持一个干净、扁平的DataFrame结构方便后续分析。lambda x: x.sample(...)这是核心逻辑。x代表当前分组比如所有9年级且符合资格的学生我们直接在这个小组内调用sample()并传入该组的目标样本量。双重random_state42我们在np.random.seed(42)设置了全局种子又在sample()里再次指定了random_state42。这是为了双重保险确保即使在复杂的groupby-apply链式操作中随机性也完全可控。运行这段代码后你会得到一个400行的sampled_df。它完美地复刻了全校3200名学生在年级和资格两个维度上的结构比例。现在你手里的不再是一堆杂乱的数据而是一份经过精心设计的、具有高度代表性的“微型全校