# # -*- coding: utf-8 -*-
# """
# Created on Sat Aug 25 10:39:22 2018
#
# @author: aoanng
# """
# from math import log
#
# ##创建数据集
# def createDataSet():
#     """
#     创建数据集
#     """
#     dataSet = [['青年', '否', '否', '一般', '拒绝'],
#                 ['青年', '否', '否', '好', '拒绝'],
#                 ['青年', '是', '否', '好', '同意'],
#                 ['青年', '是', '是', '一般', '同意'],
#                 ['青年', '否', '否', '一般', '拒绝'],
#                 ['中年', '否', '否', '一般', '拒绝'],
#                 ['中年', '否', '否', '好', '拒绝'],
#                 ['中年', '是', '是', '好', '同意'],
#                 ['中年', '否', '是', '非常好', '同意'],
#                 ['中年', '否', '是', '非常好', '同意'],
#                 ['老年', '否', '是', '非常好', '同意'],
#                 ['老年', '否', '是', '好', '同意'],
#                 ['老年', '是', '否', '好', '同意'],
#                 ['老年', '是', '否', '非常好', '同意'],
#                 ['老年', '否', '否', '一般', '拒绝'],
#                 ]
#     featureName = ['年龄', '有工作', '有房子', '信贷情况']
#     # 返回数据集和每个维度的名称
#     return dataSet, featureName
#
# ##分割数据集
# def splitDataSet(dataSet,axis,value):
#     """
#     按照给定特征划分数据集
#     :param axis:划分数据集的特征的维度
#     :param value:特征的值
#     :return: 符合该特征的所有实例（并且自动移除掉这维特征）
#     """
#
#     # 循环遍历dataSet中的每一行数据
#     retDataSet = []
#     for featVec in dataSet:
#         if featVec[axis] == value:
#             reduceFeatVec = featVec[:axis] # 删除这一维特征
#             reduceFeatVec.extend(featVec[axis+1:])
#             retDataSet.append(reduceFeatVec)
#     return retDataSet
#
# ##计算信息熵
# # 计算的始终是类别标签的不确定度
# def calcShannonEnt(dataSet):
#     """
#     计算训练数据集中的Y随机变量的香农熵
#     :param dataSet:
#     :return:
#     """
#     numEntries = len(dataSet) # 实例的个数
#     labelCounts = {}
#     for featVec in dataSet: # 遍历每个实例，统计标签的频次
#         currentLabel = featVec[-1] # 表示最后一列
#         # 当前标签不在labelCounts map中，就让labelCounts加入该标签
#         if currentLabel not in labelCounts.keys():
#             labelCounts[currentLabel] =0
#         labelCounts[currentLabel] +=1
#
#     shannonEnt = 0.0
#     for key in labelCounts:
#         prob = float(labelCounts[key]) / numEntries
#         shannonEnt -= prob * log(prob,2) # log base 2
#     return shannonEnt
#
# ## 计算条件熵
# def calcConditionalEntropy(dataSet,i,featList,uniqueVals):
#     """
#     计算x_i给定的条件下，Y的条件熵
#     :param dataSet: 数据集
#     :param i: 维度i
#     :param featList: 数据集特征列表
#     :param unqiueVals: 数据集特征集合
#     :return: 条件熵
#     """
#     ce = 0.0
#     for value in uniqueVals:
#         subDataSet = splitDataSet(dataSet,i,value)
#         prob = len(subDataSet) / float(len(dataSet)) # 极大似然估计概率
#         ce += prob * calcShannonEnt(subDataSet) #∑pH(Y|X=xi) 条件熵的计算
#     return ce
#
# ##计算信息增益
# def calcInformationGain(dataSet,baseEntropy,i):
#     """
#     计算信息增益
#     :param dataSet: 数据集
#     :param baseEntropy: 数据集中Y的信息熵
#     :param i: 特征维度i
#     :return: 特征i对数据集的信息增益g(dataSet | X_i)
#     """
#     featList = [example[i] for example in dataSet] # 第i维特征列表
#     uniqueVals = set(featList) # 换成集合 - 集合中的每个元素不重复
#     newEntropy = calcConditionalEntropy(dataSet,i,featList,uniqueVals)#计算条件熵，
#     infoGain = baseEntropy - newEntropy # 信息增益 = 信息熵 - 条件熵
#     return infoGain
#
# ## 算法框架
# def chooseBestFeatureToSplitByID3(dataSet):
#     """
#     选择最好的数据集划分
#     :param dataSet:
#     :return:
#     """
#     numFeatures = len(dataSet[0]) -1 # 最后一列是分类
#     baseEntropy = calcShannonEnt(dataSet) #返回整个数据集的信息熵
#     bestInfoGain = 0.0
#     bestFeature = -1
#     for i in range(numFeatures): # 遍历所有维度特征
#         infoGain = calcInformationGain(dataSet,baseEntropy,i) #返回具体特征的信息增益
#         if(infoGain > bestInfoGain):
#             bestInfoGain = infoGain
#             bestFeature = i
#     return bestFeature # 返回最佳特征对应的维度
#
# def createTree(dataSet,featureName,chooseBestFeatureToSplitFunc = chooseBestFeatureToSplitByID3):
#     """
#     创建决策树
#     :param dataSet: 数据集
#     :param featureName: 数据集每一维的名称
#     :return: 决策树
#     """
#     classList = [example[-1] for example in dataSet] # 类别列表
#     if classList.count(classList[0]) == len(classList): # 统计属于列别classList[0]的个数
#         return classList[0] # 当类别完全相同则停止继续划分
#     if len(dataSet[0]) ==1: # 当只有一个特征的时候，遍历所有实例返回出现次数最多的类别
#         return majorityCnt(classList) # 返回类别标签
#     bestFeat = chooseBestFeatureToSplitFunc(dataSet)#最佳特征对应的索引
#     bestFeatLabel = featureName[bestFeat] #最佳特征
#     myTree ={bestFeatLabel:{}}  # map 结构，且key为featureLabel
#     del (featureName[bestFeat])
#     # 找到需要分类的特征子集
#     featValues = [example[bestFeat] for example in dataSet]
#     uniqueVals = set(featValues)
#     for value in uniqueVals:
#         subLabels = featureName[:] # 复制操作
#         myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet,bestFeat,value),subLabels)
#     return myTree
#
# # 测试决策树的构建
# dataSet,featureName = createDataSet()
# myTree = createTree(dataSet,featureName)
# print(myTree)

# -*- coding: utf-8 -*-
from math import log
import operator
import pickle
'''
输入：原始数据集、子数据集（最后一列为类别标签，其他为特征列）
功能：计算原始数据集、子数据集（某一特征取值下对应的数据集）的香农熵
输出：float型数值（数据集的熵值）
'''
def calcShannonEnt(dataset):
    numSamples = len(dataset)
    labelCounts = {}
    for allFeatureVector in dataset:
        currentLabel = allFeatureVector[-1]
        if currentLabel not in labelCounts.keys():
            labelCounts[currentLabel] = 0
        labelCounts[currentLabel] += 1
    entropy = 0.0
    for key in labelCounts:
        property = float(labelCounts[key])/numSamples
        entropy -= property * log(property,2)
    return entropy

'''
输入：无
功能：封装原始数据集
输出：数据集、特征标签
'''
def creatDataSet():
    dataset = [[1,1,'yes'],[1,1,'yes'],[1,0,'no'],[0,1,'no'],[0,0,'no']]
    labels = ['no surfacing','flippers']
    return dataset,labels

'''
输入：数据集、数据集中的某一特征所在列的索引、该特征某一可能取值（例如，（原始数据集、0,1 ））
功能：取出在该特征取值下的子数据集（子集不包含该特征）
输出：子数据集
'''
def getSubDataset(dataset,colIndex,value):
    subDataset = [] #用于存储子数据集
    for rowVector in dataset:
        if rowVector[colIndex] == value:
            #下边两句实现抽取除第colIndex列特征的其他特征取值
            subRowVector = rowVector[:colIndex]
            subRowVector.extend(rowVector[colIndex+1:])
            #将抽取的特征行添加到特征子数据集中
            subDataset.append(subRowVector)
    return subDataset

'''
输入：数据集
功能：选择最优的特征，以便得到最优的子数据集（可简单的理解为特征在决策树中的先后顺序）
输出：最优特征在数据集中的列索引
'''
def BestFeatToGetSubdataset(dataset):
    #下边这句实现：除去最后一列类别标签列剩余的列数即为特征个数
    numFeature = len(dataset[0]) - 1
    baseEntropy = calcShannonEnt(dataset)
    bestInfoGain = 0.0; bestFeature = -1
    for i in range(numFeature):#i表示该函数传入的数据集中每个特征
        # 下边这句实现抽取特征i在数据集中的所有取值
        feat_i_values = [example[i] for example in dataset]
        uniqueValues = set(feat_i_values)
        feat_i_entropy = 0.0
        for value in uniqueValues:
            subDataset = getSubDataset(dataset,i,value)
            #下边这句计算pi
            prob_i = len(subDataset)/float(len(dataset))
            feat_i_entropy += prob_i * calcShannonEnt(subDataset)
        infoGain_i = baseEntropy - feat_i_entropy
        if (infoGain_i > bestInfoGain):
            bestInfoGain = infoGain_i
            bestFeature = i
    return bestFeature

'''
输入：子数据集的类别标签列
功能：找出该数据集个数最多的类别
输出：子数据集中个数最多的类别标签
'''
def mostClass(ClassList):
    classCount = {}
    for class_i in ClassList:
        if class_i not in classCount.keys():
            classCount[class_i] = 0
        classCount[class_i] += 1
    sortedClassCount = sorted(classCount.iteritems(),
    key=operator.itemgetter(1),reverse = True)
    return sortedClassCount[0][0]

'''
输入：数据集，特征标签
功能：创建决策树（直观的理解就是利用上述函数创建一个树形结构）
输出：决策树（用嵌套的字典表示）
'''
def creatTree(dataset,labels):
    classList = [example[-1] for example in dataset]
    #判断传入的dataset中是否只有一种类别，是，返回该类别
    if classList.count(classList[0]) == len(classList):
        return classList[0]
    #判断是否遍历完所有的特征,是，返回个数最多的类别
    if len(dataset[0]) == 1:
        return mostClass(classList)
    #找出最好的特征划分数据集
    bestFeat = BestFeatToGetSubdataset(dataset)
    #找出最好特征对应的标签
    bestFeatLabel = labels[bestFeat]
    #搭建树结构
    myTree = {bestFeatLabel:{}}
    del (labels[bestFeat])
    #抽取最好特征的可能取值集合
    bestFeatValues = [example[bestFeat] for example in dataset]
    uniqueBestFeatValues = set(bestFeatValues)
    for value in uniqueBestFeatValues:
        #取出在该最好特征的value取值下的子数据集和子标签列表
        subDataset = getSubDataset(dataset,bestFeat,value)
        subLabels = labels[:]
        #递归创建子树
        myTree[bestFeatLabel][value] = creatTree(subDataset,subLabels)
    return myTree

'''
输入：测试特征数据
功能：调用训练决策树对测试数据打上类别标签
输出：测试特征数据所属类别
'''
def classify(inputTree,featlabels,testFeatValue):
    firstStr = inputTree.keys()[0]
    secondDict = inputTree[firstStr]
    featIndex = featlabels.index(firstStr)
    for firstStr_value in secondDict.keys():
        if testFeatValue[featIndex] == firstStr_value:
            if type(secondDict[firstStr_value]).__name__ == 'dict':
                classLabel = classify(secondDict[firstStr_value],featlabels,testFeatValue)
            else: classLabel = secondDict[firstStr_value]
    return classLabel


'''
输入：训练树，存储的文件名
功能：训练树的存储
输出：
'''
def storeTree(trainTree,filename):

    fw = open(filename,'w')
    pickle.dump(trainTree,fw)
    fw.close()
def grabTree(filename):

    fr = open(filename)
    return pickle.load(fr)


if __name__ == '__main__':
    dataset,labels = creatDataSet()
    storelabels = labels[:]#复制label
    trainTree = creatTree(dataset,labels)
    classlabel = classify(trainTree,storelabels,[0,1])
    print(classlabel)