大多数真实世界的数据集都包含异常值:那些与其余数据点差异极大的数据点。这是由于测量误差、数据输入错误或者极其罕见的事件所致,但这些异常值可能会使统计分析产生偏差,并导致结果不准确且不可靠。因此,识别和处理这些异常值对于有效的数据分析至关重要。完成本教程后,你将掌握足够的知识,能够在数据分析流程中运用这些技术。
概述
本篇全面的教程将介绍如何在实际数据集上检测和处理异常值的相关技巧。我们将涵盖以下内容:
- 了解数据的背景和所属领域
- 数据分布的可视化
- 应用统计方法
- 考虑数据转换
- 处理异常值
领域知识
在处理数据集时,首先应当具备相关领域的知识——即对该数据所涉及的具体领域或行业的理解与专业技能。这种知识有助于界定何为异常值。因为在一个领域中的异常情况在另一个领域中可能是正常的数据点。
例如,一位金融分析师可能会认为股票价格 5% 的变动是正常的,而在天气数据中,5 度的温度变化可能就是显著的变化。有了足够的领域知识,你就可以根据历史数据和经验设定更准确的异常值检测阈值。
让我们以一个来自医院的样本数据集为例,该数据集包含患者的血糖水平。一位领域专家(比如医生)知道,对于一个健康的个体,血糖水平通常在 70 至 140 毫克/分升之间:
import pandas as pd
# Sample data
data = {'patient_id': [1, 2, 3, 4, 5],
'glucose_level': [85, 92, 130, 200, 75]}
df = pd.DataFrame(data)
# Use domain knowledge to define outlier threshold
lower_threshold = 70
upper_threshold = 140
# Identify outliers
df['is_outlier'] = df['glucose_level'].apply(lambda x: x > upper_threshold)
print(df)
在这里,允许血糖水平的知识可以识别血糖水平超出正常范围的患者:
patient_id glucose_level is_outlier
0 1 85 False
1 2 92 False
2 3 130 False
3 4 200 True
4 5 75 False
情境因素
情境因素是指可能影响数据的外部因素。这些因素包括季节性趋势、经济状况、特殊事件等等。
• 季节性:数据可能具有季节性模式。例如,零售销售额通常在节假日期间大幅增长。
• 经济因素:经济衰退可能会导致财务数据出现异常模式。
• 特殊事件:诸如自然灾害之类的事件可能会导致原本稳定的数据出现显著偏差。
以一个销售数据样本为例。由于节假日购物,12 月的销售额通常会增加。但这些是正常的模式,并非一定是异常值。因此,我们将 12 月标记为销售额预期较高的月份:
import pandas as pd
# Sample data
data = {'month': ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
'sales': [200, 220, 230, 250, 260, 270, 300, 320, 310, 330, 350, 600]}
df = pd.DataFrame(data)
# Contextual knowledge: Define December as a month with expected high sales
expected_high_sales_month = 'Dec'
# Identify outliers considering the context
df['is_outlier'] = df.apply(lambda row: row['sales'] > 500 and row['month'] != expected_high_sales_month, axis=1)
print(df)
以下是12月销售额未被标记为异常值的产出:
month sales is_outlier
0 Jan 200 False
1 Feb 220 False
2 Mar 230 False
3 Apr 250 False
4 May 260 False
5 Jun 270 False
6 Jul 300 False
7 Aug 320 False
8 Sep 310 False
9 Oct 330 False
10 Nov 350 False
11 Dec 600 False
可视化异常数据
可视化是识别数据中异常值的有力工具。通过可视化数据,您可以发现不寻常的数值和可能表明存在异常值的模式。以下是用于可视化数据的一些常见图表。
箱形图
箱形图,或箱线图,是另一种理解数据分布的方式。它们突出显示四分位距(IQR),这有助于识别位于四分位数之外 1.5 倍 IQR 范围内的数据点,通常认为是异常值(接下来会介绍!)。
箱形图还适用于比较不同类别之间的分布情况。让我们举个例子:
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
# Sample data
data = {'category': ['A', 'A', 'A', 'A', 'A', 'B', 'B', 'B', 'B', 'B'],
'values': [10, 12, 14, 15, 30, 10, 11, 13, 13, 50]}
df = pd.DataFrame(data)
# Create a box plot
sns.boxplot(x='category', y='values', data=df)
plt.title('Box Plot of Values by Category')
plt.show()
在本例中,箱形图有助于识别每个类别中的异常值。位于“胡须”之外的数据点被认为是潜在的异常值:
散点图
散点图对于可视化一组数据的两个变量的值非常有用。它们对于检查变量之间的关系和识别多变量数据中的异常值非常有用。请看示例:
import pandas as pd
import matplotlib.pyplot as plt
# Sample data
data = {'x': [2, 4, 6, 8, 10, 12, 14, 16, 18, 20],
'y': [4, 8, 12, 16, 20, 24, 28, 32, 36, 63]}
df = pd.DataFrame(data)
# Create a scatter plot
plt.scatter(df['x'], df['y'])
plt.title('Scatter Plot of X vs Y')
plt.xlabel('X')
plt.ylabel('Y')
plt.show()
散点图显示了x和y之间的线性关系,除了最后一个数据点,它是异常值:
直方图
直方图有助于理解变量的分布情况,其做法是将数据划分为若干区间(即“分组”),然后展示每个区间内数据点出现的频率。
它们有助于理解数据的分布情况,包括偏度、分布形态以及是否存在异常值。直方图尤其有助于直观呈现数据集中不同数值范围出现的频率。直方图展示了数值的频率分布情况,并突出了分布图最右侧端的那个异常值:
import pandas as pd
import matplotlib.pyplot as plt
# Sample data
data = {'values': [1, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5, 20]}
df = pd.DataFrame(data)
# Create a histogram
plt.hist(df['values'], bins=10, edgecolor='black')
plt.title('Histogram of Values')
plt.xlabel('Value')
plt.ylabel('Frequency')
plt.show()
直方图显示了这些值的频率分布,分布的最右端可能有一个异常值:
因此,诸如箱形图、散点图和直方图之类的可视化提供了对数据的直观和即时的洞察,使你能够识别可能表示异常值的异常值和模式。
运用统计方法
统计方法提供了一种确定异常值的定量方法。这些方法基于数据的统计特性,有助于建立检测异常的客观标准。这里有两种常用的离群值检测统计方法。
z分数
z分数衡量的是一个数据点离均值的距离——用标准差来表示。
数学上,它由:
Z
=
(
X
−
μ
)
/
σ
Z = (X - μ) / σ
Z=(X−μ)/σ
其中,X为数据点,μ为数据的平均值,σ为标准差。
一个点的z分数在[-3,3]范围之外表明它可能是一个异常值,因为它距离平均值超过三个标准差。
让我们举个例子:
import numpy as np
import pandas as pd
# Sample data
data = {'values': [10, 12, 13, 15, 18, 19, 20, 22, 25, 100]}
df = pd.DataFrame(data)
# Calculate Z-scores
df['z_score'] = (df['values'] - df['values'].mean()) / df['values'].std()
# Identify outliers
threshold = 3
df['is_outlier'] = df['z_score'].abs() > threshold
print(df)
在这个例子中,数据点100的z分数大于3,表明它可能是一个离群值:
values z_score is_outlier
0 10 -0.578386 False
1 12 -0.503271 False
2 13 -0.465714 False
3 15 -0.390599 False
4 18 -0.277926 False
5 19 -0.240368 False
6 20 -0.202811 False
7 22 -0.127696 False
8 25 -0.015023 False
9 100 2.801794 False
四分位距(IQR)
四分位距(IQR)同样用于衡量离散程度,它是第三四分位数(Q3)与第一四分位数(Q1)之间的差值。
从数学角度来讲,四分位距(IQR)的计算公式为:
I
Q
R
=
Q
3
−
Q
1
IQR = Q3 - Q1
IQR=Q3−Q1
其中 Q1 和 Q3 分别为第一四分位数和第三四分位数。
通常情况下,离群值是指低于 Q1 - 1.5 * IQR 或高于 Q3 + 1.5 * IQR 的数据点。因此,四分位距有助于根据数据的分布范围来识别离群值。
这里有一个示例:
import pandas as pd
# Sample data
data = {'values': [10, 12, 13, 15, 18, 19, 20, 22, 25, 100]}
df = pd.DataFrame(data)
# Calculate Q1, Q3, and IQR
Q1 = df['values'].quantile(0.25)
Q3 = df['values'].quantile(0.75)
IQR = Q3 - Q1
# Identify outliers
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
df['is_outlier'] = (df['values'] upper_bound)
print(df)
在这个特定的例子中,100位于IQR定义的边界之外,将其标记为异常值:
values is_outlier
0 10 False
1 12 False
2 13 False
3 15 False
4 18 False
5 19 False
6 20 False
7 22 False
8 25 False
9 100 True
我们看到,诸如 Z 分数和四分位距(IQR)之类的统计方法为检测异常值提供了一种系统性的方法。
转换数据
你可以使用诸如对数和布克斯 - 奎奥斯(Box-Cox)变换等转换方法来帮助稳定方差并使数据分布标准化,尤其是在数据点分布在较宽范围内时。
虽然这些转换并非主要为检测异常值而设计,但它们可以通过使数据更适合标准统计技术间接地协助识别和处理异常值。
对数变换
对数变换是对每个数据点应用对数。这种转换对于倾斜数据特别有用,因为它压缩了数据的范围并减少了大值的影响。
这有助于使偏斜数据更加对称,从而可以提高假设数据正态分布的离群值检测方法的性能。请看例子:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# Sample data
data = {'values': [1, 2, 5, 10, 50, 100, 200, 500, 7000, 15000]}
df = pd.DataFrame(data)
# Apply log transformation
df['log_values'] = np.log(df['values'])
# Plot original vs. log-transformed data
plt.figure(figsize=(10, 5))
plt.subplot(1, 2, 1)
plt.hist(df['values'], bins=10, edgecolor='black')
plt.title('Original Data')
plt.subplot(1, 2, 2)
plt.hist(df['log_values'], bins=10, edgecolor='black')
plt.title('Log-Transformed Data')
plt.show()
这是对数变换数据的直方图:
Box-Cox转换
Box-Cox变换是一组幂变换,可以使非正态数据变得更正态。变换定义为:
其中 X 为原始数据,Y 为转换后的数据,λ 为转换参数。
参数 λ 被估计以实现最佳转换,使数据尽可能接近正态分布。这对于依赖正态性假设的异常值检测方法是有益的。
虽然对数变换和Box-Cox变换并非直接用于检测异常值的方法,但它们有助于使数据更适合分析,从而稳定方差并使分布标准化。
妥善处理异常值
一旦识别出异常值,下一步就是根据其性质及其对分析的影响来对其进行适当处理。以下是处理异常值的三种常见策略:删除、封顶和填补缺失值。
删除异常值
删除异常值意味着删除那些属于错误数据或与分析无关的数据点。这种方法适用于异常值是由数据输入错误或测量异常引起的。这样做可以确保剩余的数据对于分析来说具有代表性且准确。
以一组学生的考试成绩数据为例,其中有一些成绩是由于已知的数据输入错误而不正确的:
import pandas as pd
# Sample data
data = {'student_id': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
'test_score': [85, 90, 95, 100, 75, 89, 100, 105, 110, 300]}
df = pd.DataFrame(data)
# Define a threshold to identify outliers (test scores above 150 are considered errors)
threshold = 150
# Remove outliers
df_cleaned = df[df['test_score'] <= threshold]
print(df_cleaned)
输出结果:
student_id test_score
0 1 85
1 2 90
2 3 95
3 4 100
4 5 75
5 6 80
6 7 100
7 8 105
8 9 110
Winsorization
Winsorization操作是指将超出某一百分位数的极端值替换为该百分位数对应的值。它将极端值调整至指定范围内的最接近值。
例如,如果在第 5 百分位数和第 95 百分位数处对数据进行 Winsorization 处理,低于第 5 百分位数的值会被设置为第 5 百分位数对应的值,高于第 95 百分位数的值会被设置为第 95 百分位数对应的值。下面的代码片段展示了如何进行此操作:
import pandas as pd
import numpy as np
# Sample data
data = {'values': [10, 12, 13, 15, 18, 19, 20, 22, 25, 100]}
df = pd.DataFrame(data)
# Define the Winsorization percentiles (e.g., 5th and 95th percentiles)
lower_percentile = 5
upper_percentile = 95
# Calculate the percentile values
lower_value = df['values'].quantile(lower_percentile / 100)
upper_value = df['values'].quantile(upper_percentile / 100)
print(f"lower value: {lower_value:.2f}")
print(f"upper value: {upper_value:.2f}")
# Winsorize the extreme values
df['winsorized_values'] = df['values'].apply(lambda x: max(lower_value, min(x, upper_value)))
print(df)
输出结果:
lower value: 10.90
upper value: 66.25
values winsorized_values
0 10 10.90
1 12 12.00
2 13 13.00
3 15 15.00
4 18 18.00
5 19 19.00
6 20 20.00
7 22 22.00
8 25 25.00
9 100 66.25
填补异常值
你还可以用更合理的值来填补异常值,比如数据集的均值或中位数。这样做可以减少异常值的影响,因为会用更能代表数据集的值来替换它们。
以一个年龄数据集为例,其中有一些值由于错误而异常偏高:
import pandas as pd
# Sample data
data = {'person_id': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
'age': [25, 30, 35, 40, 28, 32, 36, 38, 150, 200]}
df = pd.DataFrame(data)
# Define a threshold to identify outliers (ages above 100 are considered errors)
threshold = 100
# Calculate the median age based on valid values
median_age = df[df['age'] <= threshold]['age'].median()
# Impute outliers with the median age
df['age_imputed'] = df['age'].apply(lambda x: x if x <= threshold else median_age)
print(df)
输出如下:
person_id age age_imputed
0 1 25 25.0
1 2 30 30.0
2 3 35 35.0
3 4 40 40.0
4 5 28 28.0
5 6 32 32.0
6 7 36 36.0
7 8 38 38.0
8 9 150 33.5
9 10 200 33.5
最后总结
为了准确地分析数据,检测和处理异常值是必要的。你可以使用领域知识、采用可视化和统计方法来帮助识别异常值。
一旦检测到异常值,就可以删除错误条目或不相关的异常值,在不丢失数据点的情况下减轻极端值的影响,并使用更合理的值来推算异常值,以保持数据的完整性。通过应用这些技术和策略,你可以高效地管理现实世界数据集中的异常值。