第二章数据预处理:公式Python代码实现
一、基本统计量计算
1. 集中趋势度量
均值计算:
import numpy as np
from scipy import stats# 原始数据
data = [65, 72, 88, 45, 93, 81, 78, 86, 90, 55]# 均值计算
mean_value = np.mean(data)
print(f"均值: {mean_value:.2f}")# 手动实现均值计算
def calculate_mean(data):return sum(data) / len(data)manual_mean = calculate_mean(data)
print(f"手动计算均值: {manual_mean:.2f}")
中位数计算:
# 中位数计算
median_value = np.median(data)
print(f"中位数: {median_value:.2f}")# 手动实现中位数
def calculate_median(data):sorted_data = sorted(data)n = len(sorted_data)if n % 2 == 0:return (sorted_data[n//2 - 1] + sorted_data[n//2]) / 2else:return sorted_data[n//2]manual_median = calculate_median(data)
print(f"手动计算中位数: {manual_median:.2f}")
众数计算:
# 众数计算
mode_result = stats.mode(data)
print(f"众数: {mode_result.mode[0]}, 出现次数: {mode_result.count[0]}")# 手动实现众数
def calculate_mode(data):from collections import Countercount = Counter(data)max_count = max(count.values())return [k for k, v in count.items() if v == max_count]manual_mode = calculate_mode(data)
print(f"手动计算众数: {manual_mode}")
2. 散布度度量
方差计算:
# 方差计算
variance_value = np.var(data)
print(f"方差: {variance_value:.2f}")# 手动实现方差
def calculate_variance(data):mean = np.mean(data)return sum((x - mean) ** 2 for x in data) / len(data)manual_variance = calculate_variance(data)
print(f"手动计算方差: {manual_variance:.2f}")
分位数计算:
# 分位数计算
q1 = np.percentile(data, 25)
q3 = np.percentile(data, 75)
iqr = q3 - q1print(f"Q1(25%分位数): {q1:.2f}")
print(f"Q3(75%分位数): {q3:.2f}")
print(f"IQR: {iqr:.2f}")# 手动实现分位数
def calculate_quantiles(data, percentile):sorted_data = sorted(data)index = (len(data) - 1) * percentile / 100lower = int(index)upper = lower + 1weight = index - lowerif upper >= len(sorted_data):return sorted_data[lower]return sorted_data[lower] * (1 - weight) + sorted_data[upper] * weightmanual_q1 = calculate_quantiles(data, 25)
manual_q3 = calculate_quantiles(data, 75)
print(f"手动计算Q1: {manual_q1:.2f}")
print(f"手动计算Q3: {manual_q3:.2f}")
import matplotlib.pyplot as pltplt.figure(figsize=(8, 6))
plt.boxplot(data, vert=False)
plt.title('箱线图 - 数据分布可视化')
plt.xlabel('数值')
plt.show()
二、距离度量计算
1. 数值型数据距离
明可夫斯基距离:
def minkowski_distance(x, y, p=2):"""计算明可夫斯基距离p=1: 曼哈顿距离p=2: 欧氏距离p=∞: 切比雪夫距离"""return sum(abs(a - b) ** p for a, b in zip(x, y)) ** (1/p)# 示例数据
point_a = [1, 2, 3]
point_b = [4, 5, 6]# 计算不同距离
manhattan = minkowski_distance(point_a, point_b, 1)
euclidean = minkowski_distance(point_a, point_b, 2)
chebyshev = minkowski_distance(point_a, point_b, 100) # 近似切比雪夫print(f"曼哈顿距离: {manhattan:.2f}")
print(f"欧氏距离: {euclidean:.2f}")
print(f"切比雪夫距离: {chebyshev:.2f}")# 使用scipy验证
from scipy.spatial import distance
print(f"SciPy曼哈顿: {distance.cityblock(point_a, point_b):.2f}")
print(f"SciPy欧氏: {distance.euclidean(point_a, point_b):.2f}")
print(f"SciPy切比雪夫: {distance.chebyshev(point_a, point_b):.2f}")
余弦相似度:
def cosine_similarity(x, y):"""计算余弦相似度"""dot_product = sum(a * b for a, b in zip(x, y))norm_x = sum(a ** 2 for a in x) ** 0.5norm_y = sum(b ** 2 for b in y) ** 0.5return dot_product / (norm_x * norm_y)# 示例
vec1 = [1, 2, 3]
vec2 = [4, 5, 6]cos_sim = cosine_similarity(vec1, vec2)
print(f"余弦相似度: {cos_sim:.4f}")# 使用scipy验证
from scipy.spatial import distance
cos_sim_scipy = 1 - distance.cosine(vec1, vec2)
print(f"SciPy余弦相似度: {cos_sim_scipy:.4f}")
2. 布尔型数据距离
Jaccard系数计算:
def jaccard_similarity(set1, set2):"""计算Jaccard相似系数"""intersection = len(set1.intersection(set2))union = len(set1.union(set2))return intersection / union if union != 0 else 0# 示例
set_a = {1, 2, 3, 4, 5}
set_b = {4, 5, 6, 7, 8}jaccard = jaccard_similarity(set_a, set_b)
print(f"Jaccard相似系数: {jaccard:.4f}")# 使用sklearn验证
from sklearn.metrics import jaccard_score
# 需要转换为二进制向量
y_true = [1, 1, 1, 1, 1, 0, 0, 0]
y_pred = [0, 0, 1, 1, 1, 1, 0, 0]
jaccard_sklearn = jaccard_score(y_true, y_pred)
print(f"Sklearn Jaccard: {jaccard_sklearn:.4f}")
三、数据标准化实现
1. Z-Score标准化
def z_score_normalization(data):"""Z-Score标准化"""mean = np.mean(data)std = np.std(data)return [(x - mean) / std for x in data]# 示例
original_data = [10, 20, 30, 40, 50]
z_score_data = z_score_normalization(original_data)print("原始数据:", original_data)
print("Z-Score标准化:", [f"{x:.2f}" for x in z_score_data])# 使用sklearn验证
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
z_score_sklearn = scaler.fit_transform(np.array(original_data).reshape(-1, 1)).flatten()
print("Sklearn Z-Score:", [f"{x:.2f}" for x in z_score_sklearn])
2. 0-1标准化
def minmax_normalization(data):"""0-1标准化"""min_val = min(data)max_val = max(data)return [(x - min_val) / (max_val - min_val) for x in data]# 示例
minmax_data = minmax_normalization(original_data)
print("0-1标准化:", [f"{x:.2f}" for x in minmax_data])# 使用sklearn验证
from sklearn.preprocessing import MinMaxScaler
minmax_scaler = MinMaxScaler()
minmax_sklearn = minmax_scaler.fit_transform(np.array(original_data).reshape(-1, 1)).flatten()
print("Sklearn 0-1标准化:", [f"{x:.2f}" for x in minmax_sklearn])
3. 小数定标标准化
def decimal_scaling(data):"""小数定标标准化"""max_abs = max(abs(x) for x in data)j = len(str(int(max_abs))) if max_abs != 0 else 1return [x / (10 ** j) for x in data]# 示例
decimal_data = decimal_scaling(original_data)
print("小数定标标准化:", [f"{x:.3f}" for x in decimal_data])
4. Logistic标准化
def logistic_normalization(data):"""Logistic标准化"""return [1 / (1 + np.exp(-x)) for x in data]# 示例
logistic_data = logistic_normalization(original_data)
print("Logistic标准化:", [f"{x:.3f}" for x in logistic_data])
四、高级算法实现
1. LOF异常检测算法
from sklearn.neighbors import LocalOutlierFactor
import numpy as np# 生成示例数据
X = np.array([[1, 2], [2, 1], [1, 1], [10, 10], [10, 11]])# LOF检测
lof = LocalOutlierFactor(n_neighbors=2)
outlier_labels = lof.fit_predict(X)
lof_scores = -lof.negative_outlier_factor_print("数据点:", X)
print("LOF标签(1正常,-1异常):", outlier_labels)
print("LOF分数:", lof_scores)# 手动实现简化版LOF
def simple_lof(data, k=2):from sklearn.neighbors import NearestNeighborsnbrs = NearestNeighbors(n_neighbors=k+1).fit(data)distances, indices = nbrs.kneighbors(data)lof_scores = []for i in range(len(data)):# 简化实现,实际LOF更复杂avg_neighbor_dist = np.mean(distances[i, 1:])lof_scores.append(avg_neighbor_dist)return lof_scoresmanual_lof = simple_lof(X)
print("简化版LOF分数:", manual_lof)
2. 离散化实现
等距离散化:
def equal_width_discretization(data, n_bins=3):"""等距离散化"""min_val = min(data)max_val = max(data)width = (max_val - min_val) / n_binsbins = []labels = []for i in range(n_bins):lower = min_val + i * widthupper = min_val + (i + 1) * widthbins.append((lower, upper))labels.append(f"Bin_{i+1}")discretized = []for x in data:for i, (lower, upper) in enumerate(bins):if lower <= x <= upper:discretized.append(labels[i])breakreturn discretized, bins# 示例
data_to_discretize = [1, 5, 10, 15, 20, 25, 30]
discretized, bins = equal_width_discretization(data_to_discretize, 3)
print("等距离散化结果:", discretized)
print("区间划分:", bins)
等频离散化:
def equal_frequency_discretization(data, n_bins=3):"""等频离散化"""sorted_data = sorted(data)n = len(data)bin_size = n // n_binsdiscretized = []for x in data:bin_index = min(sorted_data.index(x) // bin_size, n_bins - 1)discretized.append(f"Bin_{bin_index + 1}")return discretized# 示例
equal_freq = equal_frequency_discretization(data_to_discretize, 3)
print("等频离散化结果:", equal_freq)
五、完整预处理流程示例
from sklearn.impute import SimpleImputer, KNNImputer
from sklearn.preprocessing import OneHotEncoder, StandardScaler
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
import pandas as pd
import numpy as np# 创建示例数据集
data = {'age': [25, 30, np.nan, 35, 40],'income': [50000, 60000, 70000, np.nan, 90000],'gender': ['M', 'F', 'M', 'F', 'M'],'education': ['Bachelor', 'Master', np.nan, 'PhD', 'Bachelor']
}df = pd.DataFrame(data)# 定义预处理流程
numeric_features = ['age', 'income']
categorical_features = ['gender', 'education']numeric_transformer = Pipeline(steps=[('imputer', KNNImputer(n_neighbors=2)),('scaler', StandardScaler())
])categorical_transformer = Pipeline(steps=[('imputer', SimpleImputer(strategy='most_frequent')),('onehot', OneHotEncoder(handle_unknown='ignore'))
])preprocessor = ColumnTransformer(transformers=[('num', numeric_transformer, numeric_features),('cat', categorical_transformer, categorical_features)])# 执行预处理
processed_data = preprocessor.fit_transform(df)
print("原始数据:")
print(df)
print("\n预处理后数据形状:", processed_data.shape)
print("预处理后数据示例:")
print(processed_data[:3])
📊 预处理流程可视化:
# 可视化预处理效果(示例)
import matplotlib.pyplot as pltplt.figure(figsize=(12, 5))# 原始数据分布
plt.subplot(1, 2, 1)
plt.hist(df['age'].dropna(), bins=10, alpha=0.7, label='Age')
plt.hist(df['income'].dropna(), bins=10, alpha=0.7, label='Income')
plt.title('原始数据分布')
plt.legend()# 预处理后数据分布
plt.subplot(1, 2, 2)
processed_df = pd.DataFrame(processed_data[:, :2], columns=['age_scaled', 'income_scaled'])
plt.hist(processed_df['age_scaled'], bins=10, alpha=0.7, label='Age (scaled)')
plt.hist(processed_df['income_scaled'], bins=10, alpha=0.7, label='Income (scaled)')
plt.title('标准化后数据分布')
plt.legend()plt.tight_layout()
plt.show()
通过以上完整的Python代码实现,您可以实际运行和测试第二章中提到的所有重要公式和算法,加深对数据预处理技术的理解。
数据预处理核心公式详解
一、基本统计量公式精讲
1. 集中趋势度量公式
均值公式:
xˉ=n1i=1∑nxi
数学意义:均值是所有数据点的算术平均值,代表数据的中心位置。它是最小二乘估计的最优解,即使各数据点到均值的距离平方和最小。
应用场景:适用于对称分布的数据,对极端值敏感。
中位数公式:
当n为奇数时:M=x2n+1
当n为偶数时:M=2x2n+x2n+1
数学意义:将数据集分为两个相等部分的值,对极端值不敏感。
应用场景:适用于偏态分布或有离群值的数据。
经验关系公式:
Mean−Mode≈3×(Mean−Median)
数学意义:描述了在偏态分布中三个集中度量的近似关系。
📊 偏态分布示意图:
2. 散布度度量公式
方差公式:
σ2=n1i=1∑n(xi−xˉ)2
数学意义:衡量数据点与均值的平均偏离程度,是数据离散程度的量化指标。
应用场景:用于评估数据的波动性和稳定性。
四分位距公式:
IQR=Q3−Q1
数学意义:描述了中间50%数据的分布范围,对极端值不敏感。
应用场景:用于构建箱线图,识别离群值。
📊 箱线图示意图:
二、距离度量公式深度解析
1. 明可夫斯基距离族
通用公式:
d(xi,xj)=(k=1∑p∣xik−xjk∣m)1/m
数学意义:明可夫斯基距离是距离度量的一般形式,通过参数m控制距离的性质。
特例分析:
m=1(曼哈顿距离):
d(xi,xj)=k=1∑p∣xik−xjk∣
适用于网格状路径的度量,如城市街区距离。
m=2(欧氏距离):
d(xi,xj)=k=1∑p(xik−xjk)2
是最常用的距离度量,具有旋转不变性。
m=∞(切比雪夫距离):
d(xi,xj)=kmax∣xik−xjk∣
适用于只关心最大差异的场景。
2. 余弦相似度公式
公式:
cos(θ)=∑k=1pxik2∑k=1pxjk2∑k=1pxikxjk
数学意义:衡量两个向量的方向相似性,与向量大小无关。
应用场景:文本相似度计算、推荐系统中用户兴趣相似度度量。
几何解释:两个向量在向量空间中的夹角余弦值,值域为[-1,1]。
3. 布尔型数据距离公式
Jaccard系数:
J=q+r+sq
数学意义:衡量两个集合的相似度,忽略同时为0的情况。
应用场景:文档相似性、用户行为模式匹配。
简单匹配方法:
d(xi,xj)=1−pm
数学意义:基于匹配属性的比例计算相异度。
三、数据标准化公式详解
1. Z-Score标准化
公式:
x∗=σx−μ
数学意义:将数据转换为均值为0、标准差为1的标准正态分布。
变换性质:
保持原始数据的分布形状
均值为0,方差为1
适用于大多数机器学习算法
应用场景:需要数据符合正态分布的算法(如SVM、线性回归)。
2. 0-1标准化(Min-Max标准化)
公式:
x∗=max−minx−min
数学意义:线性变换将数据压缩到[0,1]区间。
变换性质:
保持原始数据的相对关系
对极端值敏感
适用于神经网络等需要特定输入范围的算法
3. 小数定标标准化
公式:
x∗=10jx
数学意义:通过移动小数点位置进行缩放。
应用场景:数据跨度很大时,简化数值表示。
4. Logistic标准化
公式:
x∗=1+e−x1
数学意义:使用Sigmoid函数将数据映射到(0,1)区间。
应用场景:需要概率输出的场景。
四、高级算法公式解析
1. LOF(局部离群因子)算法
核心公式体系:
可达距离:
reach-distk(B,A)=max{k-distance(B),d(A,B)}
数学意义:避免距离过近导致的数值不稳定。
局部可达密度:
lrdk(A)=∑B∈Nk(A)reach-distk(B,A)∣Nk(A)∣
数学意义:衡量点A周围的局部密度。
局部离群因子:
LOFk(A)=∣Nk(A)∣⋅lrdk(A)∑B∈Nk(A)lrdk(B)
数学意义:
LOF≈1:正常点
LOF<1:密度较高,可能是核心点
LOF>1:密度较低,可能是异常点
2. 3σ原则离散化
区间划分公式:
[μ−σ,μ+σ]:约68%数据(正常范围)
[μ−2σ,μ−σ)和 [μ+σ,μ+2σ]:约27%数据(一般异常)
[μ−3σ,μ−2σ)和 [μ+2σ,μ+3σ]:约4.2%数据(极端异常)
超出μ±3σ:约0.3%数据(高度异常)
LOF
我们来对局部离群因子(Local Outlier Factor, LOF)算法进行不简化的、详尽的讲解。本文将深入其核心思想、完整数学定义、算法流程、解释其输出,并讨论其优缺点。
一、核心思想与直观理解
LOF算法是一种基于密度的离群点检测方法。其基本思想非常直观:通过比较一个数据点与其邻域点的局部密度,来判断该点是否为离群点。如果一个点的密度明显低于其邻域点的密度,那么它就很可能是离群点。
📊 密度对比示意图:
想象一下人群聚集的场景。在一个人群密集的广场上(高密度区域),大多数人的周围都有很多邻居。如果此时有一个人孤零零地站在广场边缘,他周围的邻居非常少(低密度),那么相对于广场上的人群,他就是一个“局部离群点”。LOF算法就是量化这种“孤独”的程度。
与传统基于全局距离或统计的方法不同,LOF能够识别出在全局分布中不明显,但在局部语境下确为异常的点。这使得LOF在处理具有不同密度簇的数据集时非常强大。
二、关键概念与数学定义
要彻底理解LOF,必须精确定义其涉及的每一个概念。以下是其完整的数学构建过程。
1. 第k距离(k-distance)
对于任意一点 A,其第k距离 k-distance(A)定义为:
点 A到某个点 O的距离 d(A,O)。
至少存在 k个点 P,满足 d(A,P)≤d(A,O)。
至多存在 k−1个点 P,满足 d(A,P)<d(A,O)。
简单来说,k-distance(A)是点 A到其第 k个最近邻的距离(不包括点 A自身)。
2. 第k距离邻域(k-distance Neighborhood)
点 A的第k距离邻域 Nk(A)包含了所有与点 A距离不超过 k-distance(A)的点。
Nk(A)={P∣d(A,P)≤k-distance(A)}
该邻域内的点的数量 ∥Nk(A)∥≥k。
3. 可达距离(Reachability Distance)
点 A关于点 B的第k可达距离定义为:
reach-distk(B,A)=max{k-distance(B),d(A,B)}
设计目的:为了平滑距离计算,减少波动。当点 A在点 B的密集邻域内时(d(A,B)小),使用 k-distance(B)作为距离,这相当于赋予点 B一个“最小感知范围”。当点 A远离点 B时,则使用实际距离。这使得密度计算更加稳定。
4. 局部可达密度(Local Reachability Density, LRD)
点 A的局部可达密度是其第k距离邻域内的所有点到 A的平均可达距离的倒数。
lrdk(A)=∑B∈Nk(A)reach-distk(B,A)∣Nk(A)∣
直观理解:分母是“平均可达距离”。平均可达距离越大,说明从邻域点“到达”点A越困难,点A所在的区域密度就越低。 因此,其倒数 lrdk(A)就代表了点 A周围的局部密度。密度高,则LRD值高;密度低,则LRD值低。
5. 局部离群因子(Local Outlier Factor, LOF)
最终,点 A的局部离群因子是其邻域点 Nk(A)的平均局部密度与点 A自身的局部密度的比值。
LOFk(A)=∣Nk(A)∣⋅lrdk(A)∑B∈Nk(A)lrdk(B)
最终解释:
LOFk(A)≈1:意味着点 A的局部密度与其邻居们的平均局部密度非常接近。因此,点 A很可能是一个内点(Inlier),处于一个密度均匀的簇中。
LOFk(A)<1:意味着点 A的局部密度高于其邻居的平均密度。点 A可能处于一个比周围区域更密集的簇中,它是一个密集点,绝对不是离群点。
LOFk(A)>1:意味着点 A的局部密度低于其邻居的平均密度。这表明点 A相对于其周围环境来说更加“孤独”,因此它很可能是一个离群点(Outlier)。LOF值越大,是离群点的可能性就越高。
📊 LOF结果示意图:
此图完美展示了LOF的核心思想。图中大部分点构成一个密集的簇(高密度区域),而点A和点B远离该簇。然而,点B虽然全局距离远,但其周围有一个局部簇,密度并不低,所以LOF值不高。点A则既远离主簇,周围又几乎没有其他点(局部密度极低),因此其LOF值非常高,被识别为离群点。这体现了LOF“局部”比较的优势。
三、完整的LOF算法流程
输入:数据集 X,参数 k(邻居数量)。
计算距离矩阵:计算所有点两两之间的距离(如欧氏距离)。
对每个点 A:
a. 计算 k-distance(A)。
b. 确定 Nk(A)。
c. 计算 lrdk(A)。
对每个点 A:
a. 计算 LOFk(A)。
输出:所有点的LOF值。根据LOF值排序,设定一个阈值(如1.5、2.0),高于阈值的点判定为离群点。
四、LOF算法的优势与局限性
优势:
无需分布假设:不同于基于统计的方法,LOF不假设数据服从某种特定分布。
识别局部离群点:能有效发现全局不明显但局部异常的点,适用于具有不同密度簇的数据集。
给出异常程度得分:LOF是一个连续的分数,而不仅仅是二元判断,便于排序和选择最异常的点。
局限性与注意事项:
参数 k敏感:k的选择至关重要。
k太小:可能无法平滑掉局部波动,导致误报,将一些本不是离群点的点标记为异常。
k太大:可能导致过度泛化,使得本地的密度特征被稀释,可能漏掉一些真实的离群点。
(通常需要通过实验和经验来选择合适的 k)
计算复杂度高:需要计算所有点对之间的距离,并对每个点找k近邻,时间复杂度约为 O(n2),对于大规模数据集计算代价高昂。
对维度灾难敏感:在高维空间中,所有点对之间的距离都变得相似,基于距离的密度概念会失效,导致LOF性能下降。
五、总结
局部离群因子(LOF)算法是一个强大而直观的离群点检测工具。它通过比较一个点的局部密度与其邻居的局部密度来工作,核心在于可达距离和局部可达密度这两个精巧的设计。其输出值 LOFk(A)提供了异常程度的量化度量。
理解LOF的关键在于摒弃“全局异常”的思维,转而从“局部相对密度”的视角审视数据。它最适合用于发现嵌入在密集簇之间或附着在密集簇边缘的局部异常点。
数学基础:基于正态分布的概率性质。
五、公式应用指导与选择原则
1. 距离度量选择指南
数据特征 | 推荐距离度量 | 理由 |
---|---|---|
数值型,各维度重要性相同 | 欧氏距离 | 保持各维度平等贡献 |
数值型,需要方向相似性 | 余弦相似度 | 忽略向量大小影响 |
布尔型数据 | Jaccard系数 | 处理非对称二元数据 |
混合数据类型 | 加权组合距离 | 灵活处理不同属性类型 |
2. 标准化方法选择原则
数据分布特征 | 推荐方法 | 注意事项 |
---|---|---|
近似正态分布 | Z-Score | 保持分布形状 |
分布未知,需要特定范围 | 0-1标准化 | 对极端值敏感 |
数值跨度大 | 小数定标 | 保持数值相对关系 |
需要概率解释 | Logistic | 输出值在(0,1)区间 |
3. 异常检测方法比较
方法 | 适用场景 | 计算复杂度 | 优点 |
---|---|---|---|
3σ原则 | 正态分布数据 | 低 | 简单快速 |
箱线图 | 任意分布 | 低 | 直观可视化 |
LOF算法 | 局部密度变化 | 高 | 发现局部异常 |
通过深入理解这些公式的数学原理和应用场景,能够根据具体数据特征选择最合适的预处理方法,为后续的机器学习模型构建奠定坚实基础。