数据科学家必备:推断统计实战指南——从抽样分布到A/B测试决策

📅 2026/6/17 13:26:21
数据科学家必备:推断统计实战指南——从抽样分布到A/B测试决策
1. 这不是“统计学考试复习资料”而是一份数据科学家每天都在用的推理工具包你打开一份用户行为漏斗报告发现新版本App的注册转化率从12.3%降到了11.7%——这0.6个百分点的下滑是真实的产品问题还是随机波动你训练了两个推荐模型A模型在测试集上准确率89.2%B模型89.5%差0.3%该上线哪个你分析了300名参与A/B测试用户的点击率发现实验组比对照组高1.8%这个差异值能代表全体用户吗这些问题不靠直觉不靠拍脑袋全靠推断统计Inferential Statistics给出可量化的回答。它不是教科书里远离现实的抽象公式而是数据科学家写SQL查完数据后、调完模型参数后、画完可视化图表后必须按下“确认键”前的最后一道逻辑校验。核心关键词——抽样分布、置信区间、假设检验、p值、统计功效——每一个都不是考试考点而是你每天在Jupyter Notebook里敲scipy.stats.ttest_ind()或statsmodels.api.OLS()时背后真正支撑你结论可信度的钢筋骨架。如果你还在用“看起来差不多”“感觉涨了”来下结论那你的分析就停留在描述性统计层面而真正的数据科学决策必须建立在推断统计提供的概率性担保之上。这篇内容专为已经会用Pandas清洗数据、会用Scikit-learn训练模型、但面对“这个差异到底有没有意义”仍会犹豫的实战者准备——它不讲证明只讲怎么用、为什么这么用、以及用错时你根本意识不到的致命陷阱。2. 项目整体设计与思路拆解从“样本看到的世界”到“总体真实的世界”2.1 核心逻辑链为什么我们不能直接看总体而必须依赖样本数据科学的现实困境非常朴素你永远无法观测到“全体”。你想知道某款APP所有1000万活跃用户对新UI的满意度但你不可能给每人发问卷你想评估一个广告策略对全国电商用户购买力的影响但你无法让所有人同时参与实验。于是我们退而求其次从总体中抽取一个样本Sample通过分析这个样本的特征去推测Infer总体Population的真实情况。这个过程看似简单却暗藏巨大风险如果抽样不随机比如只调查了APP里最活跃的10%用户他们本就更宽容那你的样本均值14.2%就会系统性高估总体均值如果样本量太小比如只问了20个人那哪怕随机抽样结果也可能因为偶然性而剧烈波动今天抽到12个满意、8个不满意明天可能变成8个满意、12个不满意。推断统计的核心任务就是量化这种“用样本猜总体”过程中固有的不确定性并给出一套严谨的规则告诉你在什么条件下可以相信你的猜测。它不承诺100%正确而是说“基于当前样本有95%的把握认为总体均值落在[11.5%, 12.9%]这个区间内”或者“如果总体其实没有差异那么我们观察到当前这么大甚至更大的差异纯属巧合的概率只有0.023即p0.023”。这种用概率语言表达确定性的思维方式是数据科学区别于经验主义的根本分水岭。2.2 方案选型为什么是中心极限定理CLT和正态近似而不是死磕原始分布当你拿到一个样本比如300名用户的点击率数据它的分布可能是歪的、有尖峰、有长尾——完全不像教科书里的钟形曲线。但推断统计的威力恰恰在于它提供了一个“万能适配器”中心极限定理Central Limit Theorem, CLT。CLT指出无论原始总体分布是什么形状只要你从总体中反复抽取大量通常n≥30即可大小为n的样本并计算每个样本的均值那么这些样本均值的分布将趋近于正态分布Normal Distribution。这个正态分布的均值就等于总体的真实均值μ它的标准差被称为“标准误Standard Error, SE”计算公式是总体标准差σ除以根号nSE σ/√n。这个定理之所以是基石是因为它把一个千变万化的现实世界强行“翻译”成了数学家研究了几百年的、性质极其优美的正态分布。有了正态分布我们就能利用其已知的、精确的面积比例例如均值±1.96个标准误覆盖95%的面积来构建置信区间、进行假设检验。我试过不用CLT直接用Bootstrap重采样10000次来模拟抽样分布结果和CLT计算出的置信区间几乎完全重合但耗时是后者的50倍。在生产环境中当你的数据管道需要每小时跑一次A/B测试报告时CLT带来的计算效率是不可替代的。当然CLT有前提样本必须独立同分布i.i.d.且样本量足够。对于n30的小样本或者总体方差未知的情况我们就得切换到t分布——它比正态分布尾巴更厚更能容纳小样本带来的额外不确定性这也是为什么scipy.stats.ttest_ind()是比ztest更常用的选择。2.3 架构设计三大支柱如何协同工作构成完整的推断闭环一个健壮的推断分析流程绝不是孤立地使用某个检验。它是一个由三个相互验证、层层递进的支柱构成的闭环描述性统计Descriptive Stats是起点而非终点计算样本均值、标准差、中位数、四分位距。这一步的目的不是下结论而是诊断数据质量。我曾遇到一个案例客户声称“新功能提升了留存”但描述性统计显示实验组的留存率分布存在一个异常高的峰值接近100%深入排查才发现是数据埋点错误把未启动App的用户也计入了“留存”。没有这一步后续所有推断都是空中楼阁。置信区间Confidence Interval, CI是核心输出回答“值是多少”它给出一个范围比如“实验组点击率比对照组高[0.8%, 2.2%]置信水平95%”。这个区间的意义是如果你重复整个抽样和计算过程100次大约有95次你算出的这个区间会包含真实的总体差异。它比一个单点估计如“高1.5%”信息量大得多因为它直观地展示了估计的精度。区间越窄说明你的估计越精确通常意味着样本量更大或变异更小区间如果包含0如[-0.3%, 1.5%]那就意味着“无差异”仍是完全可能的你无法宣称有提升。假设检验Hypothesis Test是决策引擎回答“有没有差异”它设定一个“零假设H₀”通常是“没有效果”、“没有差异”如H₀: μ₁ - μ₂ 0然后计算在H₀为真的前提下观察到当前样本数据或更极端数据的概率即p值。p值小通常0.05说明在“没效果”的世界里出现你看到的结果非常不可能因此你有理由拒绝H₀接受“备择假设H₁”即“有效果”。但p值绝不等于“H₀为假的概率”这是最常见的误解。它只是衡量证据强度的一个标尺。一个p0.049的结果和p0.051的结果在科学意义上并无本质区别但机械的“p0.05”阈值常被滥用。所以真正的专业实践是把CI和p值一起看CI告诉你差异有多大、有多准p值告诉你这个差异是否“显著到不太可能是偶然”。两者结合才能做出既稳健又务实的业务决策。3. 核心细节解析与实操要点参数、假设与那些文档里不会写的坑3.1 置信区间的构建从理论公式到Python一行代码的真相置信区间的通用公式是样本统计量 ± (临界值 × 标准误)。对于两个独立样本均值的差异如A/B测试公式为(x̄₁ - x̄₂) ± t* × √(s₁²/n₁ s₂²/n₂)其中x̄₁, x̄₂是两组样本均值s₁, s₂是样本标准差n₁, n₂是样本量t*是t分布的临界值取决于置信水平和自由度。这个公式背后藏着几个关键细节决定了你结果的生死标准误SE的计算为何是“平方和再开方”这源于方差的可加性。两个独立随机变量之差的方差等于各自方差之和Var(X-Y) Var(X) Var(Y)。而标准误是标准差所以是√(Var(X) Var(Y))。如果你错误地写成√(s₁²/n₁) √(s₂²/n₂)即SE₁ SE₂你的区间会系统性地过宽导致过度保守错过真实效应。我踩过这个坑在早期代码里手写了加法结果CI宽度比正确计算的宽了近40%差点让我否决了一个真正有效的功能。t*临界值自由度df怎么算对于两独立样本最常用的是Welchs t-test它不假设两组方差相等其自由度计算复杂Welchs approximation但scipy.stats.ttest_ind()默认就用这个你无需手动计算。关键是要理解df越大t分布越接近正态分布t就越小CI就越窄。小样本df小时t会显著大于1.96正态分布的95%临界值这是t分布对小样本不确定性的“补偿”。Python实操别再手算用statsmodels获取最丰富的信息scipy的ttest_ind()只返回t值和p值而statsmodels的CompareMeans则能一站式输出CI、t值、p值、效应量Cohens dfrom statsmodels.stats.weightstats import CompareMeans from statsmodels.stats.weightstats import DescrStatsW # 假设group_a和group_b是两个numpy数组 cm CompareMeans(DescrStatsW(group_a), DescrStatsW(group_b)) # 获取95%置信区间差异 ci cm.tconfint_diff(alpha0.05, usevarunequal) # 获取t检验结果 tstat, pval, df cm.ttest_ind(usevarunequal) print(f差异的95% CI: [{ci[0]:.3f}, {ci[1]:.3f}]) print(ft值: {tstat:.3f}, p值: {pval:.3f}, 自由度: {df:.1f})这段代码输出的信息远超一个简单的“p0.05”它让你一眼看清效应的方向、大小、精度和统计显著性。3.2 假设检验的四大支柱缺一不可的“体检清单”一个有效的假设检验必须同时满足四个基本假设。任何一个不成立你的p值就失去了意义。这不是理论教条而是我在多个项目中被数据“打脸”后总结的血泪清单独立性Independence这是最高优先级也是最容易被忽视的。A/B测试中如果同一个用户在实验组和对照组都出现了比如因设备ID去重失败或者用户之间存在社交传染A用户点击影响了B用户那么观测值就不是独立的。此时标准的t检验会严重低估标准误导致p值虚低产生大量假阳性。解决方案是严格的数据去重按user_id聚合和实验设计确保流量分割互斥。正态性Normalityt检验要求样本均值的抽样分布是正态的。根据CLT大样本n30通常没问题。但对于小样本原始数据的分布就很重要。如果数据极度偏斜如收入、点击次数即使n50t检验也可能不稳健。此时首选非参数检验如Mann-Whitney U检验它不依赖分布假设只比较秩次。scipy.stats.mannwhitneyu()就是为此而生。方差齐性Homoscedasticity即两组数据的方差应大致相等。如果实验组方差是对照组的3倍以上标准t检验的统计功效会下降。scipy.stats.levene()可以检验此假设。如果不满足务必在ttest_ind()中设置equal_varFalse即使用Welchs t-test它会自动调整自由度和标准误。随机抽样Random Sampling这是所有推断的根基。如果你的“样本”其实是方便样本如只分析了iOS用户忽略了Android那么你的结论只能推广到这个方便样本而非你声称的“全体用户”。在数据科学中这往往体现为选择偏差Selection Bias。解决之道是明确界定你的目标总体并确保你的数据采集机制能无偏地覆盖它。提示在跑任何检验前先执行这个三行检查print(样本量:, len(group_a), len(group_b)) print(方差比:, np.var(group_a)/np.var(group_b)) print(Levene检验p值:, scipy.stats.levene(group_a, group_b).pvalue)这三行代码能帮你避开80%的常见陷阱。3.3 效应量Effect Size为什么p值再小也不能代替“实际意义”这是数据科学家走向成熟的标志性认知跃迁。p值只告诉你“差异是否不太可能是偶然”但它完全不告诉你“这个差异有多大”。想象一个拥有100万用户的A/B测试即使实验组点击率只比对照组高0.001%即万分之一在如此巨大的样本量下p值也可能小于0.001显得“极其显著”。但这个0.001%的提升在商业上毫无价值甚至可能被运维成本所抵消。这时你就必须看效应量。最常用的是Cohens dd (x̄₁ - x̄₂) / s_pooled其中s_pooled是合并标准差。Cohen给出了经验性解读d0.2为小效应0.5为中等0.8为大效应。一个d0.05的结果无论p值多小都意味着效应微乎其微。statsmodels的CompareMeans可以直接计算effect_size cm.effectsize print(fCohens d: {effect_size:.3f})我个人的经验是在汇报A/B测试结果时必须同时呈现三个数字差异值如1.5%、95%置信区间如[0.8%, 2.2%]和Cohens d如0.32。这三个数字合起来才构成一个完整、可信、可行动的结论。只报p值是数据分析师报全三个才是数据科学家。4. 实操过程与核心环节实现从原始数据到可交付结论的完整流水线4.1 数据准备与清洗让推断从干净的起点出发推断统计的输入必须是高质量的、结构化的数值型数据。以一个典型的电商A/B测试为例原始日志表user_events可能包含数百万行我们需要将其提炼为一个极简的、适合统计分析的宽表-- SQL: 从原始事件日志中聚合出每个用户的最终状态 WITH user_metrics AS ( SELECT user_id, -- 实验分组确保是首次访问时分配的避免污染 MAX(CASE WHEN event_name experiment_assignment THEN experiment_group END) AS group, -- 关键指标是否在7天内完成购买二值变量 MAX(CASE WHEN event_name purchase THEN 1 ELSE 0 END) AS converted, -- 或者连续变量7天内总消费金额 SUM(CASE WHEN event_name purchase THEN amount ELSE 0 END) AS total_spend FROM user_events WHERE event_time 2024-01-01 AND event_time 2024-01-08 GROUP BY user_id ) SELECT group, converted, total_spend FROM user_metrics WHERE group IN (control, treatment) -- 严格过滤排除未分组用户 AND converted IS NOT NULL; -- 排除数据缺失用户这段SQL的关键点在于MAX()聚合确保每个用户只有一行记录避免了同一用户多次事件造成的重复计数。experiment_group取MAX()是为了捕获用户首次被分配到的组别防止后期因重定向等原因导致的组别混淆。WHERE子句的双重过滤IN和IS NOT NULL是硬性要求任何缺失值或无效组别都会破坏独立性假设。清洗后的Python DataFramedf应该长这样user_idgroupconvertedtotal_spendu123control00.0u456treatment1299.0............注意converted是0/1变量适用于卡方检验或logistic回归total_spend是连续变量适用于t检验。指标类型决定了你该用哪种检验方法这是第一步也是最重要的一步。4.2 核心推断分析针对不同指标类型的“三步走”标准化流程场景一二值转化率如注册、购买、点击——使用卡方检验Chi-square或Z检验这是最常见场景。我们关心的是“比例”是否有差异。Step 1: 构建列联表Contingency Tableimport pandas as pd import numpy as np from scipy import stats # 将DataFrame转换为列联表 contingency_table pd.crosstab(df[group], df[converted]) print(contingency_table) # 输出示例 # converted 0 1 # group # control 1200 300 # treatment 1150 350Step 2: 执行卡方检验chi2, p_val, dof, expected stats.chi2_contingency(contingency_table) print(f卡方值: {chi2:.3f}, p值: {p_val:.3f}) # 计算效应量Phi系数Phi Coefficient适用于2x2表 phi np.sqrt(chi2 / contingency_table.sum().sum()) print(fPhi系数: {phi:.3f}) # Phi 0.1 通常认为有实际意义Step 3: 计算置信区间比例差异对于比例差异statsmodels提供了专门的函数from statsmodels.stats.proportion import proportion_confint # 分别计算两组的转化率和置信区间 conv_control contingency_table.loc[control, 1] / contingency_table.loc[control].sum() conv_treat contingency_table.loc[treatment, 1] / contingency_table.loc[treatment].sum() # 计算两组转化率差异的95%置信区间使用Newcombe方法更准确 ci_diff proportion_confint( count[contingency_table.loc[treatment, 1], contingency_table.loc[control, 1]], nobs[contingency_table.loc[treatment].sum(), contingency_table.loc[control].sum()], methodnewcomb ) print(f转化率差异的95% CI: [{ci_diff[0]-ci_diff[1]:.3f}, {ci_diff[1]-ci_diff[0]:.3f}]) # 更清晰的写法 diff_point conv_treat - conv_control print(f点估计差异: {diff_point:.3f}) print(f95% CI: [{ci_diff[0]:.3f}, {ci_diff[1]:.3f}]) # 这是[下限, 上限]场景二连续型指标如停留时长、客单价——使用Welchs t检验Step 1: 按组分离数据group_control df[df[group] control][total_spend].dropna() group_treatment df[df[group] treatment][total_spend].dropna() print(f对照组样本量: {len(group_control)}, 均值: {group_control.mean():.2f}) print(f实验组样本量: {len(group_treatment)}, 均值: {group_treatment.mean():.2f})Step 2: 执行Welchs t检验并获取CIfrom statsmodels.stats.weightstats import CompareMeans from statsmodels.stats.weightstats import DescrStatsW cm CompareMeans(DescrStatsW(group_treatment), DescrStatsW(group_control)) ci cm.tconfint_diff(alpha0.05, usevarunequal) tstat, pval, df cm.ttest_ind(usevarunequal) effect_size cm.effectsize print(f均值差异点估计: {group_treatment.mean() - group_control.mean():.2f}) print(f95%置信区间: [{ci[0]:.2f}, {ci[1]:.2f}]) print(ft值: {tstat:.3f}, p值: {pval:.3f}, 自由度: {df:.1f}) print(fCohens d: {effect_size:.3f})Step 3: 可视化辅助判断箱线图均值点import matplotlib.pyplot as plt import seaborn as sns plt.figure(figsize(8, 5)) sns.boxplot(datadf, xgroup, ytotal_spend) # 添加均值点红点和误差线95% CI for i, group in enumerate([control, treatment]): data df[df[group]group][total_spend].dropna() mean_val data.mean() ci_lower, ci_upper proportion_confint(countdata.sum(), nobslen(data), methodwilson) if False else (0,0) # 连续变量用t-CI # 实际上这里用t-CI更合适但绘图时可用seaborn的pointplot sns.pointplot(datadf, xgroup, ytotal_spend, joinFalse, capsize0.1, colorred) plt.title(实验组 vs 对照组客单价分布) plt.show()这张图能让你一眼看出两组的中位数箱体是否分离离群值点是否过多均值点红点是否落在对方箱体内这些视觉线索与数字结果相互印证能极大增强你结论的说服力。4.3 结论解读与业务沟通如何把统计语言翻译成老板能听懂的话技术分析的终点不是一张漂亮的Jupyter Notebook而是推动业务决策。这就要求你必须精通“翻译”。以下是我总结的“三句话结论模板”屡试不爽第一句事实陈述“基于对X名用户的分析实验组的[指标名称]平均为[数值]对照组为[数值]差异为[数值]。”例基于对15000名用户的分析实验组的7日留存率平均为28.4%对照组为26.7%差异为1.7个百分点。第二句统计确定性“这个差异的95%置信区间为[下限]到[上限]且p值为[数值]表明该差异极不可能是由随机波动引起的。”例这个差异的95%置信区间为[0.9, 2.5]个百分点且p值为0.002表明该差异极不可能是由随机波动引起的。第三句业务意义与行动建议“考虑到该效应量Cohens d [数值]属于[小/中/大]效应且[提及业务影响如预计每月可增加收入Y万元]我建议[具体行动如全量上线该功能]。”例考虑到该效应量Cohens d 0.28属于中等效应且预计每月可增加收入约120万元我建议在下个版本中全量上线该个性化推荐模块。注意永远不要说“证明了”、“100%确定”。要说“提供了强有力的证据表明”、“有高度统计学信心认为”。统计学的本质是管理不确定性而非消除它。5. 常见问题与排查技巧实录那些让你深夜debug的“幽灵bug”5.1 “p值明明0.05但业务方说效果不明显”——效应量缺失综合征现象A/B测试报告里p0.001结论是“显著提升”但产品负责人反馈“用户根本没感觉数据后台也看不出变化。”排查思路立刻检查效应量和置信区间。大概率是样本量过大导致微小的、无业务价值的差异也被判为“显著”。实操步骤计算Cohens d。如果d 0.1基本可以判定为“统计显著但业务不显著”。查看置信区间。如果区间宽度极窄如[0.001%, 0.003%]说明精度虽高但效应本身微乎其微。回归业务场景这个0.002%的提升对应多少实际用户能带来多少收入是否值得投入开发和运维成本我的心得在启动一个大型A/B测试前我会和产品、运营团队一起预设一个最小可检测效应Minimum Detectable Effect, MDE。比如“我们只关心能带来至少0.5%转化率提升的功能”。然后用样本量计算器如statsmodels.stats.power.zt_ind_solve_power反推出所需的最小样本量。这能避免“为显著而显著”的资源浪费。5.2 “同样的数据用t检验和Mann-Whitney检验结论相反”——分布假设冲突现象对一组严重右偏的收入数据大部分用户0消费少数用户高消费t检验p0.12不显著而Mann-Whitney检验p0.03显著。原因t检验假设数据近似正态而你的数据违背了这一假设。t检验在这种情况下统计功效Power会大幅下降即它更难发现真实的差异从而更容易得出“不显著”的错误结论II类错误。Mann-Whitney作为非参数检验对此类数据更鲁棒。排查与解决诊断用scipy.stats.shapiro()或Q-Q图检查正态性。对于收入、点击次数等典型长尾数据Shapiro检验几乎总是显著p0.05表明非正态。决策树如果样本量大n50且数据只是轻微偏斜t检验通常仍可用CLT保障。如果样本量小n30或数据极度偏斜如大量0值无条件选择Mann-Whitney U检验。scipy.stats.mannwhitneyu(group_a, group_b, alternativetwo-sided)。我的心得对于互联网产品的核心指标如ARPU、LTV我默认采用非参数检验。因为它们的分布形态是领域常识没必要每次都要做正态性检验来“自证清白”。效率和稳健性优先于理论上的“最优”。5.3 “置信区间很宽结果不可信”——样本量不足或变异过大现象计算出的点击率差异CI是[-1.5%, 4.5%]宽度高达6个百分点这意味着“可能下降1.5%也可能上升4.5%”结论完全模糊。原因置信区间的宽度 2 × (临界值 × 标准误)而标准误SE σ/√n。所以宽度主要受两个因素影响总体标准差σ数据本身的变异程度和样本量n。σ大用户行为差异大或n小都会导致SE大CI宽。排查与优化检查n确认你的分组样本量是否均衡。如果对照组有10000人实验组只有2000人那实验组的SE会是对照组的√5≈2.2倍严重拖累整体精度。确保流量分配是50/50。检查σ计算两组的标准差。如果某组标准差异常高如实验组用户行为两极分化可能意味着实验设计有问题如功能只对特定人群有效但未做人群分层。解决方案增加样本量延长测试周期这是最直接的方法。降低变异进行分层分析Stratified Analysis。例如先按新老用户分层再在每一层内做t检验。这能控制混杂变量减小层内的σ从而得到更窄的CI。更换指标如果原始指标如总时长变异太大考虑使用更稳定的指标如“是否停留超过1分钟”的二值变量。我的心得在项目初期我会用历史数据估算σ然后用公式n ≈ (Z* × σ / Margin_of_Error)²来预估所需样本量。其中Margin_of_Error是你能接受的CI半宽如±0.5%。这比盲目跑一周再看结果是否“够宽”要高效得多。5.4 “A组B组B组C组但A组和C组比较却不显著”——多重比较谬误现象你做了三组对比A/B/C发现A vs B显著B vs C显著但A vs C却不显著。这似乎违反了传递性ABC 应推出 AC。原因每一次假设检验都有犯I类错误假阳性的风险α0.05。当你进行多次比较时整体犯错的概率会急剧上升。进行k次独立检验整体错误率约为1 - (1-α)^k。对于k3整体错误率≈14.3%远高于单次的5%。A vs C不显著很可能是因为它的p值刚好在0.05边缘而其他两次“幸运”地低于了阈值。解决方案必须进行多重比较校正Multiple Testing Correction。Bonferroni校正最简单粗暴将α阈值除以比较次数k。对于3次比较新阈值为0.05/3≈0.0167。只有p0.0167才算显著。优点是简单、保守缺点是过于严格会增加II类错误假阴性。Benjamini-Hochberg (BH)校正控制错误发现率False Discovery Rate, FDR更适用于探索性分析。statsmodels.stats.multitest.multipletests()可一键实现。from statsmodels.stats.multitest import multipletests # 假设你有三个p值 pvals [0.012, 0.035, 0.048] reject, pvals_corrected, alphacSidak, alphacBonf multipletests(pvals, alpha0.05, methodfdr_bh) print(校正后p值:, pvals_corrected) print(是否拒绝H0:, reject)我的心得在正式的、需要向高层汇报的A/B测试中我坚持使用Bonferroni校正。因为它的逻辑最清晰“我们只允许整体有5%的犯错风险”。而在日常的、快速迭代的数据探索中BH校正能帮助我更快地发现潜在信号。关键是要意识到不做校正的多重比较其结果的可靠性是存疑的。6. 工具链与工程化实践如何让推断统计成为可复用、可审计的流水线6.1 从Jupyter到Production封装你的统计逻辑在探索阶段Jupyter Notebook是神器。但当一个分析模式如A/B测试框架被反复使用时就必须工程化。我的做法是将核心统计逻辑封装成一个Python类class ABTestAnalyzer: def __init__(self, control_data, treatment_data, metric_typecontinuous): self.control control_data self.treatment treatment_data self.metric_type metric_type def run_analysis(self): if self.metric_type binary: return self._analyze_binary() elif self.metric_type continuous: return self._analyze_continuous() def _analyze_continuous(self): from statsmodels.stats.weightstats import CompareMeans from statsmodels.stats.weightstats import DescrStatsW cm CompareMeans(DescrStatsW(self.treatment), DescrStatsW(self.control)) ci cm.tconfint_diff(alpha0.05, usevarunequal) tstat, pval, df cm.ttest_ind(usevarunequal) return { point_estimate: self.treatment.mean() - self.control.mean(), ci_95: list(ci), p_value: pval, t_stat: tstat