第六步: 绘制决策树的图像
我们通过得到决策树的深度和叶子结点的作用是:
1.完成对于图像整体比例的把握,叶子结点有n个,就把横坐标分成n份进行绘制结点的宽度,
2.同理深度有n层将纵坐标分成n份,完成结点的高度绘制。
3.并以此为基础,通过计算公式得到根结点和叶子结点的位置,还有父子结点之间的特征文本的位置。
4.并调用上几步函数完成结点绘制
# 所以结点的绘制过程是根左右深度遍历到最左边的叶子结点,确认是根直接绘制,。
# 然后把最左边最小的根节点的叶子结点绘制完往上递归绘制根节点,再绘制根节点的兄弟姐妹,再往上回退,再绘制。
def plotTree(myTree, parentPt, nodeTxt): # 输入当前的字典树,父节点,结点填充文本
numLeafs = getNumLeafs(myTree) # 得到当前的根节点树下的叶子结点个数
depth = getTreeDepth(myTree) # 得到当前根节点树下的最大深度
firstside = list(myTree.keys()) # 获取了当前树的所有键
firstStr = firstside[0] # 得到当前节点的键,键值如果是字典,那么键里存的就是根节点文本,
# 计算子节点的坐标,一开始最初始的树的子节点算出来和根节点的坐标是一样的,所以画不出指向根结点箭头
# 详细解释一下中点公式怎么算的,首先最开始是因为xOff定义在脱出画面的半个结点长度
# 因此需要给它加上(当前根的叶子节点数+1)/2个的长度为1/totalW的距离才能到达当前子节点中点的位置,所以下列公式即可算出
cntrPt = (plotTree.xOff + (1.0 + float(numLeafs)) /2.0 / plotTree.totalW, plotTree.yOff)
plotMidText(cntrPt, parentPt, nodeTxt) # 填充当前的父子结点之间的文本信息
plotNode(firstStr, cntrPt, parentPt, decisionNode) # 绘制当前根结点和指向根结点箭头
secondDict = myTree[firstStr] # 获取当前根节点下面的子树
plotTree.yOff = plotTree.yOff - 1.0 / plotTree.totalD # 纵坐标到下一层,将减少1/totalD的权重长度
for key in secondDict.keys(): # 遍历字典的值,找值类型是否是字典,即找根结点所有的分支,找叶子节点
if type(secondDict[key]) == dict:
# 这里有个很重要的结论是:如果当前键的键值是字典,说明有子树
# 而且键存着是子树的根节点文本
# 当前键的键值不是字典类型,那么说明键存着父子结点填充文本,键值为叶子结点文本
# 这里三个参数分别是当前字典的值(也就是分支树),下一层的根节点坐标,和下一层根节点要填充的文本
plotTree(secondDict[key], cntrPt, str(key)) # 是字典类型,继续递归绘制下一层的根节点
else:
# 不是字典类型,说明已经到了叶子结点,接下来绘制叶子结点
plotTree.xOff = plotTree.xOff + 1.0 / plotTree.totalW # 一旦发现叶子节点,从左往右不断绘制叶子结点,体现在每次给叶子节点横坐标加1/totalW的长度
# 绘制当前的叶子节点和指向叶节点的箭头,和父子结点之间的填充文本
# 起点是cntrPt,此时的cntrPt其实是父节点的坐标值,(plotTree.xOff, plotT