决策树分类(一)

目录

系列文章目录

文章目录

前言

一、引言

 二、决策树的概论

三、决策树的一般流程:

四、引入信息论划分数据集

五、python实现计算信息熵

六、划分数据集

七、选择最好的数据集划分方式实现

八、递归构建决策树

九、使用Matplotlib绘制树形图

十、测试算法:使用决策树执行分类

十一、最终结果:

十二、比较K-NN和决策树的优缺点:

总结



前言

        本篇内容学习如何从一堆原始数据中构造决策树,首先我们先引出决策树的概论,在讨论构造决策树的方法、如何编写构造树的python代码、接着提出一些度量算法的成功率的方法以及构造分类器,并用Matplotlib绘制决策树图,最后输入数据 ,进行预测。


提示:以下是本篇文章正文内容,下面案例可供参考

一、引言

        你或多或少听说过20个问题的游戏,游戏的规则很简单:参与游戏的一方在脑海里像某个事物,其他参与者向他提出问题,只允许提20个问题,被问的参与者只能用对或者错去回答问题,问问题的人通过推断分析,逐步缩小被问问题参与者所猜想的事物。决策树的工作原理与此游戏类似,用户输入一系列数据然后给出游戏答案。

可见如下漫画:

决策树分类(一)_第1张图片

 二、决策树的概论

        学过数据结构的人都知道树的概念,决策树就是一颗树,不过它是我们在处理问题的时候有选择的进行抉择,不过,没有学过的也不用担心,下面我们通过一个例子说明:

下图是假象构造的邮件分类系统,它首先检测发送邮件的域名地址。如果域名地址为myEmployer.com,则将其放到分类“无聊时需要阅读的邮件”中,如果不是来自这个域名,则检查邮件里是由含有“曲棍球”单词,如果包含,则将邮件归类到“需要及时处理的朋友邮件”中,如果不包含,则将邮件归类到“无需阅读的垃圾邮件”

决策树分类(一)_第2张图片

 流程图形式的决策树(例子来源机器学习实践这本书)

         长方形代表判断模块,椭圆形代表终止模块(叶子节点),表示已经得出结论,可以终止运行,从判断模块引出的左右箭头称作分支,它可以到达另一个判断模块或者终止模块。

        决策树优点:计算复杂度不高,输出结果易于理解,对中间值的缺失不敏感,可以处理不相关特征数据。

        决策树缺点:可能产生过匹配问题。

        适用数据类型:数值型和标称型

三、决策树的一般流程:

        收集数据和准备数据:这里我所使用的是20场王者荣耀排位的数据,记录了四个特征值:双方杀敌人数差、经济差、输出伤害差、承受伤害差以及标签label

kills_differ economics_differ harms_differ bearharm_differ labels
-11 -8.4 -114.2 113.9 失败
-6 -4.5 29.2 -29.3 失败
2 3.8 46.9 -47.1 胜利
-5 -7.1 -43 42.9 失败
11 8.4 70.6 -70.4 胜利
6 8 70.8 -70.9 胜利
19 11 81.4 -73.1 胜利
12 4.9 16.3 -16.3 胜利
-12 -9.9 -33.9 33.7 失败
18 5 33.1 -31.2 胜利
12 5.8 79.4 -79.3 胜利
13 10 -30.8 30.8 胜利
-10 -6.9 -98.8 98.4 失败
-11 -6.3 -104.1 104.2 失败
-3 -0.3 -51.1 51.5 失败
-5 4.8 50.7 -50.6 胜利
15 5.8 82.1 -81.9 胜利
-7 -8.4 -1.9 1.9 失败
21 12.7 130.4 -130.8 胜利
-7 -3.8 -13.3 13.4 失败

        分析数据:构造树完成之后,我们应该检测图形是否符合预期。

        训练算法:构造树的数据结构

        测试算法:使用经验树计算错误率

        使用算法:此步骤可以适用于任何监督学习的算法,而使用决策树可以更好的理解数据的内在含义。

四、引入信息论划分数据集

        在构建决策树时,我们需要解决的第一个问题是,当前数据集哪个特征在划分数据类别时起到决定型作用。为找到决定性的特征,划分出最好的结果,我们必须要有一个评价每个特征的指标;我们引入了信息增益。

        信息增益:在划分数据集之前之后信息发生的变化。知道如何计算信息增益,我们就能计算每个特征值划分数据集获得的信息增益,获得信息增益最高的特征就是最好的选择

那么如何计算信息增益呢?

        首先我们应该知道什么是信息熵(香农熵)?

        :定义为信息的期望值(注意:和数学中我们计算的期望值是有区别的),明确这个概念之前,我们必须知道信息的定义。如果待分类的事物可能划分在多个分类之中,则符号xi的信息定义为

        l(x_{i})=-\log p(x_{i})

其中ix的下标,log是以2为底,p(xi)是选择该分类的概率。

计算熵:我们需要计算所有类别所有可能值包含的信息期望值,计算公式为:

H=-\sum_{i=1}^{n}p(x_{i})\log p(x_{i})

其中ix的下标,log是以2为底,p(xi)是选择该分类的概率,n为分类的数目。

计算信息增益

Gain(D,v)=H(D)-\sum_{v=1}^{V}\frac{\left | D^{v} \right |}{\left | D \right |}*H(D^{v})

H(D)为标签类别的信息熵,H(D^{v})为特征的信息熵,D^{v}为特征取值为某一个值得数量

五、python实现计算信息熵

        下面是未进行封装的代码,这里我是对最后一列,即标签(胜利、失败)进行统计,计算出胜利和失败的信息熵,你可以根据自己的需要统计并计算对应特征的信息熵

from math import log
import pandas as pd


#引入pandas包打开excel表格
excel = pd.ExcelFile("E:/Learn_work/Decision_data/WZ_data.xlsx")
#读取excel内容
ft = pd.read_excel(excel)

#print(ft)

#下面计算给定数据集的信息熵(香农熵)
ft1 = np.array(ft)  #将读取的excel表格里的内容转化为数组形式
#print(ft1)
numEntries = len(ft1)    #计算数据集中实例的总是
#print("\n",numEntries)
labelsCounts = {}    #显示的声明一个变量保存实例总数,创建一个字典
for feat in ft1:
    currentLabels = feat[-1] #获取数组的最后一列值,即为标签,这里可以根据你自己的需要进行改变
    #print(currentLabel)
    if currentLabels not in labelsCounts.keys():     #如果当前currentLable没有在labelsCounts.key()(labelsCounts的键)中,则扩展字典并将当前键值加入字典
        labelsCounts[currentLabels] = 0 
        print(labelsCounts[currentLabels])
    labelsCounts[currentLabels] += 1    #每个键值记录了当前类别出现的次数
    print(labelsCounts[currentLabels])

shannonEnt = 0.0    #初始化信息熵
for key in labelsCounts:
    prob = float(labelsCounts[key])/numEntries  #计算出现的概率
    shannonEnt -= prob*log(prob,2)  #计算信息熵
print("信息熵:"shannonEnt) 

 信息熵:0.9927744539878084

下面是上述代码的封装

def Oper_non(dataset):  #参数dataSet为数据集
    numEntries = len(dataset)    #计算数据集中实例的总数
    #print("\n",numEntries)
    labelsCounts = {}    #显示的声明一个变量保存实例总数,创建一个字典
    for feat in dataset:
        currentLabels = feat[-1] #获取数组的最后一列值,即为标签
        print(currentLabels)
    
        if currentLabels not in labelsCounts.keys():     #如果当前currentLable没有在labelsCounts.key()(labelsCounts的键)中,则扩展字典并将当前键值加入字典
            labelsCounts[currentLabels] = 0 
            print(labelsCounts[currentLabels])
        labelsCounts[currentLabels] += 1    #每个键值记录了当前类别出现的次数
        print(labelsCounts[currentLabels])

    shannonEnt = 0.0    #初始化信息熵
    for key in labelsCounts:
        prob = float(labelsCounts[key])/numEntries  #计算出现的概率
        shannonEnt -= prob*log(prob,2)  #计算信息熵
    print("信息熵",shannonEnt) 
    return shannonEnt

六、划分数据集

        前面我们已经学习并实现了如何计算信息熵,但我们还需要划分数据集,度量划分数据集的熵,判断当前是否正确划分数据集,我们将对每个特征划分数据集的结果计算一次信息熵,以便判断按照哪个特征划分数据集是最好的方式。

       下面这个是我没有封装成函数的python代码,featVec[0]是kills_differ这个特征的特征值,你可以在此处把0改为1、2、3……等,把-11改为你数据集中某一列特征中出现过的特征值:

#按照给定特征划分数据集,
Count = 0       #初始化
retDataSet = [] #创建一个列表
for featVec in ft1:     #循环列表,循环的时候是一行一行的,列如:featVec[0] = ft1[0,:],即featVec等于的是ft的第一行,featVec[0] = ft1第一行的第一个数
    # print("\n",featVec[0])        #所以循环print(featVec[0])输出的是第一列,即为特征的特征值

    if featVec[0] == -11:  #判断特征值
        #print("\n",featVec[0])

        reduceFeatVet = featVec[0]  #将判断为真的特征值赋值给reduceFeatVec,数据集这个列表中各个元素也是列表,我们需要遍历数据集中的每一个元素,一旦符合符合要求的值,则将其添加到新创建的列表中
        Count += 1          #记录这个特征值出现的次数
        #print(reduceFeatVet)
        # reduceFeatVet = np.append(reduceFeatVet,featVec[0+1:])  #extend()向列表尾部追加一个列表,将列表中的每个元素都追加进来,在原有列表上增加,a=[1,2,3]、b=[1,2,3],a.extend(b)为[1,2,3,1,2,3]。
        retDataSet.append(reduceFeatVet)    #将reduceFeatVec的值保存到retDataSet的列表中,a=[1,2,3]、b=[1,2,3],a.append(b)为[1,2,3,[1,2,3]]

print(retDataSet)
print(Count)

       下面是将上述代码封装成函数之后的代码:

#按照给定特征划分数据集
def splitDataSet(dataSet,axis,value):      #输入三个参数:dataSet为待划分得数据集,axis为划分数据集得特征,value为返回的特征的值
    Count = 0       #初始化
    retDataSet = [] #创建一个列表
    for featVec in dataSet:     #循环列表,循环的时候是一行一行的,列如:featVec[0] = ft1[0,:],即featVec等于的是ft的第一行,featVec[0] = ft1第一行的第一个数
        # print("\n",featVec[0])        #所以循环print(featVec[0])输出的是第一列,即为特征的特征值

        if featVec[axis] == value:  #判断,
            #print("\n",featVec[0])

            reduceFeatVet = featVec[axis]  #将判断为真的特征值赋值给reduceFeatVec,数据集这个列表中各个元素也是列表,我们需要遍历数据集中的每一个元素,一旦符合符合要求的值,则将其添加到新创建的列表中
            Count += 1          #记录这个特征值出现的次数
            #print(reduceFeatVet)
        
            retDataSet.append(reduceFeatVet)    #将reduceFeatVec的值保存到retDataSet的列表中,a=[1,2,3]、b=[1,2,3],a.append(b)为[1,2,3,[1,2,3]]

    print(retDataSet)
    print(Count)
    return retDataSet

        接下来我们将遍历整个数据集,循环计算香农熵和splitDataSet()函数,找到最好的特征划分方式。

七、选择最好的数据集划分方式实现

        在函数调用的数据需要满足一定的要求:

                1.数据必须是一种由列表元素组成的列表,而且所有的列表元素都要有相同的数据长度。

                2.数据的最后一列或者每一个实例的最后一个元素是当前实例的类别标签。

这里,会调用前面香农熵和splitDataSet()函数

#计算信息增益
def chooseBestFeatureToSplit(dataSet):     #设置两个参数dataSet是数据集,axis是特征
    numfeatures = len(dataSet[0])-1    #获取特征的长度,因为这里传入的ft1是矩阵,与前面的featVec是有区别的
    #print(numfeatures)
    baseEntropy = Oper_non(dataSet)  # 计算熵
    #print(baseEntropy)
    bestInfoGain = 0.0
    bestfeature = -1
    for i in range(numfeatures):  # 循环遍历数据集的所有特征
        featList = [example[i] for example in dataSet]  # 使用列表推导来创建新的列表,将数据集中所有第i个特征值或者所有可能存在的值写入这个新的list
        #print(featList)
        uniqueVals = set(featList)      #set()函数是表示生成集合,集合里的所有元素互异
        #print(uniqueVals)
        newEntropy = 0.0
        subdataSet = []
        for value in uniqueVals:  # 遍历当前特征中唯一的属性值,对每个唯一属性值划分一次数据集
            subDataSet = splitDataSet(dataSet, i, value)
            subdataSet.append(subDataSet)
            #print(subdataSet)
            prob = len(subdataSet) / float(len(dataSet))
            #print(prob)
            newEntropy += prob * Oper_non(subdataSet)  # 计算数据集某一特征总的新熵
            print("prob与特征的信息熵之积",newEntropy)
         # 下面为计算信息增益
        infoGain = baseEntropy - newEntropy
        if (infoGain > bestInfoGain):
            bestInfoGain = infoGain
            bestfeature = i
    return bestfeature

InforGain = chooseBestFeatureToSplit(ft1)   #调用函数

        到这里,我们从计算信息熵、信息增益到它们的代码实现已经完成,接下来,我们要开始构建决策树了。

八、递归构建决策树

        从上面,我们已经学习了从数据集构造决策树算法所需要的子模块功能,其工作原理如下:

        得到原始数据集,然后基于最好的属性值进行数据集划分,由于特征值可能有多个,因此可能存在大于两个分支的数据集划分,第一次划分之后,数据将被向下传递到树分支的下一个节点,在这个节点上,我们可以再次划分数据,因此采用递归的原则处理数据集。

        这里需要注意的是递归的条件:程序遍历完所有划分数据集的属性,或者每个分支下的所有实例都具有相同的分类。

# 创建树
def createTree(dataSet, labels):  # 输入两个参数,dataSet为数据集,labels为标签列表,标签列表包含了数据集中所有特征的标签
    classList = [example[-1] for example in dataSet]    #将数据集最后一列,即标签存储到classList中  
    
    '''利用推导公式:例如:list = [2,3,4,5]
        for i in range(list):
            list1 = i**2
            print(list1)
    等同于list1 = [i**2 for i in range(list) ]
    '''

    if classList.count(classList[0]) == len(classList):   #递归的第一个停止条件,所有的类标签完全相同,则直接返回该类标签                      
        return classList[0]                                                    
    if len(dataSet[0]) == 1:     #递归的第二个停止条件:使用完了所有特征,任然不能将数据集划分成仅包含唯一类别的分组                                           
        return majorityCny(classList)       #使用majorityCny函数挑选出现次数最多的类别作为返回值
    bestfeat = chooseBestFeatureToSplit(dataSet)    #在chooseBestFeatureToSplit()函数中,最后返回的是i值,即为特征
    print("最好的特征",bestfeat)
    bestfeatLabel = labels[bestfeat]
    print("最好的标签",bestfeatLabel)
    myTree = {bestfeatLabel: {}}    #创建树,使用字典存储树的信息
    #print(myTree)
    #del (labels[bestfeat])      #del()函数,删除一个或者连续几个元素,del(a[0])表示删除第0个元素
    featValues = [ example[bestfeat] for example in dataSet]
    # print(featValues)
    uniqueVals = set(featValues)
    for value in uniqueVals:
        
        subLabels = labels[:]
        
        myTree[bestfeatLabel][value] = createTree(splitDataSet(dataSet, bestfeat, value), subLabels)
    return myTree
characts = ['kills_differ','economics_differ','harms_differ','bearharm_differ'] #特征
Tree = createTree(ft1,characts)

九、使用Matplotlib绘制树形图

import matplotlib.pyplot as plt
import matplotlib

#用Matplotlib注解绘制树形图

# 定义文本框和箭头格式
decisionNode = dict(boxstyle="square", fc="0.8")  #boxstyle文本框样式、fc=”0.8” 是颜色深度
leafNode = dict(boxstyle="round4", fc="0.8")      #叶子节点
arrow_args = dict(arrowstyle="<-")                #定义箭头
 
# 绘制带箭头的注解
def plotNode(nodeTxt, centerPt, parentPt, nodeType):    #此函数执行绘制功能
    #createPlot.ax1是表示: ax1是函数createPlot的一个属性
    createPlot.ax1.annotate(nodeTxt, xy=parentPt, xycoords='axes fraction',xytext=centerPt,
                            textcoords='axes fraction',va="center", ha="center", bbox=nodeType, arrowprops=arrow_args)
 
# 获取叶节点的数目和树的层数
def getNumLeafs(myTree):
    numLeafs = 0                                      # 初始化
    firstStr = list(myTree.keys())[0]                 # 获得第一个key值(根节点)
    secondDict = myTree[firstStr]                     # 获得value值
    for key in secondDict.keys():
        if type(secondDict[key]).__name__ == 'dict':  # 测试节点的数据类型是否为字典
            numLeafs += getNumLeafs(secondDict[key])  # 递归调用函数
        else:
            numLeafs += 1
    return numLeafs
 
# 获取树的深度
def getTreeDepth(myTree):
    maxDepth = 0                                           # 初始化
    firstStr = list(myTree.keys())[0]                      # 获得第一个key值(根节点)
    secondDict = myTree[firstStr]                          # 获得value值
    for key in secondDict.keys():
        if type(secondDict[key]).__name__ == 'dict':       # 测试节点的数据类型是否为字典
            thisDepth = 1 + getTreeDepth(secondDict[key])  # 递归调用
        else:
            thisDepth = 1
        if thisDepth > maxDepth:
            maxDepth = thisDepth
    return maxDepth


# 在父子节点间填充文本信息
def plotMidText(cntrPt, parentPt, txtString):
    xMid = (parentPt[0] - cntrPt[0]) / 2.0 + cntrPt[0]
    yMid = (parentPt[1] - cntrPt[1]) / 2.0 + cntrPt[1]
    createPlot.ax1.text(xMid, yMid, txtString, va="center", ha="center", rotation=30)
 
# 画树
def plotTree(myTree, parentPt, nodeTxt):
    numLeafs = getNumLeafs(myTree)        # 获取树高
    depth = getTreeDepth(myTree)          # 获取树深度
    firstStr = list(myTree.keys())[0]     # 这个节点的文本标签
    cntrPt = (plotTree.xOff + (1.0 + float(numLeafs)) / 2.0 / plotTree.totalW, plotTree.yOff) #plotTree.totalW, plotTree.yOff全局变量,追踪已经绘制的节点,以及放置下一个节点的恰当位置
    plotMidText(cntrPt, parentPt, nodeTxt)                #标记子节点属性
    plotNode(firstStr, cntrPt, parentPt, decisionNode)
    secondDict = myTree[firstStr]
    plotTree.yOff = plotTree.yOff - 1.0 / plotTree.totalD  #减少y偏移
    for key in secondDict.keys():
        if type(secondDict[key]).__name__ == 'dict':
            plotTree(secondDict[key], cntrPt, str(key))
        else:
            plotTree.xOff = plotTree.xOff + 1.0 / plotTree.totalW
            plotNode(secondDict[key], (plotTree.xOff, plotTree.yOff), cntrPt, leafNode)
            plotMidText((plotTree.xOff, plotTree.yOff), cntrPt, str(key))
    plotTree.yOff = plotTree.yOff + 1.0 / plotTree.totalD
 
# 绘制决策树
def createPlot(inTree):
    fig = plt.figure(1, facecolor='white')                          # 创建一个新图形
    fig.clf()                                                       # 清空绘图区
    font = {'family': 'MicroSoft YaHei'}
    matplotlib.rc("font", **font)
    axprops = dict(xticks=[], yticks=[])
    createPlot.ax1 = plt.subplot(111, frameon=False, **axprops)
    plotTree.totalW = float(getNumLeafs(inTree))
    plotTree.totalD = float(getTreeDepth(inTree))
    plotTree.xOff = -0.5 / plotTree.totalW;
    plotTree.yOff = 1.0;
    plotTree(inTree, (0.5, 1.0), '')
    plt.show()

十、测试算法:使用决策树执行分类

        依靠训练数据构造的决策树之后,我们要将它运用到实际的数据分类中。在执行数据分类时,需要使用决策树以及构造决策树的标签向量。

#构建分类器
#测试算法:使用决策树执行分类
def classify(inputTree,featLabels,testVec):
    firstStr = inputTree.keys()[0]
    secondDict = inputTree[firstStr]
    featIndex = featLabels.index(firstStr)
    for key in secondDict.keys():
        if testVec[featIndex] == key:
            if type(secondDict[key])._name_ == 'dict':
                classLabel = classify((secondDict[key]),featLabels,testVec)
            else:
                classLabel = secondDict[key]
            return classLabel

十一、最终结果:

from math import log
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import pandas as pd
import operator


def Oper_non(dataset):  # 参数dataSet为数据集,axis为特征
    numEntries = len(dataset)  # 计算数据集中实例的总数
    # print("\n",numEntries)
    labelsCounts = {}  # 显示的声明一个变量保存实例总数,创建一个字典
    for feat in dataset:
        currentLabels = feat[-1]  # 获取数组的某一列特征,循环列表,循环的时候是一行一行的,列如:feat[0] = ft1[0,:],即featVec等于的是ft的第一行,featVec[0] = ft1第一行的第一个数
        #print(currentLabels)

        if currentLabels not in labelsCounts.keys():  # 如果当前currentLable没有在labelsCounts.key()(labelsCounts的键)中,则扩展字典并将当前键值加入字典
            labelsCounts[currentLabels] = 0
            #print(labelsCounts[currentLabels])
        labelsCounts[currentLabels] += 1  # 每个键值记录了当前类别出现的次数
        #print(labelsCounts[currentLabels])

    shannonEnt = 0.0  # 初始化信息熵
    for key in labelsCounts:
        prob = float(labelsCounts[key]) / numEntries  # 计算出现的概率
        shannonEnt -= prob * log(prob, 2)  # 计算信息熵
    #print("信息熵", shannonEnt)
    return shannonEnt

#ShangNon = Oper_non(ft1,-1)



# 按照给定特征划分数据集
def splitDataSet(dataSet, axis, value):  # 输入三个参数:dataSet为待划分得数据集,axis为划分数据集得特征,value为返回的特征的值
    Count = 0 
    retDataSet = [] #创建一个列表
    for featVec in dataSet:     #循环列表,循环的时候是一行一行的,列如:featVec[0] = ft1[0,:],即featVec等于的是ft的第一行,featVec[0] = ft1第一行的第一个数
        # print("\n",featVec[0])        #所以循环print(featVec[0])输出的是第一列,即为特征的特征值

        if featVec[axis] == value:  #判断,
            #print("\n",featVec[0])

            reduceFeatVet = featVec[axis]  #将判断为真的特征值赋值给reduceFeatVec,数据集这个列表中各个元素也是列表,我们需要遍历数据集中的每一个元素,一旦符合符合要求的值,则将其添加到新创建的列表中
            #Count += 1          #记录这个特征值出现的次数
            #print(reduceFeatVet)
            retDataSet.append(reduceFeatVet)    #将reduceFeatVec的值保存到retDataSet的列表中,a=[1,2,3]、b=[1,2,3],a.append(b)为[1,2,3,[1,2,3]]
    print(retDataSet)
    #print(Count)
    return retDataSet

#Character = splitDataSet(ft1,0,12)


#计算信息增益
def chooseBestFeatureToSplit(dataSet):     #参数dataSet是数据集
    numfeatures = len(dataSet[0])-1    #获取特征的长度,因为这里传入的ft1是矩阵,与前面的featVec是有区别的
    #print(numfeatures)
    baseEntropy = Oper_non(dataSet)  # 计算熵
    #print(baseEntropy)
    bestInfoGain = 0.0
    bestfeature = -1
    for i in range(numfeatures):  # 循环遍历数据集的所有特征
        featList = [example[i] for example in dataSet]  # 使用列表推导来创建新的列表,将数据集中所有第i个特征值或者所有可能存在的值写入这个新的list
        #print(featList)
        uniqueVals = set(featList)      #set()函数是表示生成集合,集合里的所有元素互异
        #print(uniqueVals)
        newEntropy = 0.0
        subdataSet = []
        for value in uniqueVals:  # 遍历当前特征中唯一的属性值,对每个唯一属性值划分一次数据集
            subDataSet = splitDataSet(dataSet, i, value)
            subdataSet.append(subDataSet)
            #print(subdataSet)
            prob = len(subdataSet) / float(len(dataSet))
            #print(prob)
            newEntropy += prob * Oper_non(subdataSet)  # 计算数据集某一特征总的新熵
           # print("prob与特征的信息熵之积",newEntropy)
         # 下面为计算信息增益
        infoGain = baseEntropy - newEntropy
        if (infoGain > bestInfoGain):
            bestInfoGain = infoGain
            bestfeature = i
    return bestfeature

InforGain = chooseBestFeatureToSplit(ft1[:,0:4])   #调用函数


# 投票抉择函数,此函数与KNN分类算法的分类器大同小异
def majorityCny(classList):
    classCount = {}
    for vote in classList: 
        if vote not in classCount.keys():   #判断,如果vote不在字典classCount里
            classCount[vote] = 0
            classCount[vote] += 1
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1),
                              reverse=True)  # sorted()是一个排序函数,iteritem()是列表的一个用法,即将键值对一起表现出来,itemgetter(1)是按照数组A的第二个元素进行排序,reverse=True表示从大到小进行排序
    return sortedClassCount[0][0]


# 创建树
def createTree(dataSet, charact):  # 输入两个参数,dataSet为数据集,charact为特征,这里应该传入特征,而不是标签,传入的参数charcat定义在此函数后面。
    classList = [example[-1] for example in dataSet]    #将数据集最后一列,即标签存储到classList中  
    
    '''利用推导公式:例如:list = [2,3,4,5]
        for i in range(list):
            list1 = i**2
            print(list1)
    等同于list1 = [i**2 for i in range(list) ]
    '''

    if classList.count(classList[0]) == len(classList):   #递归的第一个停止条件,所有的类标签完全相同,则直接返回该类标签                      
        return classList[0]                                                    
    if len(dataSet[0]) == 1:     #递归的第二个停止条件:使用完了所有特征,任然不能将数据集划分成仅包含唯一类别的分组                                           
        return majorityCny(classList)       #使用majorityCny函数挑选出现次数最多的类别作为返回值
    bestfeat = chooseBestFeatureToSplit(dataSet)    #在chooseBestFeatureToSplit()函数中,最后返回的是i值,即为特征
    #print(bestfeat)
    bestfeatLabel = charact[bestfeat]
    print("特征",bestfeatLabel)
    myTree = {bestfeatLabel: {}}    #创建树,使用字典存储树的信息
    #print(myTree)
    #del (labels[bestfeat])      #del()函数,删除一个或者连续几个元素,del(a[0])表示删除第0个元素
    featValues = [ example[bestfeat] for example in dataSet]
    # print(featValues)
    uniqueVals = set(featValues)
    for value in uniqueVals:
        for i in charact:
            
            subLabels = i
        
            myTree[bestfeatLabel][value] = createTree(splitDataSet(dataSet, bestfeat, value), subLabels)
    return myTree
characts = ['kills_differ','economics_differ','harms_differ','bearharm_differ']    #这里是特征,creatTree()函数调用它
Tree = createTree(ft1,characts)



#用Matplotlib注解绘制树形图

# 定义文本框和箭头格式
decisionNode = dict(boxstyle="square", fc="0.8")  #boxstyle文本框样式、fc=”0.8” 是颜色深度
leafNode = dict(boxstyle="round4", fc="0.8")      #叶子节点
arrow_args = dict(arrowstyle="<-")                #定义箭头
 
# 绘制带箭头的注解
def plotNode(nodeTxt, centerPt, parentPt, nodeType):    #此函数执行绘制功能
    #createPlot.ax1是表示: ax1是函数createPlot的一个属性
    createPlot.ax1.annotate(nodeTxt, xy=parentPt, xycoords='axes fraction',xytext=centerPt,
                            textcoords='axes fraction',va="center", ha="center", bbox=nodeType, arrowprops=arrow_args)
 
# 获取叶节点的数目和树的层数
def getNumLeafs(myTree):
    numLeafs = 0                                      # 初始化
    firstStr = list(myTree.keys())[0]                 # 获得第一个key值(根节点)
    secondDict = myTree[firstStr]                     # 获得value值
    for key in secondDict.keys():
        if type(secondDict[key]).__name__ == 'dict':  # 测试节点的数据类型是否为字典
            numLeafs += getNumLeafs(secondDict[key])  # 递归调用函数
        else:
            numLeafs += 1
    return numLeafs
 
# 获取树的深度
def getTreeDepth(myTree):
    maxDepth = 0                                           # 初始化
    firstStr = list(myTree.keys())[0]                      # 获得第一个key值(根节点)
    secondDict = myTree[firstStr]                          # 获得value值
    for key in secondDict.keys():
        if type(secondDict[key]).__name__ == 'dict':       # 测试节点的数据类型是否为字典
            thisDepth = 1 + getTreeDepth(secondDict[key])  # 递归调用
        else:
            thisDepth = 1
        if thisDepth > maxDepth:
            maxDepth = thisDepth
    return maxDepth


# 在父子节点间填充文本信息
def plotMidText(cntrPt, parentPt, txtString):
    xMid = (parentPt[0] - cntrPt[0]) / 2.0 + cntrPt[0]
    yMid = (parentPt[1] - cntrPt[1]) / 2.0 + cntrPt[1]
    createPlot.ax1.text(xMid, yMid, txtString, va="center", ha="center", rotation=30)
 
# 画树
def plotTree(myTree, parentPt, nodeTxt):
    numLeafs = getNumLeafs(myTree)        # 获取树高
    depth = getTreeDepth(myTree)          # 获取树深度
    firstStr = list(myTree.keys())[0]     # 这个节点的文本标签
    cntrPt = (plotTree.xOff + (1.0 + float(numLeafs)) / 2.0 / plotTree.totalW, plotTree.yOff) #plotTree.totalW, plotTree.yOff全局变量,追踪已经绘制的节点,以及放置下一个节点的恰当位置
    plotMidText(cntrPt, parentPt, nodeTxt)                #标记子节点属性
    plotNode(firstStr, cntrPt, parentPt, decisionNode)
    secondDict = myTree[firstStr]
    plotTree.yOff = plotTree.yOff - 1.0 / plotTree.totalD  #减少y偏移
    for key in secondDict.keys():
        if type(secondDict[key]).__name__ == 'dict':
            plotTree(secondDict[key], cntrPt, str(key))
        else:
            plotTree.xOff = plotTree.xOff + 1.0 / plotTree.totalW
            plotNode(secondDict[key], (plotTree.xOff, plotTree.yOff), cntrPt, leafNode)
            plotMidText((plotTree.xOff, plotTree.yOff), cntrPt, str(key))
    plotTree.yOff = plotTree.yOff + 1.0 / plotTree.totalD
 
# 绘制决策树
def createPlot(inTree):
    fig = plt.figure(1, facecolor='white')                          # 创建一个新图形
    fig.clf()                                                       # 清空绘图区
    font = {'family': 'MicroSoft YaHei'}
    matplotlib.rc("font", **font)
    axprops = dict(xticks=[], yticks=[])
    createPlot.ax1 = plt.subplot(111, frameon=False, **axprops)
    plotTree.totalW = float(getNumLeafs(inTree))
    plotTree.totalD = float(getTreeDepth(inTree))
    plotTree.xOff = -0.5 / plotTree.totalW;
    plotTree.yOff = 1.0;
    plotTree(inTree, (0.5, 1.0), '')
    plt.show()
Createplot = createPlot(Tree)

决策树分类(一)_第3张图片

 

        我这里的图只绘制出训练集的决策树形图,测试部分的代码没有进行,即第十条测试算法没有执行,

十二、比较K-NN和决策树的优缺点:

        K-NN:无法给出数据的内在含义

        决策树:主要优势在于数据形式非常容易理解

这里是我上传到百度网盘的数据集,和前面的第三条的数据一样,下载链接提取码如下:

链接:https://pan.baidu.com/s/1GBTOzE3kBS1UPFIaDzELzw 
提取码:1234

这里是上传至百度网盘的代码文档,下载链接提取码如下:

链接:https://pan.baidu.com/s/1G2FbOCMsptVyyARWFz3WQw 
提取码:1234


总结

        从我们用Matplotlib绘制的图中,我们更能直观的看出我们决策的过程,对比之前KNN分类算法,决策树的分类算法更直观。后续我们还将扩展另外一种划分数据集方法——基尼指数,完成树处理,即树的预剪枝、后预剪枝等

        好了,各位码友们本篇文章到此就结束了,希望这篇文章能给你们带一点点价值,也希望我们相互学习,如果大家有什么建议,或者平时看到有什么好的书,或者文章都可以在下面留言,也可以私信我。

你可能感兴趣的:(机器学习,python,pandas)