拉伊达法则(3σ准则)在Python数据清洗中的原理、实现与避坑指南

发布时间:2026/8/29 12:34:26
拉伊达法则(3σ准则)在Python数据清洗中的原理、实现与避坑指南 1. 项目概述从数据清洗的“脏活”说起做数据分析或者机器学习的朋友肯定都经历过这个阶段拿到一份数据兴冲冲地准备建模结果一跑出来模型效果差得离谱或者某个指标的统计结果怎么看怎么不对劲。这时候十有八九是你的数据里混进了“捣蛋鬼”——异常值。这些偏离主流群体太远的“异类”就像汤里的一颗老鼠屎能轻易地毁掉你精心熬制的“数据高汤”。今天要聊的“拉伊达法则”就是数据清洗工具箱里一把经典、好用的“漏勺”专门用来把这些“老鼠屎”给捞出来。别看它原理简单用对了场景效果立竿见影是每个数据从业者都应该掌握的基本功。拉伊达法则也叫3σ准则其核心思想基于一个基本假设在正态分布的数据中绝大多数约99.73%的观测值会落在平均值加减三个标准差的范围内。那些落在这个范围之外的数据点就被认为是小概率事件可以当作异常值来处理。这个法则在工程、质量控制、金融数据分析等领域应用非常广泛。当然它也不是万能的前提假设“数据服从或近似服从正态分布”是关键。如果你的数据是偏态分布或者存在多个峰值盲目套用3σ准则可能会误伤“良民”或者漏掉“真凶”。所以理解原理、明确适用场景比单纯调用一个函数更重要。这篇文章我会以一个从业超过十年的“数据清洁工”视角带你彻底搞懂拉伊达法则。我们不只讲Python代码怎么敲更要拆解背后的统计逻辑探讨不同场景下的应用技巧和避坑指南。无论你是刚入门的数据分析师还是需要快速处理实验数据的科研人员这篇内容都能给你一套可直接上手、且知其所以然的解决方案。2. 核心原理与适用性深度拆解2.1 拉伊达法则的统计学根基要玩转一个工具首先得理解它的设计图纸。拉伊达法则的根基深植于正态分布和切比雪夫定理之中。正态分布也叫高斯分布是自然界和社会科学中最常见的一种连续概率分布。它的概率密度函数呈经典的“钟形曲线”关于其均值μ对称。在正态分布中数据分布的离散程度由标准差σ来描述。一个非常重要的特性是约有68.27%的数据落在(μ-σ, μσ)区间内约有95.45%的数据落在(μ-2σ, μ2σ)区间内而约有99.73%的数据会落在(μ-3σ, μ3σ)区间内。这最后一个数字99.73%就是拉伊达法则3σ准则的直接来源。它意味着如果一个数据点距离均值超过了3个标准差那么它属于这个正态分布总体的概率只有不到0.27%是一个典型的小概率事件。在统计学上我们通常有理由将这种小概率事件视为异常。注意这里有一个常见的误解。很多人认为拉伊达法则“要求”数据必须严格服从正态分布。实际上对于近似正态分布的数据该法则依然有很好的参考价值。更广义的理论支持来自切比雪夫定理该定理对任何分布形态的数据都成立它指出对于任意数据集至少有(1 - 1/k²)比例的数据落在均值加减k个标准差的范围内。当k3时至少有88.89%的数据在(μ-3σ, μ3σ)内。这说明即使分布未知超过3σ的数据也确实是分布非常边缘的部分。正态分布下的99.73%是这个定理在特定分布下的最优情况。2.2 适用场景与前置检查明白了原理我们就能更理智地判断什么时候该请出拉伊达法则这把“刀”。最适合的应用场景近似单峰正态分布的数据这是它的“主场”。例如同一生产线上产品的某个尺寸测量值、大量人群的身高体重数据、重复实验的测量误差等。这些数据通常围绕一个中心值波动且偏度和峰度接近0。大样本数据中心极限定理告诉我们当样本量足够大时样本均值的分布会趋近于正态分布。因此对于大样本量的数据集即使原始分布不完全正态使用拉伊达法则识别出的异常值也常具有参考意义。初步的异常值筛查在探索性数据分析阶段拉伊达法则可以作为一个快速、自动化的工具帮助我们定位那些明显偏离的“极端值”为进一步分析提供线索。需要谨慎或避免使用的场景严重偏态分布例如收入数据、城市人口数据通常呈右偏分布少数极高值拉高了均值。此时均值本身已不能代表数据“中心”用基于均值和标准差的3σ法则会严重误判可能将大量正常的高值数据误标为异常。多峰分布数据数据存在多个聚集中心。一个全局的均值和标准差无法描述这种复杂结构会漏掉某个子群内部的异常值或者将另一个子群的正常值判为异常。小样本数据样本量太小如少于30时计算出的均值和标准差本身就不稳定受个别数据点影响极大此时应用3σ准则可靠性很差。数据中包含大量异常值如果异常值本身占比就很高比如超过5%它们会严重扭曲均值和标准差的计算导致“掩蔽效应”和“淹没效应”。即一个强异常值会把均值拉向自己同时夸大标准差使得其他异常值变得“看起来正常”或者大量异常值共同作用使标准差膨胀导致无法识别出任何异常值。实操前的必要检查因此在动手写代码剔除异常值之前务必先做两件事可视化检查绘制数据的直方图、核密度估计图或箱线图。用肉眼直观判断数据分布是否大致对称、呈单峰。定量计算计算数据的偏度和峰度。对于标准正态分布偏度和峰度均为0。通常如果偏度的绝对值小于1峰度的绝对值小于3可以认为数据近似正态可以尝试使用拉伊达法则。from scipy import stats skewness stats.skew(your_data) kurtosis stats.kurtosis(your_data) # 注意scipy的kurtosis计算的是超额峰度正态分布下为0 print(f偏度: {skewness:.4f}, 峰度: {kurtosis:.4f})3. Python实现从基础到工业级稳健方案3.1 基础实现与逐行解析我们先从最直观、最基础的实现开始。假设我们有一个一维的数值型数据数组data。import numpy as np def remove_outliers_3sigma_basic(data): 使用3σ拉伊达法则识别并移除异常值基础版 参数: data: 一维numpy数组或列表 返回: filtered_data: 移除异常值后的数据数组 outlier_indices: 被判定为异常值的原始索引列表 # 将输入转换为numpy数组便于计算 data_array np.array(data) # 计算数据的均值 (μ) 和标准差 (σ) # 这里使用的是样本标准差 (ddof1)在样本数据时更常用 mean np.mean(data_array) std np.std(data_array, ddof1) # 设定上下限阈值 lower_bound mean - 3 * std upper_bound mean 3 * std # 找出正常值的布尔掩码 (True表示正常值) # 使用逻辑与()确保数据同时大于下限且小于上限 mask (data_array lower_bound) (data_array upper_bound) # 获取异常值的索引位置 outlier_indices np.where(~mask)[0].tolist() # ~mask取反得到异常值掩码 # 使用掩码过滤数据得到清洗后的数据 filtered_data data_array[mask] return filtered_data, outlier_indices逐行解析与关键点np.std(data_array, ddof1)这是关键参数。ddof代表“Delta Degrees of Freedom”。在统计学中计算样本标准差时分母通常是n - 1而非n这是为了对总体标准差进行无偏估计。ddof1对应分母n-1ddof0对应分母n。对于样本数据通常使用ddof1。如果你确信你的数据就是总体本身可以用ddof0。阈值计算lower_bound mean - 3 * std。注意我们同时设定了上下限。有些场景下如只有单侧异常可以只设定上限或下限。布尔掩码索引data_array[mask]是NumPy的高效过滤方式。mask是一个与data_array形状相同的布尔数组为True的位置被保留。这种方式比用循环遍历快得多。返回值我们不仅返回清洗后的数据还返回异常值的索引。保留索引非常重要因为它允许你回溯这些异常值进行后续分析比如这些异常值是否来自某个特定批次是否对应某个特殊事件。3.2 处理多维数据与DataFrame实际工作中我们面对的多是表格型数据如Pandas DataFrame需要按列特征分别处理异常值。import pandas as pd import numpy as np def remove_outliers_df_3sigma(df, columnsNone, inplaceFalse): 对Pandas DataFrame的指定列应用3σ法则去除异常值。 参数: df: 输入的Pandas DataFrame columns: 需要处理的列名列表。如果为None则处理所有数值型列。 inplace: 是否在原DataFrame上修改。False则返回新的DataFrame。 返回: 清洗后的DataFrame以及一个记录各列异常值索引的字典。 if not inplace: df df.copy() # 如果未指定列则选择所有数值类型的列 if columns is None: columns df.select_dtypes(include[np.number]).columns.tolist() outliers_report {} # 用于记录每列异常值的索引 for col in columns: # 检查列中是否有非数值或全为NaN的情况 if not pd.api.types.is_numeric_dtype(df[col]) or df[col].isnull().all(): print(f警告: 列 {col} 非数值型或全为空已跳过。) continue # 计算该列的均值和标准差忽略NaN col_data df[col].dropna() if len(col_data) 2: # 数据太少无法计算有意义的std print(f警告: 列 {col} 有效数据少于2个已跳过。) continue mean col_data.mean() std col_data.std(ddof1) # 样本标准差 # 计算阈值 lower_bound mean - 3 * std upper_bound mean 3 * std # 找出异常值的布尔掩码 (True表示异常值) # 注意这里要处理原df[col]中的NaN它们不应被标记为异常值 outlier_mask df[col].notna() ((df[col] lower_bound) | (df[col] upper_bound)) # 记录异常值索引 outlier_indices df.index[outlier_mask].tolist() outliers_report[col] outlier_indices # 将异常值替换为NaN一种常见的处理方式 df.loc[outlier_mask, col] np.nan # 可选打印简要信息 if outlier_indices: print(f列 {col}: 发现 {len(outlier_indices)} 个异常值。阈值范围: [{lower_bound:.4f}, {upper_bound:.4f}]) if inplace: return None, outliers_report else: return df, outliers_report关键技巧与注意事项列类型检查使用pd.api.types.is_numeric_dtype确保只处理数值列避免对字符串或日期列进行无意义的均值计算。处理NaN值现实数据常有缺失。.dropna()用于计算统计量而.notna()用于在标记异常值时排除已有的NaN。我们将异常值替换为NaN这是一种保守且常见的做法保留了数据点的“位置”后续可以选择填充、插值或直接删除整行。inplace参数提供这个选项是良好的编程实践。默认inplaceFalse可以避免意外修改原始数据更安全。报告输出函数返回一个异常值报告字典这对于后续的根因分析至关重要。你可以知道是哪些行、在哪些特征上出了问题。3.3 工业级稳健方案应对非正态与异常值污染基础版3σ法则的致命弱点是均值和标准差本身极易受异常值影响。这就像一个“先有鸡还是先有蛋”的问题——我们需要用稳健的统计量来识别异常值但这些统计量却被异常值污染了。解决方案是使用稳健统计量如中位数和四分位距。方案一基于中位数和MAD的稳健3σ法则中位数对异常值不敏感。我们可以用中位数代替均值用中位数绝对偏差代替标准差。def remove_outliers_robust_mad(data, threshold3.5): 使用中位数和MAD的稳健方法识别异常值。 参数: data: 一维数据 threshold: 调整阈值通常3.5对应正态分布下的3σ 返回: filtered_data, outlier_indices data_array np.array(data) median np.median(data_array) # 计算MAD: 所有数据点与中位数偏差的绝对值的中位数 mad np.median(np.abs(data_array - median)) # 为了将MAD作为标准差的一致估计量需要一个缩放常数对于正态分布约1.4826 # 使得MAD_norm ≈ σ mad_norm 1.4826 * mad if mad ! 0 else 0 lower_bound median - threshold * mad_norm upper_bound median threshold * mad_norm mask (data_array lower_bound) (data_array upper_bound) outlier_indices np.where(~mask)[0].tolist() filtered_data data_array[mask] return filtered_data, outlier_indices为什么是1.4826在标准正态分布下数据与中位数偏差的绝对值的中位数MAD约等于0.6745σ。因此σ ≈ MAD / 0.6745 ≈ 1.4826 * MAD。这个缩放使得mad_norm在正态数据下近似等于标准差。方案二IQR法箱线图原理这可能是目前最流行、最稳健的异常值检测方法之一完全不依赖于均值和标准差也不假设正态分布。def remove_outliers_iqr(data, k1.5): 使用IQR四分位距法识别异常值。这是箱线图的标准方法。 参数: data: 一维数据 k: 范围乘数通常1.5温和异常值或3极端异常值 返回: filtered_data, outlier_indices data_array np.array(data) q1 np.percentile(data_array, 25) q3 np.percentile(data_array, 75) iqr q3 - q1 lower_bound q1 - k * iqr upper_bound q3 k * iqr mask (data_array lower_bound) (data_array upper_bound) outlier_indices np.where(~mask)[0].tolist() filtered_data data_array[mask] return filtered_data, outlier_indicesIQR法的优势它只依赖于数据的25%和75%分位数这两个分位数对异常值非常不敏感。k1.5这个经验值在正态分布下大致对应着上下限约在μ±2.7σ的位置能识别出约0.7%的温和异常值。实操建议首选可视化先画箱线图直观看到IQR法识别的异常点。组合使用对于一份新数据可以同时运行经典3σ、稳健MAD 3σ和IQR法对比它们找出的异常值集合。如果三者找出的集合高度重合说明这些点很可能是“真异常”如果差异很大就需要深入分析数据分布了。领域知识最重要任何统计方法给出的都是“疑似异常”列表。最终是否剔除、如何处置删除、修正、保留必须结合业务逻辑和领域知识来判断。例如在金融欺诈检测中一个极高的交易额可能是异常也可能是关键的案件线索绝不能简单删除。4. 实战案例电商平台用户交易金额分析让我们通过一个模拟的实战案例把上面的方法串起来。假设我们是一家电商平台的数据分析师需要分析某一天用户的交易金额数据用于后续的用户分层或风险识别。4.1 数据模拟与探索import numpy as np import pandas as pd import matplotlib.pyplot as plt import seaborn as sns # 设置随机种子保证可复现 np.random.seed(42) # 模拟核心正常用户交易金额对数正态分布更符合实际情况 n_normal 980 # 对数正态分布先生成正态分布的变量再取指数 log_normal_data np.random.normal(loc5.0, scale0.8, sizen_normal) core_amounts np.exp(log_normal_data) # 金额大致在几十到几百之间 # 模拟少量高价值用户也是正常的 n_high_value 15 high_value_amounts np.random.uniform(800, 1500, n_high_value) # 模拟异常值可能是数据录入错误如多输了小数点或欺诈试探 n_errors 5 error_amounts_low np.array([0.01, 0.05, 0.1]) # 极低金额可能是测试订单 error_amounts_high np.array([99999.0, 150000.0]) # 极高金额可能是输入错误 # 合并所有数据 all_amounts np.concatenate([core_amounts, high_value_amounts, error_amounts_low, error_amounts_high]) np.random.shuffle(all_amounts) # 打乱顺序 # 创建DataFrame df_trans pd.DataFrame({user_id: range(len(all_amounts)), transaction_amount: all_amounts}) print(f数据总行数: {len(df_trans)}) print(df_trans[transaction_amount].describe())首先我们通过描述性统计和可视化来感知数据。# 1. 描述性统计 print(交易金额描述性统计:) print(df_trans[transaction_amount].describe()) print(f\n偏度: {df_trans[transaction_amount].skew():.4f}) print(f峰度: {df_trans[transaction_amount].kurtosis():.4f}) # 2. 可视化 fig, axes plt.subplots(1, 3, figsize(15, 4)) # 直方图 密度曲线 axes[0].hist(df_trans[transaction_amount], bins50, edgecolorblack, alpha0.7, densityTrue) sns.kdeplot(df_trans[transaction_amount], axaxes[0], colorred, linewidth2) axes[0].axvline(df_trans[transaction_amount].mean(), colorgreen, linestyle--, labelf均值: {df_trans[transaction_amount].mean():.2f}) axes[0].axvline(df_trans[transaction_amount].median(), colororange, linestyle--, labelf中位数: {df_trans[transaction_amount].median():.2f}) axes[0].set_title(交易金额分布直方图) axes[0].set_xlabel(交易金额) axes[0].set_ylabel(密度) axes[0].legend() axes[0].grid(True, alpha0.3) # 箱线图 axes[1].boxplot(df_trans[transaction_amount], vertTrue, patch_artistTrue) axes[1].set_title(交易金额箱线图) axes[1].set_ylabel(交易金额) axes[1].grid(True, alpha0.3) # Q-Q图 (检验正态性) from scipy import stats stats.probplot(df_trans[transaction_amount], distnorm, plotaxes[2]) axes[2].set_title(Q-Q图 (vs 正态分布)) axes[2].grid(True, alpha0.3) plt.tight_layout() plt.show()通过观察你会发现数据严重右偏偏度远大于0直方图有一个长长的尾巴均值远大于中位数。箱线图的上方会出现一些独立的点异常值。Q-Q图上的点严重偏离对角线证实了数据不服从正态分布。这是一个典型的不适合直接用经典3σ法则的场景4.2 多方法对比与结果分析现在我们分别用经典3σ、稳健MAD 3σ和IQR法来检测异常值并对比结果。# 定义对比函数 def compare_outlier_methods(series, method_names, method_funcs): 对比不同异常值检测方法的结果。 results {} for name, func in zip(method_names, method_funcs): filtered_data, outlier_idx func(series.values) outlier_values series.iloc[outlier_idx].tolist() if outlier_idx else [] results[name] { indices: outlier_idx, values: outlier_values, count: len(outlier_idx), filtered_data: filtered_data } print(f{name}: 检出 {len(outlier_idx)} 个异常值。) if outlier_values: print(f 异常值示例: {outlier_values[:5]}) # 打印前5个 return results # 准备方法 from functools import partial methods [ (3σ Classic, partial(remove_outliers_3sigma_basic)), (3σ Robust (MAD), partial(remove_outliers_robust_mad, threshold3.5)), (IQR (k1.5), partial(remove_outliers_iqr, k1.5)), (IQR (k3), partial(remove_outliers_iqr, k3)), # 更宽松的IQR ] method_names [m[0] for m in methods] method_funcs [m[1] for m in methods] # 执行对比 comparison_results compare_outlier_methods(df_trans[transaction_amount], method_names, method_funcs)预期结果与分析3σ Classic可能会检出最多的异常值甚至包括一部分真实的高价值用户。因为极高的异常值如99999把均值和标准差都拉得非常大导致上限极高但极低异常值0.01仍能被检出。同时右偏分布导致均值右侧数据本就稀疏经典3σ的对称区间会误伤右侧正常高值。3σ Robust (MAD)表现会好很多。中位数不受极端值影响MAD也对异常值稳健。它能稳定地检出我们模拟的极低和极高异常值同时大概率能保留真实的高价值用户。IQR (k1.5)这是箱线图的默认标准。它会将高于Q3 1.5*IQR和低于Q1 - 1.5*IQR的点视为异常。在我们的右偏数据中它能有效检出极高的异常值但对于极低异常值如果Q1本身很小可能不够敏感。检出数量可能介于前两者之间。IQR (k3)更宽松的标准用于识别“极端异常值”。它可能只检出那个最离谱的99999和150000。通过这个对比你可以清晰地看到对于非正态的右偏数据经典3σ法则基本失效而稳健方法MAD或IQR才是更可靠的选择。4.3 决策与处理流程基于检测结果我们如何做决策确认异常值将几种稳健方法如MAD 3σ和IQR k1.5都检出的点列为高置信度异常值。业务复核极低金额0.01, 0.05, 0.1查询这些订单。可能是测试订单、刷单行为或支付系统测试。需要与运营或风控部门确认处理方式如标记为测试订单不参与业务分析。极高金额99999, 150000这很可能是数据录入错误多输了小数点实际应为999.99和1500.00。需要联系数据来源部门或通过规则进行修正如金额超过某个阈值的订单自动触发复核流程。被IQR法检出但未被MAD法检出的高值如1200这可能是真实的高价值用户。绝不能直接删除需要结合用户历史行为、用户等级等信息进行判断。如果该用户历史上就有大额消费记录这很可能是正常交易。执行处理根据复核结果在数据中做相应标记、修正或删除。通常建议先创建一个新的“数据清洗标记”列而不是直接删除原始数据。# 假设我们最终决定基于MAD 3σ的结果进行标记 _, outlier_idx_mad remove_outliers_robust_mad(df_trans[transaction_amount].values, threshold3.5) df_trans[is_outlier_mad] False df_trans.loc[outlier_idx_mad, is_outlier_mad] True df_trans[amount_cleaned] df_trans[transaction_amount].where(~df_trans[is_outlier_mad], othernp.nan) print(f标记为异常的交易数量: {df_trans[is_outlier_mad].sum()}) print(原始数据与清洗后数据统计对比:) print(df_trans[[transaction_amount, amount_cleaned]].describe())5. 高级话题与避坑指南5.1 多变量情境下的异常值检测现实中的数据往往是多维的。一个用户在“交易金额”上正常但在“购买频率”和“客单价”的组合上可能就显得异常。单变量检测会忽略这种多维关系。马氏距离法是一种经典的多变量异常值检测方法。它考虑了特征之间的相关性。对于一个p维的数据点x其到数据分布中心的马氏距离D²为D² (x - μ)^T * Σ^(-1) * (x - μ)其中μ是均值向量Σ是协方差矩阵。这个距离服从卡方分布。我们可以将马氏距离大于某个卡方分布阈值如对应p297.5%分位数的点视为异常值。from scipy.stats import chi2 def detect_outliers_mahalanobis(df, features, alpha0.975): 使用马氏距离检测多变量异常值。 参数: df: DataFrame features: 特征列名列表 alpha: 卡方分布的置信度阈值 返回: outlier_mask: 布尔序列True表示异常值 mahalanobis_d2: 马氏距离的平方 X df[features].values # 计算均值向量和协方差矩阵的逆使用伪逆提高数值稳定性 mean_vec np.mean(X, axis0) cov_mat np.cov(X, rowvarFalse) try: inv_cov_mat np.linalg.inv(cov_mat) except np.linalg.LinAlgError: # 如果矩阵奇异使用伪逆 inv_cov_mat np.linalg.pinv(cov_mat) # 计算每个样本到中心的马氏距离平方 diff X - mean_vec mahalanobis_d2 np.sum(diff inv_cov_mat * diff, axis1) # 计算卡方分布的阈值 threshold chi2.ppf(alpha, dflen(features)) outlier_mask mahalanobis_d2 threshold return outlier_mask, mahalanobis_d2注意马氏距离法对异常值污染同样敏感因为均值向量和协方差矩阵的计算会被异常值影响。可以使用最小协方差行列式等稳健方法先估计出“干净”数据的均值和协方差。5.2 自动化流程与参数调优在自动化数据流水线中异常值处理需要谨慎设置参数并记录所有决策。参数网格不要固定使用k3或k1.5。可以基于历史数据或业务理解为不同字段设置不同的阈值。例如对于“用户年龄”范围可能设定为[10, 100]直接用硬编码规则比统计方法更可靠。流水线集成将异常值检测模块化集成到scikit-learn的Transformer中方便嵌入到机器学习流水线。from sklearn.base import BaseEstimator, TransformerMixin import pandas as pd class RobustSigmaOutlierRemover(BaseEstimator, TransformerMixin): def __init__(self, threshold3.5, methodmad): self.threshold threshold self.method method # mad or iqr self.lower_bounds_ {} self.upper_bounds_ {} def fit(self, X, yNone): # X 是DataFrame for col in X.columns: if pd.api.types.is_numeric_dtype(X[col]): data X[col].dropna() if self.method mad: median np.median(data) mad np.median(np.abs(data - median)) scale 1.4826 if mad ! 0 else 1 mad_norm mad * scale self.lower_bounds_[col] median - self.threshold * mad_norm self.upper_bounds_[col] median self.threshold * mad_norm elif self.method iqr: q1 np.percentile(data, 25) q3 np.percentile(data, 75) iqr q3 - q1 self.lower_bounds_[col] q1 - self.threshold * iqr self.upper_bounds_[col] q3 self.threshold * iqr else: self.lower_bounds_[col] -np.inf self.upper_bounds_[col] np.inf return self def transform(self, X): X_transformed X.copy() for col in X.columns: lb self.lower_bounds_.get(col, -np.inf) ub self.upper_bounds_.get(col, np.inf) # 将异常值替换为NaN outlier_mask X[col].notna() ((X[col] lb) | (X[col] ub)) X_transformed.loc[outlier_mask, col] np.nan return X_transformed5.3 常见陷阱与避坑指南陷阱一在循环中重复计算统计量。如果你需要按组如按城市、按日期处理异常值不要在循环中对每个子集单独调用np.mean和np.std。使用Pandas GroupBy的transform方法进行向量化操作效率天差地别。# 错误做法 (慢) # for city in df[city].unique(): # city_data df[df[city] city] # mean city_data[amount].mean() ... # 正确做法 (快) def sigma_filter_series(s, n3): mean s.mean() std s.std(ddof1) return s.between(mean - n*std, mean n*std) df[is_normal] df.groupby(city)[amount].transform(sigma_filter_series)陷阱二忽视时间序列的局部特性。对于时间序列数据如股票日收益率、每日销售额全局的均值和标准差没有意义。异常应该相对于近期趋势来判断。需要使用滚动窗口计算局部统计量如过去30天的均值和标准差再应用3σ法则。陷阱三处理后的数据分布失真。剔除异常值后数据的方差会变小可能会让你后续的模型过于“乐观”。一种做法是不要直接删除而是用缩尾处理将超过99%分位数的值用99%分位数替代低于1%分位数的值用1%分位数替代。这保留了数据的分布形态和极值信息同时减弱了极端值的影响。def winsorize_series(s, limits(0.01, 0.99)): lower_bound s.quantile(limits[0]) upper_bound s.quantile(limits[1]) return s.clip(lowerlower_bound, upperupper_bound)陷阱四把“新奇点”当“异常点”。在监控或在线学习中一个新出现的、但与历史模式不同的模式点可能是新的趋势开始而不是错误。这属于“新奇点检测”范畴比简单的异常值检测更复杂。最后记住数据清洗的第一原则任何自动化的异常值处理都必须有可追溯的日志和人工复核机制。拉伊达法则及其变体是强大的辅助工具但最终让数据产生价值的永远是人的业务洞察和谨慎判断。

相关新闻