使用K-Means聚类算法检测离群点

1. 前言

数据集为电商真实订单数据经过处理后的RFM数据,来源为本人的文章 《利用Python实现电商用户价值分层(RFM模型与基于RFM的K-Means聚类算法)》 中第五小结聚类中的k_data。在该文章中并没有对离群点进行检测,所以在本文中,将使用K-Means检测其离群点。

2.代码

2.1 数据转换

载入数据

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
plt.rcParams['font.sans-serif'] = ['SimHei'] 
plt.rcParams['axes.unicode_minus'] = False  
plt.style.use('fivethirtyeight')
import warnings
warnings.filterwarnings("ignore")

data = pd.read_csv("rfm.csv", index_col = 'CustomerID')
data.head()

在这里插入图片描述

# 查看偏度、峰度
pd.DataFrame([i for i in zip(data.columns, data.skew(), data.kurt())], 
             columns=['特征', '偏度', '峰度'])

在这里插入图片描述

  • 可见数据并不满足正态分布,所以接下来使用box-cox将其转换成正态分布

boxcox转换

# R中存在0值,进行box-cox转换时存在0值会将其转变成无穷大,所以将所有的值加上一个很小的数全部变成正数
data_bc = data.copy()
data_bc.R = data_bc.R + 0.0001

# boxcox转换
for i in data_bc.columns:  # 自动计算λ
    data_bc[i], _ = stats.boxcox(data_bc[i])

# 查看转换之后的偏度、峰度
pd.DataFrame([i for i in zip(data_bc.columns, data_bc.skew(), data_bc.kurt())], 
             columns=['特征', '偏度', '峰度'])

在这里插入图片描述

  • 转换后的数据基本满足正态分布

数据标准化

from sklearn.preprocessing import scale
std_scale_data = scale(data_bc) # 标准化
std_data = pd.DataFrame(std_scale_data, columns = ['R', 'F', 'M'], index = data_bc.index)
std_data.head()

在这里插入图片描述
由于只有三个特征,所以无需将其降维,接下来将直接使用聚类算法决定类别

2.2 确定聚类类别

使用肘部法则确定聚类类别数

from sklearn.cluster import KMeans
# 选择K的范围 ,遍历每个值进行评估
inertia_list = []
for k in range(1,10):
    model = KMeans(n_clusters = k, max_iter = 500, random_state = 12)
    kmeans = model.fit(std_data)
    inertia_list.append(kmeans.inertia_)

# 绘图
fig,ax = plt.subplots(figsize=(8,6))    
ax.plot(range(1,10), inertia_list, '*-', linewidth=1)
ax.set_xlabel('k')
ax.set_ylabel("inertia_score") 
ax.set_title('inertia变化图')
plt.show()

在这里插入图片描述

  • 从上图可见,曲线在2处有明显的拐点,所以选择2类比较合适

2.3 聚类

聚类

k = 2
threshold = 3  # 阈值选择3
model = KMeans(n_clusters = k, max_iter = 500)  # 分为k类
model.fit(std_data)  # 开始聚类
std_data['label'] = model.labels_ # 将聚类后的类别标签生成label列

计算相对距离

relative_distance = []
for i in range(k):  # 逐一处理
    distance = std_data.query("label == @i")[['R', 'F', 'M']] - model.cluster_centers_[i]  # 计算各点至簇中心点的距离
    absolute_distance = distance.apply(np.linalg.norm, axis = 1)  # 求出绝对距离
    relative_distance.append(absolute_distance / absolute_distance.median())  # 求相对距离并添加
    
std_data['relative_distance'] = pd.concat(relative_distance)  # 合并

筛选出离群点

std_data['outlier_3'] = std_data.relative_distance.apply(lambda x: 1 if x > threshold else 0)

查看离群点的数量

std_data['outlier_3'].value_counts()

在这里插入图片描述

  • 一共检测出15个离群点

2.4 可视化

查看离群点的相对距离分布图

# 绘图查看离群点的相对距离
fig, ax = plt.subplots(figsize=(10,6))
colors = {0:'blue', 1:'red'}
ax.scatter(std_data.index, std_data.relative_distance, c=std_data.outlier_3.apply(lambda x: colors[x]))
ax.set_xlabel('客户ID')
ax.set_ylabel('相对距离')
plt.show()

在这里插入图片描述

  • 可以看到阈值选择为3的时候,大于3的离群点都已被选出

查看离群点在rfm三个纬度的分布图

# 查看三维分布图
from mpl_toolkits.mplot3d import Axes3D

fig = plt.figure(figsize=(12, 8), dpi=200)
ax = Axes3D(fig, rect = [0, 0, .95, 1], elev = 30, azim = -60)

ax1 = ax.scatter(std_data.query("outlier_3 == 0").R, std_data.query("outlier_3 == 0").F, 
                     std_data.query("outlier_3 == 0").M, edgecolor = 'k', color = 'r')
ax2 = ax.scatter(std_data.query("outlier_3 == 1").R, std_data.query("outlier_3 == 1").F, 
                     std_data.query("outlier_3 == 1").M, edgecolor = 'k', color = 'b')
ax.legend([ax1, ax2], ['正常点', '离群点'])
ax.invert_xaxis()
ax.set_xlabel('R')
ax.set_ylabel('F')
ax.set_zlabel('M')
ax.set_title('Outlier Scatter k={}'.format(threshold))
plt.show()

在这里插入图片描述

  • 观察上图可以看到,检测出的离群点基本都分布在边缘

查看其R、F、M数据

data['outlier_3'] = std_data['outlier_3']
data.query("outlier_3 == 1")

在这里插入图片描述

  • 可以看到,特征较极端的数据会被检测为离群值。

相对距离阈值k的选取直接影响了离群值的检测,我们查看k=2时的数据分布。

std_data['outlier_2'] = std_data.relative_distance.apply(lambda x: 1 if x > 2 else 0)

在这里插入图片描述

  • 更多边缘的特征被检测为离群值

查看在k=2与3之间的值

# 查看在k=2与3之间的值前20个
data.query("outlier_3 ==0 & outlier_2 ==1").head(20)

在这里插入图片描述
可以看到,相对于k=3时检测出的离群值,k=2时的某些值显得并不那么“极端”,看起来并不符合业务逻辑。

所以k的选取尤为关键,至于如何选取k值,则需要依业务场景而论。

参考书籍:Python数据分析与挖掘实战

### 欧氏聚类离群点检测 欧氏聚类是一种基于空间距离的聚类方法,适用于云数据中的对象分割。通过计算之间的欧几里得距离来形成簇,可以有效识别具有相似几何特性的集合。当应用于离群点检测时,可以通过设定阈值排除那些不属于任何显著簇的小规模集。 以下是使用 `open3D` 和 `numpy` 库实现欧氏聚类并进行离群点检测的一个具体示例: #### 数据准备 首先加载云数据,并将其转换为适合处理的形式。 ```python import open3d as o3d import numpy as np # 加载云文件(假设为 PLY 文件) pcd = o3d.io.read_point_cloud("point_cloud.ply") # 转换为 NumPy 数组以便进一步操作 points = np.asarray(pcd.points) ``` #### 参数设置 定义用于欧氏聚类的关键参数,包括邻域半径 (`eps`) 和最小数 (`min_points`)。 ```python # 设置欧氏聚类参数 eps = 0.02 # 邻域半径,单位取决于坐标系尺度 min_points = 10 # 构成一个簇所需的最少数 ``` #### 执行欧氏聚类 利用 Open3D 的内置功能执行聚类操作。 ```python with o3d.utility.VerbosityContextManager(o3d.utility.VerbosityLevel.Debug) as cm: labels = np.array(pcd.cluster_dbscan(eps=eps, min_points=min_points)) max_label = labels.max() print(f"Point cloud has {max_label + 1} clusters") # 输出总共有多少个簇 ``` #### 离群点提取 将未被分配到任何簇的视为离群点。 ```python outliers = points[labels == -1] # 创建一个新的云集表示这些离群点 outlier_pcd = o3d.geometry.PointCloud() outlier_pcd.points = o3d.utility.Vector3dVector(outliers) # 可视化原始云和离群点 o3d.visualization.draw_geometries([pcd]) o3d.visualization.draw_geometries([outlier_pcd], window_name="Outliers", width=800, height=600) ``` 上述代码实现了完整的流程:从读取云数据开始,经过参数配置、执行 DBSCAN 聚类算法,最终筛选出离群点并可视化结果[^5]。 --- ### 进一步优化建议 为了提高性能或适应特定需求,可考虑调整以下方面: - **动态调整参数**:针对不同场景下的云特性,自动调节 `eps` 和 `min_points` 的大小[^1]。 - **降噪预处理**:在正式应用前对输入数据实施滤波或其他形式的清理工作,减少噪声干扰的影响[^3]。 - **集成多种策略**:结合其他类型的异常检测技术(如统计学模型),增强系统的鲁棒性和准确性[^4]。 ---
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值