在我们的日常生活中,我们经常使用“平均数”这个词:平均工资、平均分数、平均年龄等等。

以一家零售店为例。如果我们想通过计算平均订单金额来了解顾客的消费情况,那么只需加载数据、运行相应的代码,就能得到每单平均20美元的结果。

看起来很简单吧?

但仔细观察后就会发现,大多数顾客购买的商品价值都在8到15美元之间。那么这个20美元的平均值是从哪里来的呢?

其实问题并不出在数据本身,而在于我们使用平均数这种统计方法。这正是教科书里常见的陷阱——在理论上一切看起来都很合理,但现实世界中的数据往往并不会如此理想。

有些顾客会进行批量购买,有些则会退货,而这些异常情况就会扭曲整个数据的平均值。

在这篇文章中,我们将利用在线零售数据集来探讨一个简单却又棘手的问题:在现实生活中,“平均数”到底意味着什么?

目录

先决条件

要理解以下内容,你需要具备以下条件:

基本的Python知识:了解变量和函数的概念。

Pandas库:熟悉数据加载及DataFrame的基本操作。

开发环境:需要能够使用Jupyter Notebook、VS Code或Google Colab等工具。

数据集:本次分析使用的在线零售数据集,可在此处下载:点击链接下载

数据集

我们将使用在线零售数据集进行分析。这个数据集包含了来自英国一家在线零售店的真实交易记录。

  1. 来源:UCI机器学习仓库

  2. 收集方:英国的一家在线零售企业(2010–2011年期间收集的数据)

  3. 数据量:共包含541,909条交易记录

  4. 数据字段:8个属性:InvoiceNo、StockCode、Description、Quantity、InvoiceDate、UnitPrice、CustomerID、Country

  5. 数据所有权:由UCI公开提供的数据集

  6. 许可政策:可供研究和教育用途使用

均值:敏感的“平均值”

在统计学和数据分析中,“平均数“和“算术平均数“这两个术语经常被交替使用。我们的目标是在数据集中计算出平均总价。在在线零售数据集的背景下,平均数的计算公式如下:

$$\text{平均订单价值} = \frac{\text{所有总价的总和}}{\text>交易数量}}$$

在我们的数据集中,平均数的计算方法是将所有交易金额(包括批量购买和退货的情况)加起来,然后除以总交易数量。这意味着,无论某个数值是异常高还是负数,它都会直接影响最终的计算结果。

# 加载数据集
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.40

该图表展示了在线零售数据集的平均总价,计算结果为20.42。(图片由作者提供)

从图中可以看出,这个数据的分布呈现出右偏态的特点。虽然计算出的平均值为20.40,但实际上这一数值具有误导性——因为大多数交易金额都处于8到15元的范围内,而某些客户进行的批量购买使得平均值被拉向了右侧。

在这种情况下,平均价格远远高于普通客户的实际消费水平,因为这个平均值对极端值非常敏感;而实际上,大部分数据的价值都位于较低的价格区间内。

简单来说,就是一些极端数值将平均数拉向了右侧,尤其是那些金额在200到300元之间的交易记录,在图表中这一点表现得尤为明显。

中位数:稳健的“中间值”

当平均值被极端数值所扭曲时,我们就需要一个不会受到这些异常值影响的指标。这时,中位数就派上了用场。

中位数被定义为在对数据排序后的中间值。

在我们的数据集中,我们会对所有交易记录进行排序,然后选取中间的那个数值。

计算中位数的公式如下:

$$\text{中位数} = \begin{cases} X_{\left[ \frac{n+1}{2} \right]} & \text{如果 } n \text{ 为奇数} \ \frac{X_{\left[ \frac{n}{2} \right]} + X_{\left[ \frac{n}{2} + 1 \right]}}{2} & \text{如果 } n \text{ 为偶数} \end{cases}$$

与平均值不同,中位数并不受极端值的影响,它关注的是数据在序列中的位置,而不是数值的大小。

# 数据清洗与特征工程
df = df.dropna(subset=['CustomerID'])
df['TotalPrice'] = df['Quantity'] * df['UnitPrice']

# 仅计算中位数
median_value = df['TotalPrice'].median()
print(f"典型订单金额(中位数):{median_value:.2f}")

计算结果如下:

典型订单金额(中位数):11.10

可以看出,这个数值落在\(8 — 15\)的范围内,而大多数交易记录也确实位于这个区间内。

该图表展示了中位数的分布情况,通过它我们可以准确了解顾客的平均消费金额。

该图表展示了中位数的分布情况,通过它我们可以准确了解顾客的平均消费金额。(图片由作者提供)

在之前的图中,由于一些大额交易的存在,平均值被拉向了右侧;但中位数所反映的只是处于中间位置的顾客的消费金额。因此,即使有人花费了300美元,或者某些交易的金额为负数,中位数仍然会保持稳定。

在上图中,中位数图表准确地显示了大多数顾客所处的消费范围。

超越平均值:通过四分位数了解数据分布

到目前为止,我们已经了解了中位数的概念,但仅仅知道数据的中心值是不够的。

要想真正理解顾客的消费情况,我们就需要了解数据的分布情况,而这时四分位数就发挥了重要作用。

四分位数将数据集划分为以下几部分:

  1. Q1(第25百分位): 25%的交易金额低于这个数值。

  2. Q2(第50百分位): 中位数

  3. Q3(第75百分位): 75%的交易金额低于这个数值。

这一概念可以用四分位距来正式表示:

$$IQR = Q_3 – Q_1$$

四分位距:用于检测异常值

四分位距用来衡量中间50%数据的分布范围。

如果四分位距较小,说明数据分布较为集中;如果四分位距较大,则说明数据分布较为分散。此外,四分位距还可以帮助我们从数学角度识别异常值。

异常值判定规则:

  1. 下界 = Q1 — 1.5 * IQR

  2. 上界 = Q3 + 1.5 * IQR

了解IQR的一个简单示例

考虑以下交易金额数据:

$$\left[ 5, 8, 10, 12, 15, 18, 20 \right]$$

步骤1:计算中位数(Q2):

中间数值为:

$$Q_2 = 12$$

步骤2:计算Q1(下四分位数):

数据的前半部分为[5, 8, 10],这部分数据的中位数为:

$$Q_1 = 8$$

步骤3:计算Q3(上四分位数):

数据的后半部分为[15, 18, 20],这部分数据的中位数为:

$$Q_3 = 18$$

步骤4:计算IQR:

$$IQR = Q_3 – Q_1 = 18 – 8 = 10$$

步骤5:确定异常值范围:

$$\begin{aligned} \text{下界} &= Q_1 – 1.5 \times IQR = 8 – 15 = -7 \ \text{上界} &= Q_3 + 1.5 \times IQR = 18 + 15 = 33 \end{aligned}$$

任何低于-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,这一数值似乎明显高于实际发生的交易总额;实际上,是一些高金额的交易使得平均值被抬高了,而异常值也进一步扭曲了这一数值。

另一方面,中位数为11.10,这个数值处于大多数交易发生的区间内。由此可见,中位数能更准确地反映普通客户的消费水平,因为它不会受到极端值的影响。

使用IQR方法去除异常值后,平均值降到了11.63,这一数值与中位数非常接近。这证明了之前的平均值本身并没有错误,只是受到了数据中极端值的影响;一旦这些异常值被剔除,平均值就成为了一个更为可靠的集中趋势指标。

结论

实验结果表明,当数据中包含异常值时,平均值可能会产生误导。在我们的数据集中,原始的平均值20.40高估了客户的消费水平,而中位数11.10则更能反映实际情况。去除异常值后,平均值变为11.63,与中位数十分接近。

这一现象提醒我们一个重要的道理:平均值本身并没有错,但使用它时必须了解数据的真实情况。

选择合适的平均数值取决于具体的数据集;在现实世界中,当数据复杂时,中位数或经过处理后的平均值往往能更准确地反映实际情况。

与我联系

  1. Medium

  2. LinkedIn

如果你想深入了解这个主题,可以访问以下链接:平均值、中位数与众数:数据分析中的集中趋势理解

Comments are closed.