数据预处理

1.依赖准备

import os
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

首先导入相关依赖库,pandas/numpy用于数据处理,os用于改变路径,matplotlib用于数据可视化。

2.数据准备

os.chdir('D:\soft\PyCharm Community Edition 2024.1\study\pythonProject\exercise')
data = pd.read_excel('data_1\附件三_末端位置的相对距离.xlsx')

进行数据读取,数据以DataFrame类型储存在环境中。

3.数据观察

3.1 观察变量(数据列名称列表)

print("columns如下:")
print(data.columns)
print()

image-20240816164625114

3.2 观察数据形状

print("shape如下:")
print(data.shape)
print()

image-20240816164723181

3.3 观察各列数据类型

print("info如下:")
data.info()
print()

image-20240816165005297

3.4 观察统计信息

print("describe如下:")
print(data.describe())
print()

image-20240816165127334

4.数据清洗

4.1 数据类型转换

4.1.1 将str转换为float
cleaned_data['末端相对于参考点的沿流线距离'] = pd.to_numeric(cleaned_data['末端相对于参考点的沿流线距离'],errors='coerce')
cleaned_data['末端相对于参考点的沿流线距离'] = cleaned_data['末端相对于参考点的沿流线距离'].round(2) 

将距离列转换为数值类型,无法转换的值变为NaN,并保留两位小数

4.2 缺失值处理

4.2.1 查看缺失值
print(cleaned_data.isna().sum())

image-20240816170031420

4.2.2 查看缺失比例
print(cleaned_data.apply(lambda x: sum(x.isnull()) / len(x), axis=0))

image-20240816170044229

4.2.3 删除法
1. 直接删除法
cleaned_data=cleaned_data.dropna()  
2.有缺失就删除
cleaned_data=cleaned_data.dropna(how='any',axis = 1 )
3.全部缺失才删除
cleaned_data = cleaned_data.dropna(how='all', axis=1)
4.基于变量缺失
cleaned_data=cleaned_data.dropna(axis = 0,how='any',subset=['日期']) 
4.2.4 插值法
1. 线性插值
cleaned_data['末端相对于参考点的沿流线距离'] = cleaned_data['末端相对于参考点的沿流线距离'].interpolate(method='linear')
2. 多项式插值
cleaned_data['末端相对于参考点的沿流线距离'] = cleaned_data['末端相对于参考点的沿流线距离'].interpolate(method='polynomial', order=2)
3. 样条插值
cleaned_data['末端相对于参考点的沿流线距离'] = cleaned_data['末端相对于参考点的沿流线距离'].interpolate(method='spline', order=3)
4. 最近邻插值
cleaned_data['末端相对于参考点的沿流线距离'] = cleaned_data['末端相对于参考点的沿流线距离'].interpolate(method='nearest')
5. 线性插值向前填充
cleaned_data['末端相对于参考点的沿流线距离'] = cleaned_data['末端相对于参考点的沿流线距离'].fillna(method='pad')
6. 线性插值向后填充
cleaned_data['末端相对于参考点的沿流线距离'] = cleaned_data['末端相对于参考点的沿流线距离'].fillna(method='backfill')

4.3 重复值处理

4.3.1 查看重复值
print(cleaned_data.duplicated())

image-20240816171723653

当出现重复值时显示为True,反之为False。

print('数据集是否存在重复观测: \n', any(cleaned_data.duplicated()))

image-20240816171843684

4.3.2 查看哪些数据重复
print(cleaned_data[cleaned_data.duplicated()]) 

image-20240816172000736

4.3.3 计算重复数量
print(np.sum(cleaned_data.duplicated()))  

image-20240816172104788

4.4 异常值处理

4.4.1 箱线图

箱线图是利用最小值,第一四分位数(Q1),中位数(Q2),第三四分位数(Q3),最大值来描述数据的一种方法。

那么,如何利用箱线图来对异常值进行处理呢?
​ 引入一个概念——四分位距(IQR):求解方法是Q3与Q1之间的差值即IQR = Q3-Q1;设定小于Q1-1.5IQR 与大于Q3+1.5IQR的值为异常值,因此,当我们作出箱线图时便能直观看出所选数据中是否有异常值。

plt.figure(figsize=(10, 6))
sns.boxplot(data=cleaned_data, x='末端相对于参考点的沿流线距离')
plt.title('末端位置的相对距离箱型图')
plt.xlabel('沿流线距离 (m)')
plt.savefig('data_2\末端位置的相对距离箱型图.png')

箱线图一般与IQR方法配合使用:

cleaned_data = cleaned_data['末端相对于参考点的沿流线距离'].dropna().values

# 计算Q1, Q3和IQR
Q1 = np.percentile(cleaned_data, 25)
Q3 = np.percentile(cleaned_data, 75)
IQR = Q3 - Q1

# 确定异常值的边界
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR

# 找到异常值的索引
outliers_iqr = np.where((cleaned_data < lower_bound) | (cleaned_data > upper_bound))[0]

# 绘制散点图
plt.figure(figsize=(10, 6))
plt.scatter(range(len(cleaned_data)), cleaned_data, c='blue', marker='*',label='正常数据')
plt.scatter(outliers_iqr, cleaned_data[outliers_iqr], c='red', marker='*',label='异常值')
plt.hlines([lower_bound, upper_bound], xmin=0, xmax=len(cleaned_data), color='green', linestyles='dashed', label='异常值界限')
plt.legend(loc='upper left')

# 标注异常值界限
plt.text(len(cleaned_data)*0.95, lower_bound, f'下界限: {lower_bound:.2f}', color='green', ha='left', va='center')
plt.text(len(cleaned_data)*0.95, upper_bound, f'上界限: {upper_bound:.2f}', color='green', ha='left', va='center')
plt.title('沿流线距离数据的异常值检测')
plt.xlabel('样本索引')
plt.ylabel('沿流线距离 (m)')
plt.grid(True)
plt.savefig('data_2\IQR结果图.png')

Figure_1

Figure_2

4.4.2 Z分数

Z分数是一种检测异常值的参数化方法,它假定数据呈正态分布,异常值位于正态曲线的尾部,并且远离平均值。

X 是数据点的值, μ 是数据集的均值, σ 是数据集的标准差。

cleaned_data = cleaned_data['末端相对于参考点的沿流线距离'].dropna().values

# 计算 Z-Score
z_scores = (cleaned_data - np.mean(cleaned_data)) / np.std(cleaned_data)

# 设置阈值为3,并找到异常值的索引
threshold = 3
outliers_zscore = np.where(np.abs(z_scores) > threshold)[0]

# 绘制 Z-Score 结果图
plt.figure(figsize=(10, 6))
plt.scatter(range(len(cleaned_data)), cleaned_data, c='blue',marker='*', label='正常数据')
plt.scatter(outliers_zscore, cleaned_data[outliers_zscore], c='red',marker='*', label='异常值')

# 添加阈值线
plt.axhline(y=np.mean(cleaned_data) + threshold * np.std(cleaned_data), color='green', linestyle='--', label='阈值线')
plt.axhline(y=np.mean(cleaned_data) - threshold * np.std(cleaned_data), color='green', linestyle='--')

# 添加阈值标注
plt.text(len(cleaned_data)*0.95, np.mean(cleaned_data) + threshold * np.std(cleaned_data), f'上阈值: {np.mean(cleaned_data) + threshold * np.std(cleaned_data):.2f}', color='green', va='bottom')
plt.text(len(cleaned_data)*0.95, np.mean(cleaned_data) - threshold * np.std(cleaned_data), f'下阈值: {np.mean(cleaned_data) - threshold * np.std(cleaned_data):.2f}', color='green', va='top')

plt.legend(loc='upper left')
plt.title('沿流线距离数据的Z-Score异常值检测')
plt.xlabel('样本索引')
plt.ylabel('沿流线距离 (m)')
plt.grid(True)
plt.savefig('data_2\Z-Score异常值检测图.png')

Z-Score异常值检测图

由图能看出来,阈值线大小和threshold取值有关,一般取值为-3和3之间。np.where()函数是寻找所有符合括号内条件的值,并返回一个数组array,其中array[0]存储了所有满足条件的值的下标。视情况而定(感觉不好掌控)。

4.4.3 孤立森林

孤立森林:孤立森林是一种经典的异常检测算法,它不需要计算距离等指标来计算样本间的差异,而是借助二叉树通过“疏离程度”来刻画样本间的差异。

cleaned_data = cleaned_data['末端相对于参考点的沿流线距离'].dropna().values

# 将数据调整为列向量
data_reshaped = cleaned_data.reshape(-1, 1)

# 使用 IsolationForest 检测异常值
clf = IsolationForest(contamination=0.05, random_state=2023)
outliers_isolation = clf.fit_predict(data_reshaped)
outliers_isolation = np.where(outliers_isolation == -1)[0]

# 绘制 Isolation Forest 结果图
plt.figure(figsize=(10, 6))
plt.scatter(range(len(cleaned_data)), cleaned_data, c='blue', marker='*',label='原始数据')
plt.scatter(outliers_isolation, cleaned_data[outliers_isolation], c='red',marker='*',label='异常值')

# 标记异常值
for idx in outliers_isolation:
    plt.text(idx, cleaned_data[idx], f'{cleaned_data[idx]:.2f}', color='red', fontsize=9, ha='right')

plt.legend(loc='upper left')
plt.title('沿流线距离数据的Isolation Forest异常值检测')
plt.xlabel('样本索引')
plt.ylabel('沿流线距离 (m)')
plt.grid(True)

# 保存图像
plt.savefig('data_2\IsolationForest_outliers_marked.png')

Figure_1

在创建 IsolationForest 模型时设置了 contamination=0.05,意思是认为数据集中有 5% 的数据是异常值。当使用 IsolationForest检测异常值时,模型会为每个数据点分配一个分数,这个分数反映了该点被认为是异常的程度。分数越低,数据点越有可能是异常值。

reshape()函数:reshape(1,-1)把数组转换为1行;reshape(-1,1)把数组转换为1列。IsolationForest()函数的参数解释:contamination(污染)说明异常数据占所有数据的比例;random_state指随机种子;n_estimators指iTree的数量,一般默认为100个,因为数据显示当iTree数量为100时,样本路径的平均高度趋于收敛。fit_predict()函数:训练和预测一起完成,可以得到模型是否异常的判断,-1为异常,1为正常。

4.4.4 DBSCAN聚类

DBSCAN聚类算法是一种基于密度的异常检测方法。

DBSCAN()函数的参数解释:eps指两个样本之间的最大距离,即扫描半径;min_samples指作为核心点的话邻域(即以其为圆心,eps为半径的圆,含圆上的点)中的最小样本数(包括点本身)。

cleaned_data = cleaned_data['末端相对于参考点的沿流线距离'].dropna().values

# 将数据调整为列向量
data_reshaped = cleaned_data.reshape(-1, 1)

# 使用 DBSCAN 进行异常值检测
dbscan = DBSCAN(eps=1, min_samples=3)
outliers_dbscan = np.where(dbscan.fit_predict(data_reshaped) == -1)[0]

# 打印异常值的索引
print("异常值索引:", outliers_dbscan)

# 绘制 DBSCAN 结果图
plt.figure(figsize=(10, 6))
plt.scatter(range(len(cleaned_data)), cleaned_data, c='blue',label='原始数据',marker="*")
plt.scatter(outliers_dbscan, cleaned_data[outliers_dbscan], c='red', label='异常值',marker="*")
plt.legend(loc='upper left')
plt.title('沿流线距离数据的DBSCAN异常值检测')
plt.xlabel('样本索引')
plt.ylabel('沿流线距离 (m)')
plt.grid(True)
plt.show()

Figure_2

补充

1. 删除 "Unnamed" 列

cleaned_data = data.drop(columns=[col for col in data.columns if "Unnamed" in col])

2.删除索引行

cleaned_data = cleaned_data.drop(index=range(0, 87))

3. 显示负号

plt.rcParams['axes.unicode_minus'] = False

4. 显示中文标签

plt.rcParams['font.sans-serif'] = ['SimHei']