数据降维:主成分分析法(PCA)
主成分分析是一种最常用的无监督降维方法,通过降维技术把多个变量化为少数几个主成分的统计分析方法。这些主成分能够反映原始变量的绝大部分信息,它们通常表示为原始变量的某种线性组合。
手 执 烟 火 以 谋 生 , 心 怀 诗 意 以 谋 爱
目录
此文为学习过程所写,如有错误,敬请指正。
1.原理介绍
在很多场景中需要对多变量数据进行观测,在一定程度上增加了数据采集的工作量。更重要的是:多变量之间可能存在相关性,从而增加了问题分析的复杂性。如果对每个指标进行单独分析,其分析结果往往是孤立的,不能完全利用数据中的信息,因此盲目减少指标会损失很多有用的信息,从而产生错误的结论。
所以需要找到一种合理的方法,在减少需要分析的指标同时,尽量减少原指标包含信息的损失,以达到对所收集数据进行全面分析的目的。由于各变量之间存在一定的相关关系,因此可以考虑将关系紧密的变量变成尽可能少的新变量,使这些新变量是两两不相关的,那么就可以用较少的综合指标分别代表存在于各个变量中的各类信息。
主成分分析是一种最常用的无监督降维方法,通过降维技术把多个变量化为少数几个主成分的统计分析方法。这些主成分能够反映原始变量的绝大部分信息,它们通常表示为原始变量的某种线性组合。
2.步骤详解
2.1 获取数据
假设现有一组数据,有m条数据,每条数据都有n个评价指标,构成了m*n的原始数据矩阵,即为X,每个变量对应的数据记为X1,X2,X3......Xn。
2.2 数据中心化 (标准化)
不同评价指标往往具有不同的量纲和量纲单位,这样的情况会影响到数据分析的结果,为了消除指标之间的量纲影响,需要进行数据标准化处理,以解决数据指标之间的可比性。原始数据经过数据标准化处理后,各指标处于同一数量级,适合进行综合对比评价。
在这里我们采用零均值法(z-score)对数据进行处理,得到均值为0,标准差为1的服从标准正态分布的数据。
其中,表示第j个指标的样本均值,
表示第j个指标的标准差,仍记中心化后数据矩阵为X。
2.3 求协方差矩阵
对中心化后数据求其协方差矩阵,记为R,则
或者另一种方法:
2.4计算协方差矩阵的特征值和特征向量
通过求协方差矩阵的特征方程:
解得其特征值有
对应的特征向量依次为:
2.5 确定主成分个数
设定一个贡献率阈值,即前p个主成分特征值的累计贡献率高于该值时即可认为这p个主成分可以表示原来n个变量,一般取0.8,0.85,0.9,0.95,0.99等。
2.6 计算主成分
在得到了主成分个数后,就可以利用前p个特征值对应的特征向量对主成分(降维后的数据)进行计算。
3.案例分析
3.1题目简介
已知判断某一水域水质情况的好坏时可以通过x1-x9共9种指标来评判,现有A-K共11条河流的指标测量情况,但是由于指标过多导致在评判以及确定每个指标的重要程度时带来了很大麻烦,所以请你利用一定的数学方法在尽量不损失原有数据信息的前提下减少评判指标。
x1 | x2 | x3 | x4 | x5 | x6 | x7 | x8 | x9 | |
A | 88 | 63 | 17 | 50 | 53 | 49 | 64 | 24 | 32 |
B | 75 | 1 | 2 | 43 | 14 | 53 | 31 | 18 | 42 |
C | 79 | 48 | 73 | 62 | 43 | 48 | 72 | 76 | 98 |
D | 73 | 95 | 9 | 34 | 20 | 46 | 54 | 4 | 59 |
E | 36 | 70 | 53 | 5 | 100 | 6 | 74 | 25 | 43 |
F | 40 | 46 | 92 | 60 | 68 | 19 | 2 | 76 | 8 |
G | 20 | 78 | 35 | 98 | 83 | 70 | 35 | 58 | 1 |
H | 67 | 29 | 38 | 3 | 32 | 27 | 3 | 47 | 75 |
I | 38 | 30 | 28 | 72 | 46 | 20 | 50 | 42 | 74 |
J | 95 | 25 | 20 | 42 | 13 | 54 | 56 | 54 | 82 |
K | 40 | 32 | 27 | 14 | 22 | 59 | 6 | 81 |
3.2 读取数据
利用jxl包从Excel读取数据并输出
//读取数据
public double[][] read(String filepath) throws IOException, BiffException,WriteException {
//创建输入流
InputStream stream = new FileInputStream(filepath);
//获取Excel文件对象
Workbook rwb = Workbook.getWorkbook(stream);
//获取文件的指定工作表 默认的第一个
Sheet sheet = rwb.getSheet("Sheet1");
rows = sheet.getRows();
cols = sheet.getColumns();
double[][] orig = new double[rows][cols];
//row为行
for(int i=0;i<sheet.getRows();i++) {
for(int j=0;j<sheet.getColumns();j++) {
String[] str = new String[sheet.getColumns()];
Cell cell = null;
cell = sheet.getCell(j,i);
str[j] = cell.getContents();
orig[i][j] = Double.valueOf(str[j]);
//original.set(i, j, orig[i][j]);
}
}
return orig;
}
输出:
3.3中心化数据
采用均值0化方法对原数据中心化处理
/**
*
* 使每个样本的均值为0
*
* @param primary
* 原始二维数组矩阵
* @return averageArray 中心化后的矩阵
*/
public double[][] changeAverageToZero(double[][] primary) {
int n = primary.length;
int m = primary[0].length;
double[] sum = new double[m];
double[] average = new double[m];
double[][] averageArray = new double[n][m];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
sum[i] += primary[j][i];
}
average[i] = sum[i] / n;
}
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
averageArray[j][i] = primary[j][i] - average[i];
}
}
return averageArray;
}
输出:
3.4 求协方差矩阵
/**
*
* 计算协方差矩阵
*
* @param matrix
* 中心化后的矩阵
* @return result 协方差矩阵
*/
public double[][] getVarianceMatrix(double[][] matrix) {
int n = matrix.length;// 行数
int m = matrix[0].length;// 列数
double[][] result = new double[m][m];// 协方差矩阵
for (int i = 0; i < m; i++) {
for (int j = 0; j < m; j++) {
double temp = 0;
for (int k = 0; k < n; k++) {
temp += matrix[k][i] * matrix[k][j];
}
result[i][j] = temp / (n - 1);
}
}
/*或者用以下方法计算:X的转置乘以X,在除以行数
Matrix X=new Matrix(matrix);
result = X.transpose().times(X).getArray();
for (int i = 0; i < m; i++) {
for (int j = 0; j < m; j++) {
result[i][j] = result[i][j] / n;
}
}
*/
return result;
}
输出:
3.5 计算特征值
对角线上数据为特征值
/**
* 求特征值矩阵
*
* @param matrix
* 协方差矩阵
* @return result 向量的特征值二维数组矩阵
*/
public double[][] getEigenvalueMatrix(double[][] matrix) {
Matrix A = new Matrix(matrix);
// 由特征值组成的对角矩阵,eig()获取特征值
// A.eig().getD().print(10, 6);
double[][] result = A.eig().getD().getArray();
return result;
}
输出:
3.6 计算特征值对应的特征向量
/**
* 标准化矩阵(特征向量矩阵)
*
* @param matrix
* 特征值矩阵
* @return result 标准化后的二维数组矩阵
*/
public double[][] getEigenVectorMatrix(double[][] matrix) {
Matrix A = new Matrix(matrix);
// A.eig().getV().print(6, 2);
double[][] result = A.eig().getV().getArray();
return result;
}
输出:
3.7 确定主成分个数并计算主成分矩阵
/**
* 寻找主成分
*
* @param prinmaryArray
* 原始二维数组数组
* @param eigenvalue
* 特征值二维数组
* @param eigenVectors
* 特征向量二维数组
* @return principalMatrix 主成分矩阵
*/
public Matrix getPrincipalComponent(double[][] primaryArray,
double[][] eigenvalue, double[][] eigenVectors) {
Matrix A = new Matrix(eigenVectors);// 定义一个特征向量矩阵
double[][] tEigenVectors = A.transpose().getArray();// 特征向量转置
Map<Integer, double[]> principalMap = new HashMap<Integer, double[]>();// key=主成分特征值,value=该特征值对应的特征向量
TreeMap<Double, double[]> eigenMap = new TreeMap<Double, double[]>(
Collections.reverseOrder());// key=特征值,value=对应的特征向量;初始化为翻转排序,使map按key值降序排列
double total = 0;// 存储特征值总和
int index = 0, n = eigenvalue.length;
double[] eigenvalueArray = new double[n];// 把特征值矩阵对角线上的元素放到数组eigenvalueArray里
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (i == j)
eigenvalueArray[index] = eigenvalue[i][j];
}
index++;
}
for (int i = 0; i < tEigenVectors.length; i++) {
double[] value = new double[tEigenVectors[0].length];
value = tEigenVectors[i];
eigenMap.put(eigenvalueArray[i], value);
}
// 求特征总和
for (int i = 0; i < n; i++) {
total += eigenvalueArray[i];
}
// 选出前几个主成分
double temp = 0;
int principalComponentNum = 0;// 主成分数
List<Double> plist = new ArrayList<Double>();// 主成分特征值
for (double key : eigenMap.keySet()) {
if (temp / total <= threshold) {
temp += key;
plist.add(key);
principalComponentNum++;
}
}
System.out.println("\n" + "当前阈值: " + threshold);
System.out.println("取得的主成分数: " + principalComponentNum + "\n");
System.out.println("主成分(特征向量)对应的特征值和其贡献率依次为:");
for (int i = 0; i<principalComponentNum; i++) {
System.out.println(plist.get(i)+"\t"+plist.get(i)*100/total+"%");
}
// 往主成分map里输入数据
for (int i = 0; i < plist.size(); i++) {
if (eigenMap.containsKey(plist.get(i))) {
principalMap.put(i, eigenMap.get(plist.get(i)));
}
}
// 把map里的值存到二维数组里
double[][] principalArray = new double[principalMap.size()][];
Iterator<Entry<Integer, double[]>> it = principalMap.entrySet()
.iterator();
for (int i = 0; it.hasNext(); i++) {
principalArray[i] = it.next().getValue();
}
/*
double[][] principalArray1 = new double[principalArray.length][principalArray[0].length+1];
for(int i=0;i<principalArray1.length ;i++) {
for(int j=0;j<principalArray1.length ;j++) {
if(j==0) {
principalArray1[i][j] = plist.get(i);
}
else {
principalArray1[i][j] = principalArray[i][j-1];
}
}
}*/
Matrix principalMatrix = new Matrix(principalArray);
return principalMatrix;
}
输出:
3.8 计算主成分(降维后数据)
/**
* 矩阵相乘
*
* @param primary
* 原始二维数组
*
* @param matrix
* 主成分矩阵
*
* @return result 结果矩阵
*/
public Matrix getResult(double[][] primary, Matrix matrix) {
Matrix primaryMatrix = new Matrix(primary);
Matrix result = primaryMatrix.times(matrix.transpose());
return result;
}
输出:
4.完整代码(Java)
程序中对Excel数据进行操作的库为jxl,矩阵运算库为jama。
4.1 方法类 pca.java
package PCA;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import Jama.Matrix;
import jxl.Cell;
import jxl.Sheet;
import jxl.Workbook;
import jxl.read.biff.BiffException;
import jxl.write.WriteException;
/*
* 算法步骤:
* 1)将原始数据按列组成n行m列矩阵X
* 2)特征中心化。即每一维的数据都减去该维的均值,使每一维的均值都为0
* 3)求出协方差矩阵
* 4)求出协方差矩阵的特征值及对应的特征向量
* 5)将特征向量按对应的特征值大小从上往下按行排列成矩阵,取前k行组成矩阵p
* 6)Y=PX 即为降维到k维后的数据
*/
public class pca {
private static final double threshold = 0.99;// 特征值阈值
int rows,cols;
//输出二维矩阵
public void matrixoutput(double[][] x) {
for(int i=0;i<x.length;i++) {
for(int j=0;j<x[0].length;j++) {
System.out.print(x[i][j]+" ");
}
System.out.println();
}
}
//读取数据
public double[][] read(String filepath) throws IOException, BiffException,WriteException {
//创建输入流
InputStream stream = new FileInputStream(filepath);
//获取Excel文件对象
Workbook rwb = Workbook.getWorkbook(stream);
//获取文件的指定工作表 默认的第一个
Sheet sheet = rwb.getSheet("Sheet1");
rows = sheet.getRows();
cols = sheet.getColumns();
double[][] orig = new double[rows][cols];
//row为行
for(int i=0;i<sheet.getRows();i++) {
for(int j=0;j<sheet.getColumns();j++) {
String[] str = new String[sheet.getColumns()];
Cell cell = null;
cell = sheet.getCell(j,i);
str[j] = cell.getContents();
orig[i][j] = Double.valueOf(str[j]);
//original.set(i, j, orig[i][j]);
}
}
return orig;
}
/**
*
* 使每个样本的均值为0
*
* @param primary
* 原始二维数组矩阵
* @return averageArray 中心化后的矩阵
*/
public double[][] changeAverageToZero(double[][] primary) {
int n = primary.length;
int m = primary[0].length;
double[] sum = new double[m];
double[] average = new double[m];
double[][] averageArray = new double[n][m];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
sum[i] += primary[j][i];
}
average[i] = sum[i] / n;
}
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
averageArray[j][i] = primary[j][i] - average[i];
}
}
return averageArray;
}
/**
*
* 计算协方差矩阵
*
* @param matrix
* 中心化后的矩阵
* @return result 协方差矩阵
*/
public double[][] getVarianceMatrix(double[][] matrix) {
int n = matrix.length;// 行数
int m = matrix[0].length;// 列数
double[][] result = new double[m][m];// 协方差矩阵
for (int i = 0; i < m; i++) {
for (int j = 0; j < m; j++) {
double temp = 0;
for (int k = 0; k < n; k++) {
temp += matrix[k][i] * matrix[k][j];
}
result[i][j] = temp / (n - 1);
}
}
/*或者用以下方法计算:X的转置乘以X,在除以行数
Matrix X=new Matrix(matrix);
result = X.transpose().times(X).getArray();
for (int i = 0; i < m; i++) {
for (int j = 0; j < m; j++) {
result[i][j] = result[i][j] / n;
}
}
*/
return result;
}
/**
* 求特征值矩阵
*
* @param matrix
* 协方差矩阵
* @return result 向量的特征值二维数组矩阵
*/
public double[][] getEigenvalueMatrix(double[][] matrix) {
Matrix A = new Matrix(matrix);
// 由特征值组成的对角矩阵,eig()获取特征值
// A.eig().getD().print(10, 6);
double[][] result = A.eig().getD().getArray();
return result;
}
/**
* 标准化矩阵(特征向量矩阵)
*
* @param matrix
* 特征值矩阵
* @return result 标准化后的二维数组矩阵
*/
public double[][] getEigenVectorMatrix(double[][] matrix) {
Matrix A = new Matrix(matrix);
// A.eig().getV().print(6, 2);
double[][] result = A.eig().getV().getArray();
return result;
}
/**
* 寻找主成分
*
* @param prinmaryArray
* 原始二维数组数组
* @param eigenvalue
* 特征值二维数组
* @param eigenVectors
* 特征向量二维数组
* @return principalMatrix 主成分矩阵
*/
public Matrix getPrincipalComponent(double[][] primaryArray,
double[][] eigenvalue, double[][] eigenVectors) {
Matrix A = new Matrix(eigenVectors);// 定义一个特征向量矩阵
double[][] tEigenVectors = A.transpose().getArray();// 特征向量转置
Map<Integer, double[]> principalMap = new HashMap<Integer, double[]>();// key=主成分特征值,value=该特征值对应的特征向量
TreeMap<Double, double[]> eigenMap = new TreeMap<Double, double[]>(
Collections.reverseOrder());// key=特征值,value=对应的特征向量;初始化为翻转排序,使map按key值降序排列
double total = 0;// 存储特征值总和
int index = 0, n = eigenvalue.length;
double[] eigenvalueArray = new double[n];// 把特征值矩阵对角线上的元素放到数组eigenvalueArray里
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (i == j)
eigenvalueArray[index] = eigenvalue[i][j];
}
index++;
}
for (int i = 0; i < tEigenVectors.length; i++) {
double[] value = new double[tEigenVectors[0].length];
value = tEigenVectors[i];
eigenMap.put(eigenvalueArray[i], value);
}
// 求特征总和
for (int i = 0; i < n; i++) {
total += eigenvalueArray[i];
}
// 选出前几个主成分
double temp = 0;
int principalComponentNum = 0;// 主成分数
List<Double> plist = new ArrayList<Double>();// 主成分特征值
for (double key : eigenMap.keySet()) {
if (temp / total <= threshold) {
temp += key;
plist.add(key);
principalComponentNum++;
}
}
System.out.println("\n" + "当前阈值: " + threshold);
System.out.println("取得的主成分数: " + principalComponentNum + "\n");
System.out.println("主成分(特征向量)对应的特征值和其贡献率依次为:");
for (int i = 0; i<principalComponentNum; i++) {
System.out.println(plist.get(i)+"\t"+plist.get(i)*100/total+"%");
}
// 往主成分map里输入数据
for (int i = 0; i < plist.size(); i++) {
if (eigenMap.containsKey(plist.get(i))) {
principalMap.put(i, eigenMap.get(plist.get(i)));
}
}
// 把map里的值存到二维数组里
double[][] principalArray = new double[principalMap.size()][];
Iterator<Entry<Integer, double[]>> it = principalMap.entrySet()
.iterator();
for (int i = 0; it.hasNext(); i++) {
principalArray[i] = it.next().getValue();
}
/*
double[][] principalArray1 = new double[principalArray.length][principalArray[0].length+1];
for(int i=0;i<principalArray1.length ;i++) {
for(int j=0;j<principalArray1.length ;j++) {
if(j==0) {
principalArray1[i][j] = plist.get(i);
}
else {
principalArray1[i][j] = principalArray[i][j-1];
}
}
}*/
Matrix principalMatrix = new Matrix(principalArray);
return principalMatrix;
}
/**
* 矩阵相乘
*
* @param primary
* 原始二维数组
*
* @param matrix
* 主成分矩阵
*
* @return result 结果矩阵
*/
public Matrix getResult(double[][] primary, Matrix matrix) {
Matrix primaryMatrix = new Matrix(primary);
Matrix result = primaryMatrix.times(matrix.transpose());
return result;
}
}
4.2 主类 pcamain.java
package PCA;
import Jama.Matrix;
import jxl.read.biff.BiffException;
import jxl.write.WriteException;
import java.io.IOException;
public class pcamain {
public static void main(String[] args) throws IOException, BiffException, WriteException {
// TODO Auto-generated catch block
//SelectData selectData = new SelectData();
pca pca = new pca();
//获得样本集
double[][] primaryArray = pca.read("pca.xls");
System.out.println("--------------------原始数据矩阵---------------------");
//pca.matrixoutput(primaryArray);
Matrix A=new Matrix(primaryArray);
A.print(10, 3);
double[][] averageArray = pca.changeAverageToZero(primaryArray);
System.out.println();
System.out.println("--------------------均值0化后的数据-------------------");
System.out.println(averageArray.length + "行," + averageArray[0].length + "列");
//pca.matrixoutput(averageArray);
Matrix B=new Matrix(averageArray);
B.print(10, 3);
System.out.println();
System.out.println("---------------------协方差矩阵-----------------------");
double[][] varMatrix = pca.getVarianceMatrix(averageArray);
//pca.matrixoutput(varMatrix);
Matrix C=new Matrix(varMatrix);
C.print(10, 3);
System.out.println();
System.out.println("------------------特征值矩阵--------------------------");
double[][] eigenvalueMatrix = pca.getEigenvalueMatrix(varMatrix);
//pca.matrixoutput(eigenvalueMatrix);
Matrix D=new Matrix(eigenvalueMatrix);
D.print(15, 10);
System.out.println();
System.out.println("------------------特征向量矩阵-----------------------");
double[][] eigenVectorMatrix = pca.getEigenVectorMatrix(varMatrix);
//pca.matrixoutput(eigenVectorMatrix);
Matrix E=new Matrix(eigenVectorMatrix);
E.print(10, 3);
System.out.println();
System.out.println("-------------------主成分矩阵-------------------------");
Matrix principalMatrix = pca.getPrincipalComponent(primaryArray, eigenvalueMatrix, eigenVectorMatrix);
principalMatrix.transpose().print(10, 3);
System.out.println();
System.out.println("--------------------降维后的矩阵------------------------");
Matrix resultMatrix = pca.getResult(primaryArray, principalMatrix);
int c = resultMatrix.getColumnDimension(); //列数
int r = resultMatrix.getRowDimension();//行数
System.out.print(r + "行," + c + "列");
resultMatrix.print(10, 3);
}
}
更多推荐
所有评论(0)