主成分分析法是一种利用相关系数对数据进行降维的方法,可用于处理维数过多、指标意义不明确的数据。求相关系数的方法有很多种,下面只以协方差法为例
相关系数
输入是 shape 为 [sample, feature] 的原始数据,先对数据进行标准化,再求其协方差,即为各个维度之间的相关系数
import numpy as np
def standardize(data, axis=None):
''' 标准化'''
data = np.array(data)
mean = np.mean(data, axis=axis, keepdims=True)
std = np.std(data, axis=axis, keepdims=True)
return (data - mean) / std
def association(data):
''' 相关系数矩阵 (协方差矩阵)
data: 形如 [n_sample, n_features] 的 array'''
return np.cov(standardize(data, axis=0).T)
旋转降维矩阵
输入是上文中得到的相关系数矩阵 (行列数相同的方阵),求解特征方程之后按照每个特征向量的绝对值的最大值进行变号 (纯属强迫症),将特征值按降序排序 (对应的特征向量同步)。特征值之比即为贡献率之比,按照给定的累积贡献度阈值对主成分进行选定
def principal(asso, thresh=0.80):
''' 主成分分析
asso: 形如 [n_features, n_features] 的 array
thresh: 累积贡献率阈值
return: 旋转降维矩阵'''
cha_value, rota = np.linalg.eig(asso)
max_idx = np.abs(rota).argmax(axis=0)
sign = np.sign([c[i] for i, c in zip(max_idx, rota.T)])
# 得到绝对值最大数的符号
rota *= sign
index = np.argsort(cha_value)[::-1]
cha_value, rota = cha_value[index], rota[:, index]
# 降序排序: 特征值、特征向量
sum_contribute = np.cumsum(cha_value) / cha_value.sum()
# 累计贡献率
for idx, sum_con in enumerate(sum_contribute):
if sum_con >= thresh:
info_contribute = cha_value[:idx + 1] / cha_value.sum()
# 取前 m 个主成分, 计算信息贡献率
print(f'主成分贡献率: {info_contribute}n')
rota = rota[:, :idx + 1]
return rota
实战:数据降维
| 省份 | 食品 | 衣着 | 家庭设备 | 医疗 | 交通 | 娱乐 | 居住 | 杂项 |
| 北京 | 2959.19 | 730.79 | 749.41 | 513.34 | 467.87 | 1141.82 | 478.42 | 457.64 |
| 天津 | 2459.77 | 495.47 | 697.33 | 302.87 | 284.19 | 735.97 | 570.84 | 305.08 |
| 河北 | 1495.63 | 515.9 | 362.37 | 285.32 | 272.95 | 540.58 | 364.91 | 188.63 |
| 山西 | 1406.33 | 477.77 | 290.15 | 208.57 | 201.5 | 414.72 | 281.84 | 212.1 |
| 内蒙古 | 1303.97 | 524.29 | 254.83 | 192.17 | 249.81 | 463.09 | 287.87 | 192.96 |
| 辽宁 | 1730.84 | 553.9 | 246.91 | 279.81 | 239.18 | 445.2 | 330.24 | 163.86 |
| 吉林 | 1561.86 | 492.42 | 200.49 | 218.36 | 220.69 | 459.62 | 360.48 | 147.76 |
| 黑龙江 | 1410.11 | 510.71 | 211.88 | 277.11 | 224.65 | 376.82 | 317.61 | 152.85 |
| 上海 | 3712.31 | 550.74 | 893.37 | 346.93 | 527 | 1034.98 | 720.33 | 462.03 |
| 江苏 | 2207.58 | 449.37 | 572.4 | 211.92 | 302.09 | 585.23 | 429.77 | 252.54 |
| 浙江 | 2629.16 | 557.32 | 689.73 | 435.69 | 514.66 | 795.87 | 575.76 | 323.36 |
| 安徽 | 1844.78 | 430.29 | 271.28 | 126.33 | 250.56 | 513.18 | 314 | 151.39 |
| 福建 | 2709.46 | 428.11 | 334.12 | 160.77 | 405.14 | 461.67 | 535.13 | 232.29 |
| 江西 | 1563.78 | 303.65 | 233.81 | 107.9 | 209.7 | 393.99 | 509.39 | 160.12 |
| 山东 | 1675.75 | 613.32 | 550.71 | 219.79 | 272.59 | 599.43 | 371.62 | 211.84 |
| 河南 | 1427.65 | 431.79 | 288.55 | 208.14 | 217 | 337.76 | 421.31 | 165.32 |
| 湖南 | 1942.23 | 512.27 | 401.39 | 206.06 | 321.29 | 697.22 | 492.6 | 226.45 |
| 湖北 | 1783.43 | 511.88 | 282.84 | 201.01 | 237.6 | 617.74 | 523.52 | 182.52 |
| 广东 | 3055.17 | 353.23 | 564.56 | 356.27 | 811.88 | 873.06 | 1082.82 | 420.81 |
| 广西 | 2033.87 | 300.82 | 338.65 | 157.78 | 329.06 | 621.74 | 587.02 | 218.27 |
| 海南 | 2057.86 | 186.44 | 202.72 | 171.79 | 329.65 | 477.17 | 312.93 | 279.19 |
| 重庆 | 2303.29 | 589.99 | 516.21 | 236.55 | 403.92 | 730.05 | 438.41 | 225.8 |
| 四川 | 1974.28 | 507.76 | 344.79 | 203.21 | 240.24 | 575.1 | 430.36 | 223.46 |
| 贵州 | 1673.82 | 437.75 | 461.61 | 153.32 | 254.66 | 445.59 | 346.11 | 191.48 |
| 云南 | 2194.25 | 537.01 | 369.07 | 249.54 | 290.84 | 561.91 | 407.7 | 330.95 |
| 西藏 | 2646.61 | 839.7 | 204.44 | 209.11 | 379.3 | 371.04 | 269.59 | 389.33 |
| 陕西 | 1472.95 | 390.89 | 447.95 | 259.51 | 230.61 | 490.9 | 469.1 | 191.34 |
| 甘肃 | 1525.57 | 472.98 | 328.9 | 219.86 | 206.65 | 449.69 | 249.66 | 228.19 |
| 青海 | 1654.69 | 437.77 | 258.78 | 303 | 244.93 | 479.53 | 288.56 | 236.51 |
| 宁夏 | 1375.46 | 480.89 | 273.84 | 317.32 | 251.08 | 424.75 | 228.73 | 195.93 |
| 新疆 | 1608.82 | 536.05 | 432.46 | 235.82 | 250.28 | 541.3 | 344.85 | 214.4 |
import pandas as pd
data = pd.read_excel("居民家庭人均全年消费性支出.xlsx")
# 支出数据
cost = data[data.columns[1:]]
# 相关系数
asso = association(cost)
print(asso.shape)
# 降维旋转矩阵: 城市发达程度
rota = principal(asso)
print(pd.DataFrame(rota, index=data.columns[1:]))
# 对数据进行降维
cost = np.array(cost @ rota)
print(cost)


