数据科学洞察:为何在处理杂乱零售数据时,均值会说谎

TL;DR · AI 摘要
文章通过真实零售数据揭示算术平均数在异常值影响下的失真问题,系统对比中位数与四分位距的鲁棒性,为数据科学实践提供可复用的清洗与分析方法。
核心要点
- 算术平均数对异常值极度敏感,易被大额订单或退货扭曲真实消费水平。
- 中位数是更稳健的中心趋势度量,能准确反映大多数客户的实际消费中值。
- 结合IQR(四分位距)可识别并过滤异常值,提升业务决策的数据可靠性。
结构提纲
按章节快速跳转。
日常使用的平均数在真实零售数据中可能严重失真,需警惕统计陷阱。
使用UCI公开的英国在线零售交易数据集(54万条记录)进行实证分析。
均值受极端值影响显著,计算结果20.40美元无法代表多数客户真实消费。
中位数不受异常值干扰,更贴近大多数顾客的实际支出水平。
使用IQR定义异常值边界,有效识别并剔除干扰性交易。
在业务分析中应优先使用中位数+IQR,而非依赖均值做决策。
思维导图
用一张图看清主题之间的关系。
查看大纲文本(无障碍 / 无 JS 友好)
- 零售数据中的平均数陷阱
- 问题根源
- 异常值(大额订单/退货)
- 均值对极端值敏感
- 解决方案
- 使用中位数替代均值
- 结合IQR识别并过滤异常值
- 实践工具
- Pandas DataFrame操作
- UCI在线零售数据集
金句 / Highlights
值得收藏与分享的关键句。
平均数不是‘平均’的同义词——它是一个对异常值极度敏感的统计量,常在真实世界中误导决策。
当90%的订单在10–25美元之间,但平均值是20.40美元时,你看到的不是‘典型客户’,而是几个大额订单的幻觉。
中位数不是‘更差的平均数’,它是另一种更真实的中心趋势度量,尤其在非正态分布数据中。
IQR不是为了删除数据,而是为了理解数据的‘正常范围’,让分析回归业务本质。

在我们的日常生活中,我们经常使用“平均”这个词:平均工资、平均分数、平均年龄等等。
以一家零售店为例。如果我们想通过平均订单金额来了解客户的消费情况,我们会加载数据、运行代码,得到每笔订单平均为 20 美元的结果。
搞定。
但似乎哪里不太对劲。
当我们仔细查看时,发现大多数客户的购买金额都是 15 美元左右。那么这 20 美元是从哪儿来的?
问题不在于数据本身,而在于“平均值”这个指标。这是一个典型的教科书陷阱:教科书中的情况完美无缺,但现实中的数据却并不听话。
有些客户大量采购(订单金额极高),有些客户退货(数量为负),还有一些异常值扭曲了整体图景。
在本文中,我们将使用在线零售数据集来回答一个简单却棘手的问题:在现实世界中,“平均”究竟意味着什么?
目录
先决条件
要跟随本教程,你需要:
基本的 Python 知识:理解变量和函数。
Pandas 库:熟悉数据加载和基本 DataFrame 操作。
开发环境:可使用 Jupyter Notebook、VS Code 或 Google Colab 等工具。
数据集:本分析使用的是在线零售数据集,可从 此处 下载。
数据集
我们将使用在线零售数据集——一个来自英国在线零售商店的真实交易数据集。
- 来源:UCI 机器学习库
- 收集时间:英国在线零售公司(2010–2011)
- 数据规模:541,909 笔交易
- 特征:8 个属性(发票号、库存代码、描述、数量、发票日期、单价、客户 ID、国家)
- 所有权:由 UCI 公开托管的数据集
- 许可:可用于研究和教育用途
均值:敏感的巨人
在统计学和数据分析中,“平均值”和“算术平均数”常被混用。我们的目标是计算数据集中所有交易的平均总金额。在在线零售数据集中,均值的计算方式如下:
在我们的数据集中,均值是通过将所有交易金额(包括批量购买和退货)相加,再除以交易总数得出的。这意味着每一个值——无论异常高还是为负——都会直接影响最终的平均值。
# 加载数据集
url = "https://archive.ics.uci.edu/ml/machine-learning-databases/00352/Online%20Retail.xlsx"
df = pd.read_excel(url, engine='openpyxl')
# 数据清洗与特征工程
df = df.dropna(subset=['CustomerID'])
df['TotalPrice'] = df['Quantity'] * df['UnitPrice']
# 计算均值(平均订单金额)
mean_value = df['TotalPrice'].mean()
print(f"平均订单金额(均值):{mean_value:.2f}")结果如下:
平均订单金额(均值):20.40乍一看,这个结果似乎很有希望:每笔交易都同等重要。但问题恰恰就出在这里。有时,少数极高或极低的交易会拉高或拉低所有普通客户的平均值。
请看下图中的均值分布:

该图展示了在线零售数据集的平均总金额,我们得到的均值为 20.42。(作者供图)
图中显示的是右偏分布,计算出的均值 20.40 实际上是一个教科书陷阱。最高的柱状图清晰表明,大多数交易集中在 15 美元左右,但红色线条却被少数客户的大额批量订单所形成的长尾向右拖拽。
在这种情况下,平均价格远高于普通客户的实际消费水平,因为它对异常值高度敏感——而实际上,大部分数据都集中在较低的价格区间。
简单来说,均值被一些极端值(尤其是 200–300 美元之间的订单)向右拉扯,这一点在图中非常明显。
当均值因极端值而失真时,我们需要一种不受此类异常值影响的指标——这就是中位数发挥作用的地方。
中位数定义为将数据排序后位于中间的值。
在我们的数据集中,我们将所有交易金额排序,取中间的那个值。
中位数的计算公式为:
与均值不同,中位数不依赖于极端值,它只关心数据的位置,而非数值大小。
# 数据清洗与特征工程
df = df.dropna(subset=['CustomerID'])
df['TotalPrice'] = df['Quantity'] * df['UnitPrice']
# 仅计算中位数
median_value = df['TotalPrice'].median()
print(f"典型订单金额(中位数):{median_value:.2f}")结果如下:
典型订单金额(中位数):11.10现在你会发现,这个结果落在了 15 美元附近——也就是大多数交易实际发生的区间。

该图展示了中位数的分布情况,准确反映了客户交易的实际金额。(作者供图)
在之前的图中,均值因大额订单被拉向右侧,而中位数只关心“中间那位客户花了多少钱”。因此,即使有人花了 300 美元,或某些交易为负值,中位数依然保持稳定。
在上图中,中位数图准确地突出了大多数客户所处的价格区间。
超越平均值:使用四分位数理解数据分布
到目前为止,我们研究了中位数,但仅知道中心位置是不够的。
要真正理解客户的消费行为,我们必须了解数据的分布范围,这时四分位数就派上用场了。
四分位数将数据集划分为以下四部分:
- Q1(第 25 百分位数):25% 的交易低于此值。
- Q2(第 50 百分位数):中位数
- Q3(第 75 百分位数):75% 的交易低于此值。
这在形式上被称为四分位距(IQR):
**IQR:检测异常值**
IQR 衡量中间 50% 数据的离散程度。
如果 IQR 很小,说明数据集中;如果 IQR 很大,说明数据分散。IQR 还能帮助我们数学上识别异常值。
异常值判定规则:
- 下界 = Q1 — 1.5 × IQR
- 上界 = Q3 + 1.5 × IQR
#### 一个简单的 IQR 示例
考虑以下交易金额:
#### 步骤 1:求中位数(Q2):
中间值为:
#### 步骤 2:求 Q1(下四分位数):
下半部分为 [5, 8, 10],其下四分位数为:
#### 步骤 3:求 Q3(上四分位数):
上半部分为 [15, 18, 20],其上四分位数为:
#### 步骤 4:计算 IQR:
#### 步骤 5:计算异常值边界:
任何低于 -7 或高于 33 的值都被视为异常值(但在本示例中,不存在异常值)。
将 IQR 应用于我们的数据集
在我们的零售数据集中,数据并非整齐划一,而是包含大量批量订单甚至负数退货。
# 1. 计算 IQR 和边界
Q1 = df['TotalPrice'].quantile(0.25)
Q3 = df['TotalPrice'].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR当我们对数据集计算 IQR 时,得到:
下界:-18.75
上界:42.45
异常值数量:33180
该图展示了异常值范围,即任何落在 -18.75 至 42.45 范围之外的值。(作者供图)
如图所示,落在 -18.75 至 42.45 范围之外的值被视为异常值,这些值将被剔除。
剔除异常值后重新计算均值
通过 IQR 方法,我们移除了超出典型消费范围的极端交易。
# 数据清洗与特征工程
df = df.dropna(subset=['CustomerID'])
df['TotalPrice'] = df['Quantity'] * df['UnitPrice']
# 原始均值
mean_value = df['TotalPrice'].mean()
print(f"原始均值:{mean_value:.2f}")
# IQR 计算
Q1 = df['TotalPrice'].quantile(0.25)
Q3 = df['TotalPrice'].quantile(0.75)
IQR = Q3 - Q1
# 定义边界
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
print(f"下界:{lower_bound:.2f}")
print(f"上界:{upper_bound:.2f}")
# 移除异常值
df_no_outliers = df[(df['TotalPrice'] >= lower_bound) & (df['TotalPrice'] <= upper_bound)]
# 移除异常值后的均值
new_mean = df_no_outliers['TotalPrice'].mean()
print(f"移除异常值后的均值:{new_mean:.2f}")重新计算后,我们得到:
原始均值:20.40
下界:-18.75
上界:42.45
移除异常值后的均值:11.63
移除异常值后,均值显著向多数交易发生的区域靠拢。我们现在得到的均值为 11.63,远优于之前受异常值拉扯的 20.40。
最终比较与洞察
综合所有图表的结果,我们对数据集有了完整理解。原始均值为 20.40,明显高于绝大多数实际发生的交易金额。这是因为少数高价值交易拉高了均值,使其被异常值扭曲。
而中位数则……