#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
    Time    : 2018/7/19 下午2:38
    Author  : wangjf
    File    : pca.py
    GitHub  : https://github.com/wjf0627
"""

import matplotlib.pyplot as plt
from numpy import *


def loadDataSet(fileName, delim='\t'):
    fr = open(fileName)
    stringArr = [line.strip().split(delim) for line in fr.readlines()]
    dataArr = [list(map(float, line)) for line in stringArr]
    #   注意这里和 Python2 的区别，需要在 map 函数外加一个 list()，否则显示为 map at 0x3fed1d0
    return mat(dataArr)


def pca(dataMat, topNfeat=9999999):
    """
    :param dataMat:
        原数据集矩阵
    :param topNfeat:
        应用的 N 个 特征
    :return:
        lowDDataMat 降维后数据集
        reconMat    新的数据集空间
    """

    #   计算每一列的均值
    meanVals = mean(dataMat, axis=0)
    print('meanVals', meanVals)

    #   每个向量同事都减去均值
    meanRemoved = dataMat - meanVals
    print('meanRemoved', meanRemoved)

    #   cov协方差=[(x1-x均值)*(y1-y均值)+(x2-x均值)*(y2-y均值)+...+(xn-x均值)*(yn-y均值)+]/(n-1)
    '''
    方差：（一维）度量两个随机变量关系的统计量
    协方差： （二维）度量各个维度偏离其均值的程度
    协方差矩阵：（多维）度量各个维度偏离其均值的程度
    当 cov(X, Y)>0时，表明X与Y正相关；(X越大，Y也越大；X越小Y，也越小。这种情况，我们称为“正相关”。)
    当 cov(X, Y)<0时，表明X与Y负相关；
    当 cov(X, Y)=0时，表明X与Y不相关。
    '''
    covMat = cov(meanRemoved, rowvar=0)

    #   eigVals 为特征值，eigVects 为特征向量
    eigVals, eigVects = linalg.eig(mat(covMat))
    print('eigVals = ', eigVals)
    print('eigVects = ', eigVects)
    #   对特征值，进行从小到大的排序，返回从小到大的 index 序号
    #   特征值的逆序就可以得到 topNfeat 个最大的特征向量
    '''
    >>> x = np.array([3,1,2])
    >>> np.argsort(x)
    array([1, 2, 0])  # index,1 = 1; index,2 = 2; index,0 = 3
    >>> y = np.argsort(x)
    >>> y[::-1]
    array([0, 2, 1])
    >>> y[:-3:-1]
    array([0, 2])  # 取出 -1, -2
    >>> y[:-6:-1]
    array([0, 2, 1])
    '''
    eigValInd = argsort(eigVals)
    print('eigValInd = ', eigValInd)
    #   -1 表示倒序，返回 topN 的特征值 [-1 到 -(topNfeat + 1)] 但是不包括 -(topNfeat + 1)本身的倒序]
    eigValInd = eigValInd[:-(topNfeat + 1):-1]
    print('eigValInd = ', eigValInd)
    #   重组 eigVects 最大到最小
    redEigVects = eigVects[:, eigValInd]
    print('redEigVects = ', redEigVects.T)

    #   将数据转换到新空间
    print('----', shape(meanRemoved), shape(redEigVects))

    lowDDataMat = meanRemoved * redEigVects
    reconMat = (lowDDataMat * redEigVects.T) + meanVals
    print('lowDDataMat = ', lowDDataMat)
    print('reconMat = ', reconMat)
    return lowDDataMat, reconMat


def replaceNanWithMean():
    dataMat = loadDataSet('/Users/wangjf/Downloads/machinelearninginaction/Ch13/secom.data', ' ')
    numFeat = shape(dataMat)[1]
    for i in range(numFeat):
        #   对 value 不为 Nan 的平均值
        #   .A 返回矩阵基于的数组
        meanVal = mean(dataMat[nonzero(~isnan(dataMat[:, i].A))[0], i])
        #   将 value 为 Nan 的值赋值为均值
        dataMat[nonzero(isnan(dataMat[:, i].A))[0], i] = meanVal
    return dataMat


def show_picture(dataMat, reconMat):
    fig = plt.figure()
    ax = fig.add_subplot(111)
    ax.scatter(dataMat[:, 0].flatten().A[0], dataMat[:, 1].flatten().A[0], marker='^', s=90)
    ax.scatter(reconMat[:, 0].flatten().A[0], reconMat[:, 1].flatten().A[0], marker='o', s=50, c='red')
    plt.show()


def analyse_data(dataMat):
    meanVals = mean(dataMat, axis=0)
    meanRemoved = dataMat - meanVals
    covMat = cov(meanRemoved, rowvar=0)
    eigVals, eigVects = linalg.eig(mat(covMat))
    eigvalInd = argsort(eigVals)

    topNfeat = 20
    eigvalInd = eigvalInd[:(topNfeat + 1):-1]
    cov_all_score = float(sum(eigVals))
    sum_cov_score = 0
    for i in range(0, len(eigvalInd)):
        line_cov_score = float(eigVals[eigvalInd[i]])
        sum_cov_score += line_cov_score
        '''
        我们发现其中有超过20% 的特征值都是0
        这就意味着这些特征都是其他特征的副本，也就是说，它们可以通过其他特征来表示，而本身并没有提供额外的信息
        
        最前面 15 个值的数量级大于 10^5，实际上那以后的值都变得非常小
        这就相当于告诉我们只有部分重要特征，重要特征的数目也很快就会下降
        
        最后，我们可能会注意到一些小的负值，他们主要源自数值误差应该四舍五入成 0
        '''
        print('主成分：%s，方差占比：%s%%，累加方差占比：%s%%'
              % (format(i + 1, '2.0f'), format(line_cov_score / cov_all_score * 100, '4.2f'),
                 format(sum_cov_score / cov_all_score * 100, '4.1f')))


if __name__ == "__main__":
    #   加载数据，并转化数据类型为 float
    dataMat = loadDataSet('/Users/wangjf/Downloads/machinelearninginaction/Ch13/testSet.txt')
    #   只需要一个特征向量
    #   lowDMat, reconMat = pca(dataMat, 1)
    #   只需要两个特征向量，和原始数据一致，没任何变化
    #   lowDMat, reconMat = pca(dataMat, 2)
    #   print(shape(lowDMat))

    #   show_picture(dataMat, reconMat)

    #   利用 PCA 对半导体制造数据降维
    dataMat = replaceNanWithMean()
    print(shape(dataMat))

    #   分析数据
    analyse_data(dataMat)
    lowDMat, reconMat = pca(dataMat, 20)
    print(shape(lowDMat))
    show_picture(dataMat, reconMat)
