三瞎颗、決策樹可視化
這里代碼都是關(guān)于Matplotlib的坐搔,如果對(duì)于Matplotlib不了解的,可以先學(xué)習(xí)下漠嵌,Matplotlib的內(nèi)容這里就不再累述咐汞。可視化需要用到的函數(shù):
getNumLeafs:獲取決策樹葉子結(jié)點(diǎn)的數(shù)目
getTreeDepth:獲取決策樹的層數(shù)
plotNode:繪制結(jié)點(diǎn)
plotMidText:標(biāo)注有向邊屬性值
plotTree:繪制決策樹
createPlot:創(chuàng)建繪制面板
我對(duì)可視化決策樹的程序進(jìn)行了詳細(xì)的注釋儒鹿,直接看代碼化撕,調(diào)試查看即可。為了顯示中文挺身,需要設(shè)置FontProperties侯谁,代碼編寫如下:
# -*- coding: UTF-8 -*-
from matplotlib.font_manager import FontProperties
import matplotlib.pyplot as plt
from math import log
import operator
"""
函數(shù)說明:計(jì)算給定數(shù)據(jù)集的經(jīng)驗(yàn)熵(香農(nóng)熵)
def calcShannonEnt(dataSet):
? ? numEntires = len(dataSet)? ? ? ? ? ? ? ? ? ? ? ? #返回?cái)?shù)據(jù)集的行數(shù)
? ? labelCounts = {}? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? #保存每個(gè)標(biāo)簽(Label)出現(xiàn)次數(shù)的字典
? ? for featVec in dataSet:? ? ? ? ? ? ? ? ? ? ? ? ? ? #對(duì)每組特征向量進(jìn)行統(tǒng)計(jì)
? ? ? ? currentLabel = featVec[-1]? ? ? ? ? ? ? ? ? ? #提取標(biāo)簽(Label)信息
? ? ? ? if currentLabel not in labelCounts.keys():? ? #如果標(biāo)簽(Label)沒有放入統(tǒng)計(jì)次數(shù)的字典,添加進(jìn)去
? ? ? ? ? ? labelCounts[currentLabel] = 0
? ? ? ? labelCounts[currentLabel] += 1? ? ? ? ? ? ? ? #Label計(jì)數(shù)
? ? shannonEnt = 0.0? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? #經(jīng)驗(yàn)熵(香農(nóng)熵)
? ? for key in labelCounts:? ? ? ? ? ? ? ? ? ? ? ? ? ? #計(jì)算香農(nóng)熵
? ? ? ? prob = float(labelCounts[key]) / numEntires? ? #選擇該標(biāo)簽(Label)的概率
? ? ? ? shannonEnt -= prob * log(prob, 2)? ? ? ? ? ? #利用公式計(jì)算
? ? return shannonEnt? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? #返回經(jīng)驗(yàn)熵(香農(nóng)熵)
"""
函數(shù)說明:創(chuàng)建測(cè)試數(shù)據(jù)集
"""
def createDataSet():
? ? dataSet = [[0, 0, 0, 0, 'no'],? ? ? ? ? ? ? ? ? ? ? ? #數(shù)據(jù)集
? ? ? ? ? ? [0, 0, 0, 1, 'no'],
? ? ? ? ? ? [0, 1, 0, 1, 'yes'],
? ? ? ? ? ? [0, 1, 1, 0, 'yes'],
? ? ? ? ? ? [0, 0, 0, 0, 'no'],
? ? ? ? ? ? [1, 0, 0, 0, 'no'],
? ? ? ? ? ? [1, 0, 0, 1, 'no'],
? ? ? ? ? ? [1, 1, 1, 1, 'yes'],
? ? ? ? ? ? [1, 0, 1, 2, 'yes'],
? ? ? ? ? ? [1, 0, 1, 2, 'yes'],
? ? ? ? ? ? [2, 0, 1, 2, 'yes'],
? ? ? ? ? ? [2, 0, 1, 1, 'yes'],
? ? ? ? ? ? [2, 1, 0, 1, 'yes'],
? ? ? ? ? ? [2, 1, 0, 2, 'yes'],
? ? ? ? ? ? [2, 0, 0, 0, 'no']]
? ? labels = ['年齡', '有工作', '有自己的房子', '信貸情況']? ? ? ? #特征標(biāo)簽
? ? return dataSet, labels? ? ? ? ? ? ? ? ? ? ? ? ? ? #返回?cái)?shù)據(jù)集和分類屬性
"""
函數(shù)說明:按照給定特征劃分?jǐn)?shù)據(jù)集
Parameters:
? ? dataSet - 待劃分的數(shù)據(jù)集
? ? axis - 劃分?jǐn)?shù)據(jù)集的特征
? ? value - 需要返回的特征的值
"""
def splitDataSet(dataSet, axis, value):? ? ?
? ? retDataSet = []? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? #創(chuàng)建返回的數(shù)據(jù)集列表
? ? for featVec in dataSet:? ? ? ? ? ? ? ? ? ? ? ? ? ? #遍歷數(shù)據(jù)集
? ? ? ? if featVec[axis] == value:
? ? ? ? ? ? reducedFeatVec = featVec[:axis]? ? ? ? ? ? ? ? #去掉axis特征
? ? ? ? ? ? reducedFeatVec.extend(featVec[axis+1:])? ? #將符合條件的添加到返回的數(shù)據(jù)集
? ? ? ? ? ? retDataSet.append(reducedFeatVec)
? ? return retDataSet? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? #返回劃分后的數(shù)據(jù)集
"""
函數(shù)說明:選擇最優(yōu)特征
Parameters:
? ? dataSet - 數(shù)據(jù)集
Returns:
? ? bestFeature - 信息增益最大的(最優(yōu))特征的索引值
Author:
? ? Jack Cui
"""
def chooseBestFeatureToSplit(dataSet):
? ? numFeatures = len(dataSet[0]) - 1? ? ? ? ? ? ? ? ? ? #特征數(shù)量
? ? baseEntropy = calcShannonEnt(dataSet)? ? ? ? ? ? ? ? #計(jì)算數(shù)據(jù)集的香農(nóng)熵
? ? bestInfoGain = 0.0? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? #信息增益
? ? bestFeature = -1? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? #最優(yōu)特征的索引值
? ? for i in range(numFeatures):? ? ? ? ? ? ? ? ? ? ? ? #遍歷所有特征
? ? ? ? #獲取dataSet的第i個(gè)所有特征
? ? ? ? featList = [example[i] for example in dataSet]
? ? ? ? uniqueVals = set(featList)? ? ? ? ? ? ? ? ? ? ? ? #創(chuàng)建set集合{},元素不可重復(fù)
? ? ? ? newEntropy = 0.0? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? #經(jīng)驗(yàn)條件熵
? ? ? ? for value in uniqueVals:? ? ? ? ? ? ? ? ? ? ? ? #計(jì)算信息增益
? ? ? ? ? ? subDataSet = splitDataSet(dataSet, i, value)? ? ? ? #subDataSet劃分后的子集
? ? ? ? ? ? prob = len(subDataSet) / float(len(dataSet))? ? ? ? ? #計(jì)算子集的概率
? ? ? ? ? ? newEntropy += prob * calcShannonEnt(subDataSet)? ? #根據(jù)公式計(jì)算經(jīng)驗(yàn)條件熵
? ? ? ? infoGain = baseEntropy - newEntropy? ? ? ? ? ? ? ? ? ? #信息增益
? ? ? ? # print("第%d個(gè)特征的增益為%.3f" % (i, infoGain))? ? ? ? ? ? #打印每個(gè)特征的信息增益
? ? ? ? if (infoGain > bestInfoGain):? ? ? ? ? ? ? ? ? ? ? ? ? ? #計(jì)算信息增益
? ? ? ? ? ? bestInfoGain = infoGain? ? ? ? ? ? ? ? ? ? ? ? ? ? #更新信息增益,找到最大的信息增益
? ? ? ? ? ? bestFeature = i? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? #記錄信息增益最大的特征的索引值
? ? return bestFeature? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? #返回信息增益最大的特征的索引值
"""
函數(shù)說明:統(tǒng)計(jì)classList中出現(xiàn)此處最多的元素(類標(biāo)簽)
Parameters:
? ? classList - 類標(biāo)簽列表
Returns:
? ? sortedClassCount[0][0] - 出現(xiàn)此處最多的元素(類標(biāo)簽)
Author:
? ? Jack Cui
"""
def majorityCnt(classList):
? ? classCount = {}
? ? for vote in classList:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? #統(tǒng)計(jì)classList中每個(gè)元素出現(xiàn)的次數(shù)
? ? ? ? if vote not in classCount.keys():classCount[vote] = 0?
? ? ? ? classCount[vote] += 1
? ? sortedClassCount = sorted(classCount.items(), key = operator.itemgetter(1), reverse = True)? ? ? ? #根據(jù)字典的值降序排序
? ? return sortedClassCount[0][0]? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? #返回classList中出現(xiàn)次數(shù)最多的元素
"""
函數(shù)說明:創(chuàng)建決策樹
Parameters:
? ? dataSet - 訓(xùn)練數(shù)據(jù)集
? ? labels - 分類屬性標(biāo)簽
? ? featLabels - 存儲(chǔ)選擇的最優(yōu)特征標(biāo)簽
Returns:
? ? myTree - 決策樹
Author:
? ? Jack Cui
"""
def createTree(dataSet, labels, featLabels):
? ? classList = [example[-1] for example in dataSet]? ? ? ? ? ? #取分類標(biāo)簽(是否放貸:yes or no)
? ? if classList.count(classList[0]) == len(classList):? ? ? ? ? ? #如果類別完全相同則停止繼續(xù)劃分
? ? ? ? return classList[0]
? ? if len(dataSet[0]) == 1 or len(labels) == 0:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? #遍歷完所有特征時(shí)返回出現(xiàn)次數(shù)最多的類標(biāo)簽
? ? ? ? return majorityCnt(classList)
? ? bestFeat = chooseBestFeatureToSplit(dataSet)? ? ? ? ? ? ? ? #選擇最優(yōu)特征
? ? bestFeatLabel = labels[bestFeat]? ? ? ? ? ? ? ? ? ? ? ? ? ? #最優(yōu)特征的標(biāo)簽
? ? featLabels.append(bestFeatLabel)
? ? myTree = {bestFeatLabel:{}}? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? #根據(jù)最優(yōu)特征的標(biāo)簽生成樹
? ? del(labels[bestFeat])? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? #刪除已經(jīng)使用特征標(biāo)簽
? ? featValues = [example[bestFeat] for example in dataSet]? ? ? ? #得到訓(xùn)練集中所有最優(yōu)特征的屬性值
? ? uniqueVals = set(featValues)? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? #去掉重復(fù)的屬性值
? ? for value in uniqueVals:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? #遍歷特征,創(chuàng)建決策樹墙贱。?
? ? ? ? subLabels = labels[:]? ? ? ? ? ? ?
? ? ? ? myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), subLabels, featLabels)
? ? return myTree
"""
函數(shù)說明:獲取決策樹葉子結(jié)點(diǎn)的數(shù)目
def getNumLeafs(myTree):
? ? numLeafs = 0? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? #初始化葉子
? ? firstStr = next(iter(myTree))? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? #python3中myTree.keys()返回的是dict_keys,不在是list,所以不能使用myTree.keys()[0]的方法獲取結(jié)點(diǎn)屬性热芹,可以使用list(myTree.keys())[0]
? ? secondDict = myTree[firstStr]? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? #獲取下一組字典
? ? for key in secondDict.keys():
? ? ? ? if type(secondDict[key]).__name__=='dict':? ? ? ? ? ? ? ? #測(cè)試該結(jié)點(diǎn)是否為字典,如果不是字典惨撇,代表此結(jié)點(diǎn)為葉子結(jié)點(diǎn)
? ? ? ? ? ? numLeafs += getNumLeafs(secondDict[key])
? ? ? ? else:? numLeafs +=1
? ? return numLeafs
"""
函數(shù)說明:獲取決策樹的層數(shù)
"""
def getTreeDepth(myTree):
? ? maxDepth = 0? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? #初始化決策樹深度
? ? firstStr = next(iter(myTree))? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? #python3中myTree.keys()返回的是dict_keys,不在是list,所以不能使用myTree.keys()[0]的方法獲取結(jié)點(diǎn)屬性伊脓,可以使用list(myTree.keys())[0]
? ? secondDict = myTree[firstStr]? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? #獲取下一個(gè)字典
? ? for key in secondDict.keys():
? ? ? ? if type(secondDict[key]).__name__=='dict':? ? ? ? ? ? ? ? #測(cè)試該結(jié)點(diǎn)是否為字典,如果不是字典魁衙,代表此結(jié)點(diǎn)為葉子結(jié)點(diǎn)
? ? ? ? ? ? thisDepth = 1 + getTreeDepth(secondDict[key])
? ? ? ? else:? thisDepth = 1
? ? ? ? if thisDepth > maxDepth: maxDepth = thisDepth? ? ? ? ? ? #更新層數(shù)
? ? return maxDepth
"""
函數(shù)說明:繪制結(jié)點(diǎn)
Parameters:
? ? nodeTxt - 結(jié)點(diǎn)名
? ? centerPt - 文本位置
? ? parentPt - 標(biāo)注的箭頭位置
? ? nodeType - 結(jié)點(diǎn)格式
Returns:
? ? 無(wú)
Author:
? ? Jack Cui
"""
def plotNode(nodeTxt, centerPt, parentPt, nodeType):
? ? arrow_args = dict(arrowstyle="<-")? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? #定義箭頭格式
? ? font = FontProperties(fname=r"c:\windows\fonts\simsun.ttc", size=14)? ? ? ? #設(shè)置中文字體
? ? createPlot.ax1.annotate(nodeTxt, xy=parentPt,? xycoords='axes fraction',? ? #繪制結(jié)點(diǎn)
? ? ? ? xytext=centerPt, textcoords='axes fraction',
? ? ? ? va="center", ha="center", bbox=nodeType, arrowprops=arrow_args, FontProperties=font)
"""
函數(shù)說明:標(biāo)注有向邊屬性值
Parameters:
? ? cntrPt报腔、parentPt - 用于計(jì)算標(biāo)注位置
? ? txtString - 標(biāo)注的內(nèi)容
Returns:
? ? 無(wú)
Author:
? ? Jack Cui
"""
def plotMidText(cntrPt, parentPt, txtString):
? ? xMid = (parentPt[0]-cntrPt[0])/2.0 + cntrPt[0]? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? #計(jì)算標(biāo)注位置? ? ? ? ? ? ? ? ?
? ? yMid = (parentPt[1]-cntrPt[1])/2.0 + cntrPt[1]
? ? createPlot.ax1.text(xMid, yMid, txtString, va="center", ha="center", rotation=30)
"""
函數(shù)說明:繪制決策樹
Parameters:
? ? myTree - 決策樹(字典)
? ? parentPt - 標(biāo)注的內(nèi)容
? ? nodeTxt - 結(jié)點(diǎn)名
Returns:
? ? 無(wú)
Author:
? ? Jack Cui
"""
def plotTree(myTree, parentPt, nodeTxt):
? ? decisionNode = dict(boxstyle="sawtooth", fc="0.8")? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? #設(shè)置結(jié)點(diǎn)格式
? ? leafNode = dict(boxstyle="round4", fc="0.8")? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? #設(shè)置葉結(jié)點(diǎn)格式
? ? numLeafs = getNumLeafs(myTree)? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? #獲取決策樹葉結(jié)點(diǎn)數(shù)目,決定了樹的寬度
? ? depth = getTreeDepth(myTree)? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? #獲取決策樹層數(shù)
? ? firstStr = next(iter(myTree))? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? #下個(gè)字典? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
? ? cntrPt = (plotTree.xOff + (1.0 + float(numLeafs))/2.0/plotTree.totalW, plotTree.yOff)? ? #中心位置
? ? plotMidText(cntrPt, parentPt, nodeTxt)? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? #標(biāo)注有向邊屬性值
? ? plotNode(firstStr, cntrPt, parentPt, decisionNode)? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? #繪制結(jié)點(diǎn)
? ? secondDict = myTree[firstStr]? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? #下一個(gè)字典剖淀,也就是繼續(xù)繪制子結(jié)點(diǎn)
? ? plotTree.yOff = plotTree.yOff - 1.0/plotTree.totalD? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? #y偏移
? ? for key in secondDict.keys():? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
? ? ? ? if type(secondDict[key]).__name__=='dict':? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? #測(cè)試該結(jié)點(diǎn)是否為字典纯蛾,如果不是字典,代表此結(jié)點(diǎn)為葉子結(jié)點(diǎn)
? ? ? ? ? ? plotTree(secondDict[key],cntrPt,str(key))? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? #不是葉結(jié)點(diǎn)纵隔,遞歸調(diào)用繼續(xù)繪制
? ? ? ? else:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? #如果是葉結(jié)點(diǎn)翻诉,繪制葉結(jié)點(diǎn),并標(biāo)注有向邊屬性值? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? 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
"""
函數(shù)說明:創(chuàng)建繪制面板
Parameters:
? ? inTree - 決策樹(字典)
Returns:
? ? 無(wú)
Author:
? ? Jack Cui
"""
def createPlot(inTree):
? ? fig = plt.figure(1, facecolor='white')? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? #創(chuàng)建fig
? ? fig.clf()? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? #清空fig
? ? axprops = dict(xticks=[], yticks=[])
? ? createPlot.ax1 = plt.subplot(111, frameon=False, **axprops)? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? #去掉x捌刮、y軸
? ? plotTree.totalW = float(getNumLeafs(inTree))? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? #獲取決策樹葉結(jié)點(diǎn)數(shù)目
? ? plotTree.totalD = float(getTreeDepth(inTree))? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? #獲取決策樹層數(shù)
? ? plotTree.xOff = -0.5/plotTree.totalW; plotTree.yOff = 1.0;? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? #x偏移
? ? plotTree(inTree, (0.5,1.0), '')? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? #繪制決策樹
? ? plt.show()? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? #顯示繪制結(jié)果? ?
if __name__ == '__main__':
? ? dataSet, labels = createDataSet()
? ? featLabels = []
? ? myTree = createTree(dataSet, labels, featLabels)
? ? print(myTree)?
? ? createPlot(myTree)
不出意外的話碰煌,我們就可以得到如下結(jié)果,可以看到?jīng)Q策樹繪制完成绅作。plotNode函數(shù)的工作就是繪制各個(gè)結(jié)點(diǎn)芦圾,比如有自己的房子、有工作俄认、yes个少、no,包括內(nèi)結(jié)點(diǎn)和葉子結(jié)點(diǎn)眯杏。plotMidText函數(shù)的工作就是繪制各個(gè)有向邊的屬性稍算,例如各個(gè)有向邊的0和1。這部分內(nèi)容呢役拴,個(gè)人感覺可以選擇性掌握,能掌握最好钾埂,不能掌握可以放一放河闰,因?yàn)楹竺鏁?huì)介紹一個(gè)更簡(jiǎn)單的決策樹可視化方法∪熳希看到這句話姜性,是不是想偷懶不仔細(xì)看這部分的代碼了?免費(fèi)視頻教程:www.mlxs.top? ??