最全总结!十大异常值补技术 !!

文摘   2024-11-11 11:16   北京  

哈喽,我是小白~

在算法的实验中,一定会遇到异常值需要处理的情况。

那么,异常值处理非常非常重要,因为异常值可能会显著影响模型的准确性,使结果偏离真实情况。处理异常值可以提升模型的鲁棒性,让模型在处理不同数据集时表现更稳定。此外,适当的异常值处理有助于防止过拟合,提高模型的泛化能力。

今儿和大家聊的10种方式有:

  • 删除异常值
  • 分箱法处理
  • 均值填充
  • 中位数填充
  • 剪尾法
  • 标准差法
  • Z-Score 标准化
  • IQR(四分位距)法
  • 箱线图分析
  • 聚类法

如果需要 本文PDF版本 的同学,文末获取~

另外,文末有总结性的干货~

一起来看下具体细化内容~

1. 删除异常值

删除异常值是最直接的一种方法。通常应用在异常值数量少、对整体数据统计特性影响较大时。删除操作将直接从数据集中移除被判定为异常值的观测值,因此在样本量较大、异常值影响较小的情况下使用会更合适。

应用情景

  • 异常值是显然错误或噪声,如传感器故障数据。
  • 数据集足够大,删除少量异常值对模型效果影响较小。

Python案例

我会生成一个包含异常值的二维数据集,通过这些图形展示如何识别并删除异常值。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

# 设置随机种子,生成可复现的数据
np.random.seed(42)

# 创建虚拟数据集
# 正常分布的数据
data_normal_x = np.random.normal(loc=50, scale=10, size=200)
data_normal_y = np.random.normal(loc=50, scale=10, size=200)

# 添加一些异常值
data_outliers_x = np.random.uniform(low=80, high=100, size=10)
data_outliers_y = np.random.uniform(low=80, high=100, size=10)

# 合并正常数据和异常数据
x = np.concatenate((data_normal_x, data_outliers_x))
y = np.concatenate((data_normal_y, data_outliers_y))

# 创建DataFrame
df = pd.DataFrame({'X': x, 'Y': y})

# 设置图形风格
sns.set(style="whitegrid")
plt.figure(figsize=(168))

# 1. 子图1:散点图
plt.subplot(221)
plt.scatter(df['X'], df['Y'], color='dodgerblue', edgecolor='k', s=50, alpha=0.7)
plt.title("Scatter Plot with Outliers")
plt.xlabel("X values")
plt.ylabel("Y values")

# 2. 子图2:箱线图(查看分布及异常值)
plt.subplot(222)
sns.boxplot(data=df, palette="bright")
plt.title("Box Plot for X and Y")
plt.xlabel("Variables")
plt.ylabel("Values")

# 3. 子图3:X变量的直方图
plt.subplot(223)
plt.hist(df['X'], bins=20, color='orange', edgecolor='black', alpha=0.7)
plt.title("Histogram of X")
plt.xlabel("X values")
plt.ylabel("Frequency")

# 4. 子图4:Y变量的直方图
plt.subplot(224)
plt.hist(df['Y'], bins=20, color='purple', edgecolor='black', alpha=0.7)
plt.title("Histogram of Y")
plt.xlabel("Y values")
plt.ylabel("Frequency")

plt.tight_layout()
plt.show()

# 异常值检测:根据箱线图的四分位间距(IQR)方法去除异常值
# 计算四分位数和IQR
Q1_X, Q3_X = df['X'].quantile(0.25), df['X'].quantile(0.75)
IQR_X = Q3_X - Q1_X
lower_bound_X = Q1_X - 1.5 * IQR_X
upper_bound_X = Q3_X + 1.5 * IQR_X

Q1_Y, Q3_Y = df['Y'].quantile(0.25), df['Y'].quantile(0.75)
IQR_Y = Q3_Y - Q1_Y
lower_bound_Y = Q1_Y - 1.5 * IQR_Y
upper_bound_Y = Q3_Y + 1.5 * IQR_Y

# 删除异常值
df_cleaned = df[(df['X'] >= lower_bound_X) & (df['X'] <= upper_bound_X) & 
                (df['Y'] >= lower_bound_Y) & (df['Y'] <= upper_bound_Y)]

# 绘制清理后的图形
plt.figure(figsize=(168))

# 1. 子图1:去除异常值后的散点图
plt.subplot(221)
plt.scatter(df_cleaned['X'], df_cleaned['Y'], color='dodgerblue', edgecolor='k', s=50, alpha=0.7)
plt.title("Scatter Plot without Outliers")
plt.xlabel("X values")
plt.ylabel("Y values")

# 2. 子图2:去除异常值后的箱线图
plt.subplot(222)
sns.boxplot(data=df_cleaned, palette="bright")
plt.title("Box Plot for X and Y (Outliers Removed)")
plt.xlabel("Variables")
plt.ylabel("Values")

# 3. 子图3:去除异常值后的X直方图
plt.subplot(223)
plt.hist(df_cleaned['X'], bins=20, color='orange', edgecolor='black', alpha=0.7)
plt.title("Histogram of X (Outliers Removed)")
plt.xlabel("X values")
plt.ylabel("Frequency")

# 4. 子图4:去除异常值后的Y直方图
plt.subplot(224)
plt.hist(df_cleaned['Y'], bins=20, color='purple', edgecolor='black', alpha=0.7)
plt.title("Histogram of Y (Outliers Removed)")
plt.xlabel("Y values")
plt.ylabel("Frequency")

plt.tight_layout()
plt.show()

  1. 散点图:用于直观展示数据的分布情况。散点图可以帮助我们看到两个变量之间的关系以及是否存在偏离数据趋势的异常点。在初始数据的散点图中,右上角的点为异常点,而清理后的散点图中这些异常点已经被删除。

  2. 箱线图:箱线图能够显示数据的分布范围及中位数,同时将超过1.5倍四分位间距(IQR)的数据标记为异常值(离群值)。清理后的箱线图中,这些离群值已消失,数据分布更加集中。

  3. 直方图:直方图用于展示单一变量的频率分布,可以清楚地看到数据的集中趋势。在包含异常值的直方图中,右侧尾部有较少频次的数据(异常值),而清理后直方图更符合正态分布,异常值的干扰已去除。

2. 分箱法处理

分箱法将连续的数值变量划分为离散的区间,将每个观测值归入相应区间。这可以将极端值转化为分箱区间中的数值,从而减少异常值对模型的影响。常见方法包括等宽分箱和等频分箱。

核心点

假设我们有数据集 ,我们可以按以下方法进行分箱处理:

1. 等宽分箱(Equal-Width Binning):

将数据集按固定宽度划分为  个区间。每个区间宽度为:

其中  为分箱数。每个数值  归入相应区间。

2. 等频分箱(Equal-Frequency Binning):

将数据分为  个频数相等的区间。假设每箱的大小为 ,则按每箱的排序位置决定区间范围。

应用情景

  • 连续数值中存在极端值,但并不需要删除。
  • 适用于决策树、朴素贝叶斯等对离散数据更敏感的模型。

Python案例

使用虚拟数据集演示分箱方法如何用于处理异常值,并在图中展示不同的分箱结果以及数据的分布变化。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from scipy.stats import zscore, norm, probplot

# 设置随机种子以保证结果可复现
np.random.seed(42)

# 创建虚拟数据集
data = np.random.normal(loc=50, scale=10, size=1000)  # 正态分布数据
outliers = np.random.normal(loc=100, scale=5, size=50)  # 添加一些异常值
data_with_outliers = np.concatenate([data, outliers])

# 创建 DataFrame
df = pd.DataFrame(data_with_outliers, columns=['Value'])

# 分箱处理 - 例如按四分位数进行分箱
bins = [df['Value'].quantile(0), df['Value'].quantile(0.25), 
        df['Value'].quantile(0.5), df['Value'].quantile(0.75), df['Value'].quantile(1)]
labels = ['Q1''Q2''Q3''Q4']
df['Binned'] = pd.cut(df['Value'], bins=bins, labels=labels, include_lowest=True)

# 计算 z-score 以标识异常值
df['Z-Score'] = zscore(df['Value'])
df['Is_Outlier'] = df['Z-Score'].abs() > 3  # 绝对值大于3视为异常值

# 绘图
fig, axs = plt.subplots(13, figsize=(186))
fig.suptitle("Data Analysis of Outlier Treatment by Binning", fontsize=16)

# 箱线图 - 观察异常值
sns.boxplot(x='Binned', y='Value', data=df, ax=axs[0], palette='Set2')
axs[0].set_title("Boxplot of Binned Data")
axs[0].set_xlabel("Binned")
axs[0].set_ylabel("Value")

# 直方图 - 分箱前后数据分布
sns.histplot(df['Value'], bins=30, color="blue", alpha=0.6, label="Original Data", kde=True, ax=axs[1])
sns.histplot(df[~df['Is_Outlier']]['Value'], bins=30, color="red", alpha=0.6, label="After Binning", kde=True, ax=axs[1])
axs[1].set_title("Histogram of Data Distribution Before and After Binning")
axs[1].set_xlabel("Value")
axs[1].set_ylabel("Frequency")
axs[1].legend()

# Q-Q 图 - 检查分布状态
probplot(df['Value'], dist="norm", plot=axs[2])
axs[2].set_title("Q-Q Plot - Original Data Distribution")
probplot(df[~df['Is_Outlier']]['Value'], dist="norm", plot=axs[2])
axs[2].set_title("Q-Q Plot - Distribution After Binning")

plt.tight_layout(rect=[00.0310.95])
plt.show()

  1. 箱线图:箱线图可以清晰地展示出不同分箱中数据的分布,尤其是其中的异常值。分箱后,某些箱可能不再包含显著的异常值,能够帮助观察分箱后的数据处理效果。

  2. 直方图:原始数据和分箱后数据的频数分布对比,帮助我们直观地观察到分箱前后的数据分布差异,是否有异常值得到有效处理。

  3. Q-Q 图:检查数据是否符合正态分布,验证分箱法是否改善了数据的分布结构。

3. 均值填充

用该特征的均值替代异常值,避免异常值对模型的影响。此方法适用于数据呈正态分布或异常值数量少的情况。

核心点

对于特征  的均值为:

判定异常值后,将其替换为均值 

应用情景

  • 适用于正态分布数据。
  • 异常值对均值影响较小时效果更佳。

Python案例

用一个虚拟数据集,模拟一组包含异常值和缺失值的特征数据。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

# 生成随机数据集,包含异常值和缺失值
np.random.seed(0)
data = np.random.normal(5010100)  # 生成均值为50,标准差为10的正态分布数据
data[np.random.randint(010010)] = np.nan  # 随机插入缺失值
data = np.append(data, [200210220])  # 添加异常值

# 创建DataFrame
df = pd.DataFrame({'feature': data})

# 异常处理:使用中位数填充缺失值
median_value = df['feature'].median()
df['feature_median_filled'] = df['feature'].fillna(median_value)

# 设置图像大小和颜色主题
plt.figure(figsize=(1410))
sns.set_palette("bright")

# 子图1:处理前数据分布(带异常值和缺失值)
plt.subplot(221)
sns.histplot(df['feature'], kde=True, color="blue", bins=20, edgecolor="black")
plt.title("Original Data Distribution (with Outliers & NaNs)")

# 子图2:处理后数据分布(中位数填充后)
plt.subplot(222)
sns.histplot(df['feature_median_filled'], kde=True, color="green", bins=20, edgecolor="black")
plt.title("Data Distribution After Median Imputation")

# 子图3:处理前箱线图(显示异常值)
plt.subplot(223)
sns.boxplot(x=df['feature'], color="orange")
plt.title("Box Plot Before Median Imputation")

# 子图4:处理后箱线图(中位数填充后)
plt.subplot(224)
sns.boxplot(x=df['feature_median_filled'], color="purple")
plt.title("Box Plot After Median Imputation")

plt.tight_layout()
plt.show()

  1. 数据分布直方图:原始数据分布图可以让我们直观地看到数据中是否存在偏差、缺失值和异常值。填充后的数据分布图有助于观察填充方法对数据分布的影响。

  2. 箱线图:箱线图展示了数据的四分位数和异常值。通过对比处理前后的箱线图,我们可以清楚地看到填充中位数对数据中的异常值和缺失值的处理效果。

4. 中位数填充

用中位数替代异常值,对非对称分布的数据更为稳定,不易受极端值影响。

核心点

特征  的中位数  是排序后的中间值。当  为奇数时:

当  为偶数时:

判定异常值后,将其替换为 

应用情景

  • 非对称分布数据。
  • 异常值多且影响均值的情况下更合适。

Python案例

使用 IQR(四分位距) 方法检测异常值,然后用 中位数 进行填充,并通过绘图展示处理前后的数据变化。这些图包括箱线图和分布图,以便大家更好地理解异常值的检测和处理的效果。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

# 设置随机种子和样式
np.random.seed(10)
sns.set(style="whitegrid")

# 生成虚拟数据
data = np.random.normal(5010100)  # 均值50,标准差10,样本数100
data = np.append(data, [120130140])  # 添加几个异常值
df = pd.DataFrame(data, columns=['Value'])

# 计算四分位数和IQR(四分位距)
Q1 = df['Value'].quantile(0.25)
Q3 = df['Value'].quantile(0.75)
IQR = Q3 - Q1

# 设置异常值范围
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR

# 标记异常值
df['Outlier'] = (df['Value'] < lower_bound) | (df['Value'] > upper_bound)

# 中位数填充
median_value = df['Value'].median()
df['Value_Filled'] = np.where(df['Outlier'], median_value, df['Value'])

# 创建图形
plt.figure(figsize=(128))

# 子图1:原始数据的箱线图
plt.subplot(221)
sns.boxplot(y='Value', data=df, palette="Pastel1")
plt.title("Boxplot of Original Data (with Outliers)", color='blue')

# 子图2:填充后的箱线图
plt.subplot(222)
sns.boxplot(y='Value_Filled', data=df, palette="Pastel2")
plt.title("Boxplot After Median Imputation", color='blue')

# 子图3:原始数据的直方图
plt.subplot(223)
sns.histplot(df['Value'], bins=15, color="coral", kde=True)
plt.title("Histogram of Original Data", color='blue')

# 子图4:填充后的直方图
plt.subplot(224)
sns.histplot(df['Value_Filled'], bins=15, color="skyblue", kde=True)
plt.title("Histogram After Median Imputation", color='blue')

plt.tight_layout()
plt.show()

  1. 子图1 和 子图2(箱线图):通过箱线图可以直观地看到数据的离群点,在原始数据的箱线图中(子图1),一些明显偏离的点(异常值)位于上下四分位的范围之外。在填充后的箱线图中(子图2),填充后的数据没有了离群点,整体分布更加集中。

  2. 子图3 和 子图4(直方图):直方图用于显示原始数据和填充数据的分布情况。在原始数据的直方图中(子图3),我们可以看到一些偏离中心的点,导致分布的偏态。经过中位数填充后(子图4),填充后的数据更符合常态分布,反映出填充对分布的影响。

5. 剪尾法

对异常值进行截断,即将数据中低于或高于一定分位数的值替换为分位数值。剪尾法减少异常值影响,但保留了数据样本。

核心点

假设上下截断比例分别为  和 ,则上限和下限分别为数据的 -分位数和 -分位数。对于数据 

其中  表示 -分位数。

应用情景

  • 异常值数量较多,但不希望完全删除。
  • 数据分布中存在特定的极端值。

Python案例

虚拟一个数据集,其中包含一些异常值,这些异常值需要通过剪尾法去除。我们使用剪尾法处理数据,最后通过多个分析图表来看看剪尾法的效果。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from scipy.stats import trim_mean

# 生成虚拟数据
np.random.seed(0)
data_normal = np.random.normal(loc=50, scale=5, size=500)  # 正态分布的数据
data_outliers = np.random.normal(loc=100, scale=5, size=20)  # 异常值数据
data = np.concatenate([data_normal, data_outliers])  # 合并数据

# 创建DataFrame
df = pd.DataFrame(data, columns=['Value'])

# 应用剪尾法(去掉上下5%的数据)
trim_ratio = 0.05
lower_limit = np.percentile(data, trim_ratio * 100)
upper_limit = np.percentile(data, (1 - trim_ratio) * 100)
df_trimmed = df[(df['Value'] >= lower_limit) & (df['Value'] <= upper_limit)]

# 设置图形外观
plt.figure(figsize=(1410))
sns.set_palette("bright")

# 子图 1:箱线图(Boxplot),显示剪尾前后的数据分布和异常值情况
plt.subplot(221)
sns.boxplot(data=[df['Value'], df_trimmed['Value']], palette="Set2")
plt.xticks([01], ['Original''Trimmed'])
plt.title("Boxplot of Original and Trimmed Data")

# 子图 2:直方图(Histogram),比较剪尾前后数据的分布
plt.subplot(222)
sns.histplot(df['Value'], bins=30, color="skyblue", kde=True, label="Original", alpha=0.6)
sns.histplot(df_trimmed['Value'], bins=30, color="orange", kde=True, label="Trimmed", alpha=0.6)
plt.legend()
plt.title("Histogram of Original and Trimmed Data")

# 子图 3:散点图(Scatter Plot),查看数据的分布情况,标记异常值
plt.subplot(223)
plt.scatter(range(len(df)), df['Value'], color="skyblue", label="Original", alpha=0.6)
plt.scatter(df_trimmed.index, df_trimmed['Value'], color="orange", label="Trimmed", alpha=0.6)
plt.xlabel("Index")
plt.ylabel("Value")
plt.legend()
plt.title("Scatter Plot of Original and Trimmed Data")

# 子图 4:密度图(Density Plot),显示数据的密度分布变化
plt.subplot(224)
sns.kdeplot(df['Value'], color="skyblue", fill=True, label="Original", alpha=0.4)
sns.kdeplot(df_trimmed['Value'], color="orange", fill=True, label="Trimmed", alpha=0.4)
plt.legend()
plt.title("Density Plot of Original and Trimmed Data")

# 显示图形
plt.tight_layout()
plt.show()

  1. 箱线图:显示原始数据和经过剪尾处理的数据的分布范围和异常值情况。可以看到剪尾后,箱线图中高于上限和低于下限的异常值已经被去除。
  2. 直方图:通过直方图展示数据的分布情况,并在原始数据和剪尾数据上叠加了密度估计曲线。可以直观观察剪尾法对数据分布形状的影响,特别是去除异常值后,数据在尾部的分布是否更集中。
  3. 散点图:通过散点图展示原始数据和剪尾数据的分布,标记异常值的具体位置,便于观察哪些数据被剪尾法去除。
  4. 密度曲线图:帮助确认数据分布的平滑变化。剪尾处理后的数据在密度曲线上没有明显的尾部异常值,可以验证剪尾法的效果。

6. 标准差法

对于正态分布数据,根据标准差的范围判断异常值。通常认为在均值两倍标准差以外的数据点可能是异常值。

核心点

假设数据均值为 ,标准差为 ,则设定一个阈值 ,通常  或 。判定异常值的条件为:

应用情景

  • 数据接近正态分布。
  • 适合于大样本数据。

Python案例

标准差法是一种简单而有效的异常检测方法,通过设置一个阈值来识别偏离平均值太远的数据点。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

# 设置随机种子,生成虚拟数据
np.random.seed(42)
# 正常数据分布
advertising_costs = np.random.normal(50050100)  # 广告费用
sales = np.random.normal(2000300100)            # 销售额

# 增加异常点
advertising_costs = np.append(advertising_costs, [100011001200])
sales = np.append(sales, [500060007000])

# 组合数据到DataFrame
data = pd.DataFrame({'Advertising Costs': advertising_costs, 'Sales': sales})

# 标准差法检测异常
mean_sales = np.mean(data['Sales'])
std_sales = np.std(data['Sales'])
threshold = 3  # 三倍标准差为异常判断阈值

# 标识异常值
data['Anomaly'] = np.abs(data['Sales'] - mean_sales) > threshold * std_sales

# 绘图
plt.figure(figsize=(1610))

# 1. 散点图:展示广告费用和销售额的关系及异常值
plt.subplot(221)
plt.scatter(data['Advertising Costs'], data['Sales'], c=data['Anomaly'], cmap='coolwarm', edgecolors='k')
plt.title('Scatter Plot of Sales vs Advertising Costs')
plt.xlabel('Advertising Costs')
plt.ylabel('Sales')
plt.colorbar(label='Anomaly (1=True, 0=False)')

# 2. 箱线图:展示销售额分布及其异常
plt.subplot(222)
sns.boxplot(x=data['Sales'], palette='viridis')
plt.title('Box Plot of Sales')
plt.xlabel('Sales')

# 3. 直方图:展示销售额的分布,突出异常区域
plt.subplot(223)
sns.histplot(data['Sales'], bins=15, kde=True, color='dodgerblue')
plt.axvline(mean_sales + threshold * std_sales, color='red', linestyle='--', label='3 Standard Deviations')
plt.axvline(mean_sales - threshold * std_sales, color='red', linestyle='--')
plt.title('Histogram of Sales with Anomaly Threshold')
plt.xlabel('Sales')
plt.legend()

# 4. 热力图:广告费用和销售额的密度分布,显示异常值的密度差异
plt.subplot(224)
sns.kdeplot(x='Advertising Costs', y='Sales', data=data, cmap='coolwarm', fill=True)
plt.scatter(data[data['Anomaly']]['Advertising Costs'], data[data['Anomaly']]['Sales'], color='red', edgecolor='k', label='Anomalies')
plt.title('Heatmap of Sales vs Advertising Costs')
plt.xlabel('Advertising Costs')
plt.ylabel('Sales')
plt.legend()

plt.tight_layout()
plt.show()

  • 散点图显示广告费用和销售额之间的关系,突出异常值在二维平面上的位置。
  • 箱线图帮助我们快速看到异常值相对于四分位数分布的偏差。
  • 直方图显示销售额的分布情况和检测到的异常值,使得异常值在总体分布中的位置更加清晰。
  • 热力图通过密度显示集中区域,突出异常值远离密集区域的情况。

7. Z-Score 标准化

利用 Z-Score 将数据标准化,通过设定阈值判定异常值。该方法基于正态分布假设。

核心点

Z-Score 计算公式为:

如果 (如  或 ),则  为异常值。

应用情景

  • 数据接近正态分布。
  • 异常值较明显。

Python案例

使用Z-Score标准化可以帮助我们识别和处理异常值。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from scipy.stats import zscore

# 设置随机种子
np.random.seed(42)

# 生成虚拟数据集,包括一些明显的异常值
data_size = 100
normal_data = np.random.normal(loc=50, scale=10, size=(data_size, 2))
outliers = np.array([[100100], [12085], [90110]])  # 添加一些异常值
data = np.vstack([normal_data, outliers])
df = pd.DataFrame(data, columns=['Feature1''Feature2'])

# 使用Z-Score标准化
df['Z_Score_Feature1'] = zscore(df['Feature1'])
df['Z_Score_Feature2'] = zscore(df['Feature2'])

# 定义异常值阈值
threshold = 3
df['Outlier'] = (np.abs(df['Z_Score_Feature1']) > threshold) | (np.abs(df['Z_Score_Feature2']) > threshold)

# 设置图形风格
plt.figure(figsize=(1410))
plt.suptitle("Outlier Detection Analysis (Z-Score Normalization)", fontsize=16)

# 1. 散点图
plt.subplot(221)
sns.scatterplot(data=df, x='Feature1', y='Feature2', hue='Outlier', palette={False'blue'True'red'}, s=100)
plt.title("Scatter Plot: Marked Outliers")
plt.xlabel("Feature 1")
plt.ylabel("Feature 2")

# 2. 直方图
plt.subplot(222)
sns.histplot(df['Feature1'], bins=20, color='skyblue', kde=True, label="Feature 1", alpha=0.7)
sns.histplot(df['Feature2'], bins=20, color='orange', kde=True, label="Feature 2", alpha=0.7)
plt.legend()
plt.title("Histogram: Feature Distribution")
plt.xlabel("Feature Value")
plt.ylabel("Frequency")

# 3. 箱线图
plt.subplot(223)
sns.boxplot(data=df[['Feature1''Feature2']], palette="Set2")
plt.title("Box Plot: Outlier Detection")
plt.xlabel("Feature")
plt.ylabel("Value")

# 4. Z-Score分布图
plt.subplot(224)
sns.histplot(df['Z_Score_Feature1'], bins=20, color='green', kde=True, label="Z-Score Feature 1", alpha=0.6)
sns.histplot(df['Z_Score_Feature2'], bins=20, color='purple', kde=True, label="Z-Score Feature 2", alpha=0.6)
plt.axvline(threshold, color='red', linestyle='--', label="Outlier Threshold")
plt.axvline(-threshold, color='red', linestyle='--')
plt.legend()
plt.title("Z-Score Distribution")
plt.xlabel("Z-Score Value")
plt.ylabel("Frequency")

plt.tight_layout(rect=[00.0310.95])
plt.show()

  • 散点图:帮助我们可视化两个特征之间的关系,并通过颜色标识出异常值,方便我们识别哪些点在Z-Score检测中被判定为异常。
  • 直方图:观察每个特征的值分布,异常值往往会出现在分布的两端,直方图能够很好地展现这一点。
  • 箱线图:清晰地展示出特征的分布、四分位数及离群点。异常值会在箱线图上表现为超出“胡须”范围的点。
  • Z-Score分布图:通过Z-Score分布情况判断出有多少数据点超过了定义的异常阈值。这有助于从标准化视角查看异常值的分布。

8. IQR(四分位距)法

通过四分位数判定异常值。数据位于上下四分位距之外的范围则为异常值。

核心点

计算上四分位数  和下四分位数 ,四分位距为:

上限为 ,下限为 。若数据  超过此范围则为异常值。

应用情景

  • 数据不符合正态分布。
  • 非对称分布效果好。

Python案例

使用四分位距(IQR)方法检测数据中的异常值,并对数据进行分析。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

# 设置随机种子
np.random.seed(42)

# 生成虚拟数据集
# 正常数据:符合正态分布
data_x = np.random.normal(5010200)
data_y = np.random.normal(5010200)

# 异常数据:添加几个异常值
outliers_x = [120130140]
outliers_y = [120130140]

# 合并正常数据和异常数据
x = np.concatenate([data_x, outliers_x])
y = np.concatenate([data_y, outliers_y])

# 构建 DataFrame
df = pd.DataFrame({'X': x, 'Y': y})

# 使用 IQR 方法检测异常值
Q1_x = df['X'].quantile(0.25)
Q3_x = df['X'].quantile(0.75)
IQR_x = Q3_x - Q1_x

Q1_y = df['Y'].quantile(0.25)
Q3_y = df['Y'].quantile(0.75)
IQR_y = Q3_y - Q1_y

# 找出 X 和 Y 方向的异常值
outliers_x = df[(df['X'] < Q1_x - 1.5 * IQR_x) | (df['X'] > Q3_x + 1.5 * IQR_x)]
outliers_y = df[(df['Y'] < Q1_y - 1.5 * IQR_y) | (df['Y'] > Q3_y + 1.5 * IQR_y)]

# 可视化:箱线图、散点图和直方图
plt.figure(figsize=(155))

# 第一个子图:箱线图
plt.subplot(131)
sns.boxplot(x=df['X'], color='skyblue')
sns.boxplot(y=df['Y'], color='salmon')
plt.title('Box Plot (IQR) - Detecting Outliers')

# 第二个子图:散点图
plt.subplot(132)
sns.scatterplot(x='X', y='Y', data=df, color='blue', label='Normal Data')
sns.scatterplot(x=outliers_x['X'], y=outliers_x['Y'], color='red', label='Outliers')
plt.title('Scatter Plot - Data Distribution & Outliers')
plt.legend()

# 第三个子图:直方图
plt.subplot(133)
sns.histplot(df['X'], bins=20, color='lightgreen', alpha=0.6, kde=True, label='X Distribution')
sns.histplot(df['Y'], bins=20, color='purple', alpha=0.6, kde=True, label='Y Distribution')
plt.title('Histogram - X and Y Distributions')
plt.legend()

plt.tight_layout()
plt.show()

  1. 数据生成:首先,我们生成符合正态分布的正常数据,并添加一些离群点。这样可以帮助展示正常数据与异常数据的区别。

  2. IQR异常检测:我们通过计算数据的四分位数(Q1和Q3)以及IQR,检测出在X和Y方向上的异常值。

  3. 可视化:在一个图表中展示了箱线图、散点图和直方图。箱线图用来直观显示四分位范围和异常点;散点图显示数据在二维空间中的分布情况;直方图展示X和Y的分布频率。

9. 箱线图分析

利用箱线图直观显示数据分布,通过箱线图的“胡须”长度来判断异常值,常结合 IQR 法使用。

核心点

箱线图的上下限基于 IQR 计算,分别为:

应用情景

  • 可视化需求。
  • 数据规模中等,便于分析。

Python案例

我们模拟一个包含年龄、收入和工作年限的虚拟数据集,分析该数据集中的异常值。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

# 设置随机种子
np.random.seed(42)

# 生成虚拟数据
n = 100
age = np.random.normal(305, n)         # 年龄,均值30,标准差5
income = np.random.normal(5000012000, n)  # 收入,均值50000,标准差12000
years_experience = np.random.normal(52, n) # 工作年限,均值5,标准差2

# 引入一些离群值
age = np.append(age, [808590])
income = np.append(income, [200000220000250000])
years_experience = np.append(years_experience, [202225])

# 构建DataFrame
data = pd.DataFrame({
    'Age': age,
    'Income': income,
    'YearsExperience': years_experience
})

# 画图
fig, axes = plt.subplots(22, figsize=(1510), constrained_layout=True)
fig.suptitle('Outlier Analysis - Age, Income, and Years of Experience', fontsize=16)

# 箱线图 - 用于查看各变量的异常值
sns.boxplot(data=data[['Age''Income''YearsExperience']], palette="Set2", ax=axes[00])
axes[00].set_title("Box Plot of Age, Income, and Years of Experience")

# 年龄 vs 收入的散点图 - 观察年龄和收入的关系以及异常值
sns.scatterplot(x='Age', y='Income', data=data, hue='Income', palette="cool", ax=axes[01])
axes[01].set_title("Scatter Plot: Age vs Income")
axes[01].set_xlabel("Age")
axes[01].set_ylabel("Income")

# 收入 vs 工作年限的散点图 - 观察收入和工作年限的关系以及异常值
sns.scatterplot(x='YearsExperience', y='Income', data=data, hue='Income', palette="viridis", ax=axes[10])
axes[10].set_title("Scatter Plot: Years of Experience vs Income")
axes[10].set_xlabel("Years of Experience")
axes[10].set_ylabel("Income")

# 年龄 vs 工作年限的散点图 - 观察年龄和工作年限的关系以及异常值
sns.scatterplot(x='Age', y='YearsExperience', data=data, hue='YearsExperience', palette="plasma", ax=axes[11])
axes[11].set_title("Scatter Plot: Age vs Years of Experience")
axes[11].set_xlabel("Age")
axes[11].set_ylabel("Years of Experience")

# 显示图例和图形
plt.show()

  1. 箱线图:用于显示每个变量的分布情况以及离群值。通过箱线图,能直观地看到哪些值远离主要数据分布(箱体),从而识别异常值。

  2. 散点图 - 年龄 vs 收入:通过将年龄和收入放在一个二维平面上,我们可以分析年龄和收入之间的关系,同时识别那些在这两个维度上异常的点。颜色根据收入的不同而变化,更直观地展示了收入的异常情况。

  3. 散点图 - 收入 vs 工作年限:在此图中,观察收入和工作年限的关系,异常值会在极端高收入或高年限的情况下出现。通过颜色区分收入的高低,有助于进一步识别高收入异常点。

  4. 散点图 - 年龄 vs 工作年限:用于分析年龄和工作年限的关系。异常值在年龄和工作年限都较高时容易出现。通过颜色区分工作年限的长短,可以快速定位那些有较高工作年限但年龄偏低的异常点。

10. 聚类法

利用聚类算法将数据分组,判断离群点是否为异常值。DBSCAN(Density-Based Spatial Clustering of Applications with Noise)是常用的基于密度的聚类算法,能有效识别密度低的异常点。

核心点

DBSCAN 聚类基于两个参数:半径  和最小点数 。对数据点 ,满足以下条件:

  1. 若  的  范围内至少有  个数据点,则属于密度可达区域。
  2. 若数据点无法属于密度聚类区域,则为噪声点(即异常值)。

应用情景

  • 高维数据。
  • 异常值无法用简单统计规则判断。

Python案例

使用聚类分析法来检测数据中的异常值,具体方法是使用 DBSCAN(密度聚类方法)识别出异常点(或孤立点),并通过多种图形来可视化数据特征和聚类结果。

import numpy as np
import matplotlib.pyplot as plt
from sklearn.cluster import DBSCAN
from sklearn.datasets import make_blobs
import seaborn as sns

# 设置随机种子
np.random.seed(42)

# 生成虚拟数据集
n_samples = 500
centers = [[-55], [00], [5-5]]
X, _ = make_blobs(n_samples=n_samples, centers=centers, cluster_std=1.0)

# 添加一些异常点
n_outliers = 50
outliers = np.random.uniform(low=-10, high=10, size=(n_outliers, 2))
X = np.vstack([X, outliers])

# 使用DBSCAN进行聚类,eps为邻域半径,min_samples为核心点的最小邻居数
dbscan = DBSCAN(eps=1.5, min_samples=5)
labels = dbscan.fit_predict(X)

# 获取聚类标签和异常点标签
core_samples_mask = np.zeros_like(labels, dtype=bool)
core_samples_mask[dbscan.core_sample_indices_] = True
unique_labels = set(labels)

# 绘图
fig, axs = plt.subplots(13, figsize=(186), dpi=100)

# 1. 原始数据分布图
axs[0].scatter(X[:, 0], X[:, 1], c='gray', s=10, alpha=0.7)
axs[0].set_title("Original Data Distribution")
axs[0].set_xlabel("Feature 1")
axs[0].set_ylabel("Feature 2")

# 2. 聚类结果图
colors = sns.color_palette("bright", len(unique_labels))
for k, col in zip(unique_labels, colors):
    if k == -1:
        # 标记异常点为黑色
        col = 'black'

    class_member_mask = (labels == k)
    xy = X[class_member_mask & core_samples_mask]
    axs[1].scatter(xy[:, 0], xy[:, 1], s=50, color=col, label=f'Cluster {k}' if k != -1 else 'Outliers', alpha=0.7)

    xy = X[class_member_mask & ~core_samples_mask]
    axs[1].scatter(xy[:, 0], xy[:, 1], s=10, color=col, alpha=0.3)

axs[1].legend(loc='upper right')
axs[1].set_title("DBSCAN Clustering Results")
axs[1].set_xlabel("Feature 1")
axs[1].set_ylabel("Feature 2")

# 3. 聚类簇中心及密度图
sns.kdeplot(x=X[:, 0], y=X[:, 1], ax=axs[2], shade=True, cmap="plasma", alpha=0.5)
for k, col in zip(unique_labels, colors):
    if k == -1:
        continue  # 跳过异常点
    class_member_mask = (labels == k)
    xy = X[class_member_mask & core_samples_mask]
    centroid = xy.mean(axis=0)
    axs[2].scatter(centroid[0], centroid[1], s=100, color=col, edgecolor='black', marker='X')

axs[2].set_title("Cluster Centers and Density Distribution")
axs[2].set_xlabel("Feature 1")
axs[2].set_ylabel("Feature 2")

plt.tight_layout()
plt.show()
  1. 数据生成:生成多个聚类簇的二维数据,并加入一些随机分布的点作为异常点。
  2. DBSCAN聚类:使用DBSCAN算法对数据进行聚类,DBSCAN通过密度判断,可以直接将密度不足的孤立点视为异常点。

  • 原始数据分布图:直观展示了原始数据集的分布情况,可以初步看到主要聚类簇的位置和一些离散的异常点。
  • 聚类结果图:通过颜色区分不同的聚类簇,黑色点表示异常点(即没有归属于任何簇的点),可以清楚地看出DBSCAN识别的异常点位置。
  • 聚类簇中心及密度图:展示了聚类簇的中心点,并通过核密度估计(KDE)展示数据密度分布,使我们能够更加清楚地看出各簇间的密度差异,离群点位置显著远离密集区域。

最后

以上就是今天所有的内容了。
获取本文PDF,点击名片回复「十大算法模型」即可~
 
另外, 我们整理了机器学习算法册子,总共16大块的内容,124个问题的总结点击领取!
如果对你来说比较有用,记得点赞、转发,收藏起来慢慢学习~
下期会有更多干货等着你!~

Python和机器学习初学者
Python和机器学习分享,只写干货,一起学习~
 最新文章