整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

全面!手把手教你决策树可视化(附链接&amp

全面!手把手教你决策树可视化(附链接&代码)

者: Terence Parr, Prince Grover

翻译:王雨桐

校对:詹好

本文长度约为9500字,建议阅读10+分钟

本文分析了决策树可视化中的关键因素,比较了现有的可视化工具。并通过大量的示例介绍了一个决策树可视化工具的设计和实现过程。

目录

  • 决策树概述
  • 决策树可视化的关键因素
  • 效果展示
  • 与现有可视化的比较
  • 我们的决策树可视化
    • 可视化特征-目标空间
    • 细节部分
    • 用可视化树来解释单次观测
    • 横向决策树
    • 简化结构
  • 前车之鉴
  • 代码示例
    • 回归树可视化----以波士顿房价为例
    • 分类树可视化---以红酒为例
  • 实践经验
    • Scikit决策树的影子树
    • 工具箱
    • SVG生成的矢量图
  • 经验总结
  • 未来工作

在适用于结构化数据的机器学习模型中,梯度提升和随机森林可以称得上是明星级模型,而决策树则是这两者的基石。决策树的可视化工作对于了解这些模型的工作原理有极大的帮助。然而目前的可视化软件工具很基础,对新手并不友好。例如,我们无法利用现有的库展示每个节点是如何划分变量的。此外,当把一个新样本放到决策树中进行预测时,我们只能生成一张可供展示的结果图片,而很难运用现有工具将整个过程可视化。

因此我们创建了一个通用包来在scikit-learn上可视化决策树模型以及解释模型。并且我们将在马上出版的机器学习书籍《The Mechanics of Machine Learning》(由JeremyHoward编写)中大量使用该包。以下是一个简单的决策树可视化示例:

附书链接:

https://mlbook.explained.ai/

本文演示了这项工作的成果,详细介绍了我们为此所做的尝试,并概述了该工具的在使用过程中的基本框架和技术细节。该可视化软件是dtreeviz的一部分,它是一个新兴的Python机器学习库。本文不会过多阐述决策树的基本原理,但为了方便您熟悉相关的术语使用,我们会对此做一个简短的概述。

决策树概述

决策树是一种基于二叉树(最多有左右两个子树)的机器学习模型。决策树遍历训练数据并将信息浓缩为二叉树的内部节点和叶节点,从而学习训练集中的观测值之间的关系,这些观测值表示为特征向量x和目标值y。(注:向量为粗体,标量为斜体。)

决策树中的每个叶子都表示特定的预测结果。回归树中输出的预测是一个(连续的)值,例如价格;而分类树中输出的预测是(离散的)目标类别(在scikit中表示为整数),例如是否患有癌症。决策树将观测分为具有相似目标值的组,每个叶子代表其中一个分组。对于回归而言,叶节点中观测的相似性意味着目标值之间的差异很小;而对于分类而言,则意味着大多数或所有观测属于同一类别。

任何一个从树根到叶节点的路径都要经过一系列(内部)决策节点。在训练过程中选出特定的分割点后,每个决策节点将x中的单个要素的值(xi)与分割点值进行比较。例如,在预测房租的模型中,决策节点会比较特征,如卧室数量和浴室数量等。(请参阅第3章,新样本预测的可视化效果。)即使在具有目标值离散的分类模型中,决策节点仍会比较数值特征值,这是因为在scitkit中,假定决策树模型的所有特征都是数值类型,因此分类变量必须要经过独热编码、合并、标签编码等处理。

为了得到决策节点,模型将遍历训练集的子集(或根节点的完整训练集)。在训练过程中,根据相似性最大化的原则,决策树将根据选择节点的特征和该特征空间内的分割点,将观察结果放入左右两个桶中(子集)。(该选择过程通常要对特征和特征值进行详尽的比较)左子集中样本的xi特征值均小于分割点,而右子集中样本的xi均大于分割点。通过为左分支和右分支创建决策节点,递归地进行树的构造。当达到某个停止标准(例如,节点中包含的观测数少于5)时,决策树终止生长。

决策树可视化的关键因素

决策树可视化应该突出以下重要元素,我们将在下文中具体阐述。

  • 决策节点的特征vs目标值分布(在本文中称为特征-目标空间)。我们想知道能否基于特征和分割点将观测进行分类。
  • 决策节点的特征和特征分割点。我们需要知道每个决策节点所选择的待考察的特征变量,以及将观测分类的标准。
  • 叶节点纯度,这会影响我们的预测置信度。较高的纯度也就意味着那些在回归问题中较低方差的叶节点,以及分类问题中包含绝大多数目标的叶节点,它们都意味着更可靠的预测效果。
  • 叶节点预测值。基于训练集的目标值,该叶节点具体的预测结果。
  • 决策节点中的样本数。我们需要了解决策节点上大部分样本的归属。
  • 叶节点中的样本数。我们的目标是让决策树的叶节点更少,数目更大和纯度更高。如果样本的节点下的样本数太少,则可能由过拟合现象。
  • 新样本如何从根节点开始被分到特定的叶节点。这有助于解释为什么新样本得到了相应的预测。例如,在预测公寓租金价格的回归树中,由于决策节点检查了卧室的数量,而新样本的卧室数量大于3,因此预测价格偏高。

效果展示

在深入研究现有的可视化工具之前,想先介绍一下我们生成的效果图。本节重点介绍一些可视化的案例,这些是我们利用一些数据集构造的scikit回归和分类决策树。你还可以利用完整的库和代码复现所有案例。

附代码链接:

https://github.com/parrt/dtreeviz/blob/master/testing/gen_samples.py

与现有可视化工具的比较

如果搜索“可视化决策树”,很快便能找到由scikit提供的基于Python语言的解决方案:sklearn.tree.export_graphviz。此外还可以找到R甚至SAS和IBM的可视化工具。在本节中,我们收集了现有的各种决策树可视化效果,并将它们与我们使用dtreeviz库制作的决策树进行了比较。在下一部分中,我们将对可视化进行更详细的讨论。

让我们使用默认设置下的scitkit可视化工具,在大家都很熟悉的的Iris数据集上绘制一个可视化的决策树。

scikit树很好地表现了树的结构,但我们仍然发现了一些问题。首先颜色的运用不够直观,比如在为什么有些节点是彩色的而有些不是这一问题上:如果颜色代表该分类器的预测类别,那么我们可能会认为只有叶子才是彩色的,因为只有叶子才有预测。事实证明,没有颜色的节点预测能力较弱。

除此之外,基尼系数(确定性得分)会占用图中的空间,并且不利于解释。在每个节点中体现各目标类别的样本数很有用,但直方图可以提供更多信息。此外,利用有颜色的目标类别图例会很好。最后,将true和false用作边缘标签并不够清晰,3和<看起来更清晰。最显著的区别是我们的决策节点利用堆叠直方图展示特征分布,每个目标类别都会用不同的颜色显示。同样,我们的叶子大小与该叶子中的样本数成正比。

再来考察一个回归的案例。下图是在波士顿数据集的上使用scikit的可视化效果,我们将它与dtreeviz的版本进行比较。

同样,在scikit树中,我们不能直观地理解颜色的用途,但是在进一步研究后,我们发现颜色较深的图像表示较高的预测目标值。如前文所述,在我们的解决方案中,决策节点下能够显示特征空间分布,比如该案例就使用了特征-目标值散点图,在叶节点总使用带状图显示目标值分布,其中点更密集的叶节点意味着该叶节点下有更多的样本。

同样,我们来考察以下R语言下的可视化决策树的软件包,该软件包的结果与scikit类似,但边缘标签的效果更好:

SAS和IBM同样提供(均不支持Python语言)决策树可视化。我们发现SAS的决策节点包括与该节点的样本目标值和其他详细信息有关的条形图:

在这一案例中有一个很好的idea,就是通过边缘宽度来体现子树中的样本量。但是由于缺少水平轴,这些条形图的解释性并不如意。测试类别变量的决策节点(上图)每个类别仅具有一个条形,因此它们只能表示简单的类别计数,而不是特征分布。对于数字特征(下图),SAS决策节点显示目标值或特征值的直方图(我们无法从图像中分辨出)。SAS节点条形图/直方图似乎只是在说明目标值,这并没有告诉我们有关特征分割的信息。

下侧的SAS树似乎突出显示了新样本的预测过程,但我们无法从其他工具和库中找到任何其他示例,这样的功能似乎并不常见。

再来考察IBM软件的情况。它在泰坦尼克数据集中表现出了非常不错的可视化效果,甚至结合IBM的Watson分析以条形图的形式显示了决策节点类别的计数:

再来看看IBM较早的SPSS产品中对决策树可视化效果:

可见,在SPSS中,这些决策节点提供了与样本目标类别计数相同的SAS条形图。

以上所提及的所有可视化都提供了不错的结果,但是给予我们启发性最大的是来自《机器学习的可视化简介》中的案例。它给我们展示了一个以动画形式展示的决策树可视化案例,如下所示:

附链接:

r2d3.us/visual-intro-to-machine-learning-part-1/(译者注:很经典的可视化,建议看原网站动态图)

除了动画的要素之外,该可视化还具有此前提到的三个独特特征:

  • 决策节点显示特征空间如何分割
  • 决策节点的分割点在分布中直观显示(就是那个三角形的指示符号)
  • 叶节点的大小与该叶中的样本数成正比

尽管该案例是出于教学目的而开发的基于hardcoded技术的可视化动图,但它给我们指明了正确的方向。

我们的决策树可视化

除了《机器学习的可视化简介》中的动画外,我们找不到另一个能够更好说明如何在决策节点(特征目标空间)处分割特征值的案例了。而这一点恰恰是在决策树模型训练期间进行操作的关键点,也是新手应该关注的点,因此我们借鉴这一方案,从检查分类树和回归树的决策节点开始我们的可视化工作。

  • 可视化特征-目标空间

同行,我们通过训练数据学习到决策节点选择特征xi并在xi的值域(特征空间)进行分割,将具有相似目标值的样本分到两个子树中的一个中。准确地说,训练过程中需要检查特征和目标值之间的关系。因此,除非我们的可视化工作在决策节点上显示了特征-目标空间,否则读者很难根据可视化图像来直观理解得到预测结果的整个过程和原因。为了突出现实决策节点是如何分割特征空间的,我们以单特征(AGE)的回归树和分类树作为展示。这个案例使用波士顿房价数据中的单个特征(age)来训练回归决策树。为了便于讨论,在下图中加入了节点id:

附生成效果图的代码:

https://github.com/parrt/dtreeviz/blob/master/testing/paper_examples.py

水平虚线表示决策节点中左子树和右子树的目标均值;垂直虚线表示特征空间中的分割点。下方的黑色三角形突出显示分割点并标出了确切的分割值。叶节点用虚线指示目标预测(这里使用的是平均值)。

如图所示,为了便于决策节点的比较,AGE特征轴都控制在了一个相同的值域上,而并没有将数值集中的区域进行方法。因此,决策节附近的样本的AGE值被限制在了一个狭窄的区域中。

例如,将节点0中的特征空间进一步被划分为了为节点1和8的特征空间;节点1的特征空间又进一步划分为节点2和5中所示的特征空间。当然,可以看到,这一决策树模型的预测效果并不是很好,这是因为出于展示的方便,我们仅仅单一的变量来训练模型,但是这个简单的案例给我们演示了如何可视化决策树划分特征空间的过程。

尽管分类决策树和回归决策树的在实现方式大致上相同,但对它们进行解释方式却大不相同,因此这两种情况的可视化效果是不同的。对于回归模型,最好使用特征与目标的散点图来显示特征-目标空间。但是,对于分类模型,目标是离散的类别而不是连续的数字,因此我们选择使用直方图来可视化特征目标空间。下图是在USER KNOWLEDGE数据上训练的分类树,同样我们只使用了单个特征(PEG)来进行训练,并且同样标记了节点id:

在这一案例中,直方图显示了PEG特征空间分布,而颜色则体现了特征与目标类别之间的关系。例如,在节点0中,我们可以看到具有very_low目标类别的样本聚集在PEG特征空间的左端,而具有high目标类别的样本聚集在右端。与回归树一样,左子树的特征空间和父节点上直方图分割点左侧的特征空间相同;右子树也同理。

例如,将节点9和12的直方图组合起来,可以得出节点8的直方图。我们将PEG决策节点的水平轴限制在相同范围,因此位于下方的直方图中的范围更窄,这也意味着分类更纯净。

为了更清楚地显示不同类别的特征空间,我们使用了堆叠直方图。值得注意的是,堆叠直方图在Y轴上的高度是所有类别的样本总数。多个类别的计数相互叠加。

当类别多于四或五个时,堆积直方图可读性很低,因此我们建议在这种情况下将直方图类型参数设置为不堆积的直方图。在基数较高的目标类别下,重叠的分布更难以可视化,并且出现问题,因此我们设置了10个目标类别的限制。使用10类Digits数据集(使用非堆叠直方图)所构成的一个比较浅的决策树的示例如下:

  • 细节部分

在前面的论述中,我们省略了一些最值得关注的可视化细节,在这里我们来对其中的一些关键要素展开分析。

在对于分类树可视化中,我们使用了节点大小来指示每个节点包含的样本数量。随着节点中样本数量的减少和叶节点直径的变小,直方图将按比例变短。

对于给定的特征,特征空间(水平轴)始终具有相同的宽度和相同的范围,这更有利于比较不同节点的特征目标空间。所有直方图的条具有相同的宽度(以像素为单位)。为了避免混乱,我们仅在水平和垂直轴上标示了最小值和最大值。

尽管在决策树的可视化过程中一般不适用饼状图来进行呈现,但我们还是选择了使用它来体现叶节点的分类。我们通过观察图中是否有明显占主导的颜色,就可以评判分类结果的纯度。饼图最大的缺陷是无法体现元素间具体的关系。我们仅仅能从其中主导的颜色决定该节点的预测结果。

对于回归树而言,为了便于比较节点,我们将所有决策节点的目标(垂直)轴限制在相同的高度和相同的范围。对于给定的特征,回归要素空间(水平轴)始终是相同的宽度和范围。将所有散点图的透明度设置为较低水平,因此较高的目标值密度对应较深的颜色。

回归叶节点在垂直方向上显示相同范围的目标空间。相比于箱状图,我们选择了带状图,因为带状图能更好地表示分布,并隐含地以点数表示样本数。(我们还用文字标示了叶子节点的样本数。)关于叶节点的预测值,我们选择了带状图的质量(均值)分布中心,同时,我们使用了虚线来将其突出显示。

除此之外,还有许多其他细节可以提高可视化图表的效果:

  1. 包含分类类别的图例。
  2. 所有颜色均从一个对于色盲而言相对友好的调色板中进行挑选,每个2至10个目标类别绑定一个调色板。
  3. 我们在文本上使用灰色而不是黑色,这更有利于人眼审视。
  4. 图中的线采用细线。
  5. 我们在带状图和饼状图中突出了其轮廓。
  • 新样本预测的可视化效果

通过了解拆分决策节点的特征空间的过程,我们能直观地看到决策树是如何得到具体的预测结果的。现在让我们看一下如何将预测新样本的过程可视化。此处的关键是可视化从根到叶节点的路上所做出的决策。

节点内的决策很简单:如果测试向量x中的特征xi小于分割点,则归类到左子树,否则归为右子树。为了突出决策过程,我们必须重点强调比较操作。对于沿叶子预测变量节点路径的决策节点,我们在水平特征空间中的位置xi处显示了橙色三角形。

如果橙色三角形位于黑色三角形的左侧,则沿左路径,否则沿右路径。预测过程中涉及的决策节点被带有虚线框的框包围,边缘较粗且呈橙色。以下为两个测试向量的示例树:

带有特征名称和值的测试向量x出现在叶子预测变量节点的下方(或横向决策树的右端)。测试向量突出显示了一个或多个决策节点中使用的特征。当特征数量达到阈值20(左右方向为10)时,测试向量不会显示未使用的特征,以避免不必要的测试向量。

  • 横向决策树

相比于纵向决策树,一些用户偏爱横向图像,有时树的性质从左到右体现得更好。在预测过程中,样本特征向量也可以从左向右延伸。以下是一些示例:

  • 简化结构

从更宏观的角度评估决策树,我们可以看到对分类过程的概述。这意味着比较树的形状和大小之类等,但更重要的是查看叶节点。我们想知道每个叶节点有多少个样本,分类的纯度如何,以及大多数样本落在哪里。

当可视化文件太大时,很难获得概述,因此我们提供了一个“non-fancy”选项。该选项可以生成较小的可视化文件,同时保留关键的叶子信息。以下案例是分别为non-fancy模式的回归树和分类树:

前车之鉴

从设计的角度对这些树可视化感兴趣的人可能会发现阅读我们尝试过并拒绝的内容很有意思。在设计分类树时,相比于块状的直方图,我们预计核密度估计会给出更准确的图像。我们完成了如下的案例:

问题在于只有一个或两个样本的决策节点中,所得到的这种分布极具误导性:

我们还尝试使用气泡图代替直方图作为分类器决策节点:

这些可视化图形看起来确实很酷,但比较下来,还是直方图更易于阅读。

关于回归树,我们考虑使用箱形图显示预测值的分布,还考虑使用简单的条形图显示样本数量:

与现在使用的条形图相比,每片叶子的双图都不令人满意。箱形图无法像条形图那样显示目标值的分布。在带状图之前,我们只是使用样本索引值作为水平轴来体现目标值:

这是一种误导,因为水平轴通常是特征空间。我们将其压缩为带状图。

代码示例

本节提供了波士顿回归数据集和红酒分类数据集的可视化示例。您还可以查看示例可视化的完整库以及生成示例的代码。

  • 回归树可视化----以波士顿房价为例

以下是一个代码示例,用于加载Boston数据并训练最大深度为3的回归树:

boston=load_boston()X_train=boston.datay_train=boston.targettestX=X_train[5,:]regr=tree.DecisionTreeRegressor(max_depth=3)regr=regr.fit(X_train, y_train)

可视化树的代码包括树模型,训练数据,特征和目标名以及新样本(如果必要的话):

viz=dtreeviz(regr, X_train, y_train,target_name='price',              feature_names=boston.feature_names,               X=testX)viz.save("boston.svg") # suffix determines thegenerated image formatviz.view()             # pop up window to display image
  • 分类树可视化---以红酒为例

这是一个代码案例,用于加载Wine数据并训练最大深度为3的分类树:

clf=tree.DecisionTreeClassifier(max_depth=3)wine=load_wine()clf.fit(wine.data,wine.target)

分类模型可视化与回归模型相同,但需要目标类名称:

viz=dtreeviz(clf, wine.data, wine.target, target_name='wine', feature_names=wine.feature_names, class_names=list(wine.target_names))viz.view()

在Jupyter notebooks中,从dtreeviz()返回的对象具有_repr_svg_()函数,Jupyter使用该函数自动显示该对象。请参阅示例笔记本。

附链接:

https://github.com/parrt/dtreeviz/blob/master/notebooks/examples.ipynb

  • JUPYTER NOTEBOOK中的小问题

截至2018年9月,Jupyter notebooks无法正常显示此库生成的SVG,字体等会变得混乱:

好消息是github和JupyterLab可以正确地显示图像。

在Juypter notebooks中使用Image(viz.topng())的视觉效果较差;如果直接调用viz.view(),会弹出一个窗口,其中会恰当地显示结果。

实践经验

我们在这个项目中遇到了很多问题,编程错误、参数设置、解决bug和各种库的错误/限制以及如何更好地融合现有工具。唯一有趣的部分是可视化设计的无数次尝试。期待这个可视化会对机器学习社区有很大的帮助,这也是我们坚持不懈完成项目的动力。结合stackoverflow,文档和繁琐的图形编程,我们大概花了两个月的时间完成了这个项目。

最终我们使用matplotlib生成决策和叶节点的图像,并使用传统的graphviz将它们组合成树。我们还在graphviz树的描述中广泛使用了HTML标签,以用于布局和字体规范。但我们遇到的最大麻烦是将所有组件组合成高质量的矢量图形。

我们先创建了一个影子树,它包括了scikit创建的决策树,让我们开始吧。

  • Scikit决策树的影子树

scikit-learn的分类树和回归决策树是为提高效率而构建的,树的延伸或提取节点信息并不是重点。我们创建了dtreeviz.shadow.ShadowDecTree和dtreeviz.shadow.ShadowDecTreeNode类,以便于使用所有树信息(传统的二叉树)。以下是通过scikit分类树或回归树模型创建影子树的方法:

shadow_tree=ShadowDecTree(tree_model,X_train, y_train, feature_names, class_names)

影子树/节点类具有许多方法,这些方法还可以用于需要遍历scikit决策树的其他库。例如,predict()不仅可以运用树来预测新样本,而且还返回被访问节点的路径。可以通过node_samples()获得与任意特定节点关联的样本。

工具箱

如果能掌握所有技巧,Graphviz和dot语言有助于设计合理的树型布局,例如当子树重叠时,如何隐藏图象重叠的边缘。如果要在同一水平上显示两个叶节点leaf4和leaf5,我们可以用到graphviz如下:

LSTAT3-> leaf4 [penwidth=0.3 color="#444443" label=<>]LSTAT3-> leaf5 [penwidth=0.3 color="#444443" label=<>]{rank=same;leaf4-> leaf5 [style=invis]}

我们通常在graphviz节点上使用HTML标签,而不仅仅是文本标签,因为它们能更好地控制文本显示,并将表格数据显示为实际表格。例如,当显示沿着树的测试向量时,使用HTML表显示测试向量:

为了从graphviz文件生成图像,我们使用graphvizpython软件包,该软件包最终是用程序例程之一(run())执行dot二进制可执行文件。有时,我们在dot命令上使用了略有不同的参数,因此我们可以更像这样更灵活地直接调用run():

cmd=["dot", "-Tpng", "-o", filename, dotfilename]stdout,stderr=run(cmd, capture_output=True, check=True, quiet=False)

我们还将使用run()函数来执行pdf2svg(PDF转SVG)工具,如下一节所述。

  • SVG生成的矢量图

我们使用matplotlib生成决策和叶子节点,随后生成graphviz /dot图像和HTMLgraphviz标签,最终通过img标签引用生成的图像,如下所示:

<imgsrc="/tmp/node3_94806.svg"/>

94806数字是进程ID,它有利于独立运行同一台计算机的多个dtreeviz实例。否则,多个进程可能会覆盖相同的临时文件。

因为需要可缩放的矢量图形,我们先尝试着导入SVG图像,但无法通过graphviz插入这些文件(两者都不是pdf)。随后我们花了四个小时才发现生成和导入SVG是两件事,需要在OS X上使用--with-librsvg进行如下操作:

$brew install graphviz --with-librsvg --with-app --with-pango

最初,当我们想从matplotlib生成PNG文件时,我们将每英寸的点数(dpi)设置为450,这样它们在iMac这样高分辨率屏幕上能有不错的效果。不幸的是,这意味着我们必须使用<td>标签的width和height参数和graphviz中的HTML表来设定整个树的大小。这会带来很多问题,因为我们必须了解matplotlib得到的宽高比。使用SVG文件后,我们不必再了解SVG 文件在HTML中的具体大小;在撰写此文档时,我们意识到没有必要了解SVG文件的具体尺寸。

然而graphviz的SVG结果仅引用了我们导入的节点文件,而没有将节点图像嵌入到整个树形图像中。这是一种很不方便的形式,因为当发送可视化树时,我们要发送文件的zip而不是单个文件。我们花了一些时间解析SVG XML,并将所有引用的图像嵌入到单个大型meta-SVG文件中。有最终,得到了很好的效果。

然后我们注意到在生成SVG时,graphviz不能正确处理HTML标签中的文本。例如,分类树图例的文本会被切除并重叠。

为了获得独立SVG文件的工作,我们首先从graphviz生成PDF文件,然后使用pdf2svg将PDF转换为SVG(pdf2cairo也似乎起作用)。

我们注意到Jupyter notebook存在一个问题,它无法正确显示这些SVG文件(请参见上文)。Jupyterlab确实可以像github一样正确处理SVG。我们添加了一个topng()方法,这样Jupyter Notebook的用户就能使用Image(viz.topng())来获取嵌入式图像。还有一个跟好的方法,调用viz.view()将弹出一个窗口,也可以正确显示图像。

经验总结

有时解决编程问题与算法无关,而与编程语言的限制和功能有关,例如构建一个工具和库。决策树可视化软件也是这种类似的情况。编程并不难,我们是通过搭配适当的图形工具和库来得到最终的结果。

设计实际的可视化效果还需要进行无数次的实验和调整。生成高质量的矢量图还需要不断试错,对结果进行完善。

我们算不上可视化的狂热者,但是对于这个特定的问题,我们一直坚持了下来,才收获了理想的效果。在爱德华·塔夫特(Edward Tufte)的研讨会上,我了解到,只要不是随意的瞎搭配,我们就可以在人眼可以处理的限度下使用丰富的图表呈现大量的信息。

在这个项目中,我们使用了设计面板中的许多元素:颜色,线条粗细,线条样式,各种图,大小(区域,长度,图形高度,...),颜色透明度(alpha),文本样式(颜色,字体,粗体,斜体,大小),图形注释和视觉流程。所有视觉元素都发挥了相应的作用。例如,我们不能仅因为某一个颜色漂亮就使用它,而是要考虑到如何使用这个颜色来突出显示重要的维度(目标类别),因为人类能轻松且快速地发现颜色差异。节点大小的差异也应该很容易被人眼捕捉到,所以我们用节点的大小来表示叶子节点数据量的大小。

未来工作

本文档中描述的可视化内容是dtreeviz机器学习库的一部分,该库还处于起步阶段。我很快会将rfpimp库移至dtreeviz。到目前为止,我们只在OS X上测试过该工具。我们期待其他平台上的程序员提供更多执导,以便包括更丰富的安装步骤。

我们还在考虑几个细节的调整,例如使直方图和分类树底部对齐,会更利于比较节点。另外,某些三角形标签与轴标签重叠。最后,如果边缘宽度和子树中的样本量成比例就更好了(如SAS)。

原文标题:

How to visualize decision trees

原文链接:

https://explained.ai/decision-tree-viz/index.html

编辑:黄继彦

校对:林亦霖

译者简介

王雨桐,UIUC统计学在读硕士,本科统计专业,目前专注于Coding技能的提升。理论到应用的转换中,敬畏数据,持续进化。

—完—

关注清华-青岛数据科学研究院官方微信公众平台“ THU数据派 ”及姊妹号“ 数据派THU ”获取更多讲座福利及优质内容。

者 | Erik-Jan van Baaren

译者 | 弯月,责编 | 屠敏

以下为译文:

元旦过完了,我们都纷纷回到了各自的工作岗位。新的一年新气象,我想借本文为大家献上 Python 语言的30个最佳实践、小贴士和技巧,希望能对各位勤劳的程序员有所帮助,并希望大家工作顺利!

1. Python 版本

在此想提醒各位:自2020年1月1日起,Python 官方不再支持 Python 2。本文中的很多示例只能在 Python 3 中运行。如果你仍在使用 Python 2.7,请立即升级。

2. 检查 Python 的最低版本

你可以在代码中检查 Python 的版本,以确保你的用户没有在不兼容的版本中运行脚本。检查方式如下:

if not sys.version_info > (2, 7):
# berate your user for running a 10 year
# python version
elif not sys.version_info >=(3, 5):
# Kindly tell your user (s)he needs to upgrade
# because you're using 3.5 features

3. IPython

IPython 本质上就是一个增强版的shell。就冲着自动补齐就值得一试,而且它的功能还不止于此,它还有很多令我爱不释手的命令,例如:

  • %cd:改变当前的工作目录

  • %edit:打开编辑器,并关闭编辑器后执行键入的代码

  • %env:显示当前环境变量

  • %pip install [pkgs]:无需离开交互式shell,就可以安装软件包

  • %time 和 %timeit:测量执行Python代码的时间

完整的命令列表,请点击此处查看(https://ipython.readthedocs.io/en/stable/interactive/magics.html)。

还有一个非常实用的功能:引用上一个命令的输出。In 和 Out 是实际的对象。你可以通过 Out[3] 的形式使用第三个命令的输出。

IPython 的安装命令如下:

pip3 install ipython

4. 列表推导式

你可以利用列表推导式,避免使用循环填充列表时的繁琐。列表推导式的基本语法如下:

[ expression for item in list if conditional ]

举一个基本的例子:用一组有序数字填充一个列表:

mylist=[i for i in range(10)]
print(mylist)
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

由于可以使用表达式,所以你也可以做一些算术运算:

squares=[x**2 for x in range(10)]
print(squares)
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

甚至可以调用外部函数:

def some_function(a):
return (a + 5) / 2

my_formula=[some_function(i) for i in range(10)]
print(my_formula)
# [2, 3, 3, 4, 4, 5, 5, 6, 6, 7]

最后,你还可以使用 ‘if’ 来过滤列表。在如下示例中,我们只保留能被2整除的数字:

filtered=[i for i in range(20) if i%2==0]
print(filtered)
# [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

5. 检查对象使用内存的状况

你可以利用 sys.getsizeof 来检查对象使用内存的状况:

import sys

mylist=range(0, 10000)
print(sys.getsizeof(mylist))
# 48

等等,为什么这个巨大的列表仅包含48个字节?

因为这里的 range 函数返回了一个类,只不过它的行为就像一个列表。在使用内存方面,range 远比实际的数字列表更加高效。

你可以试试看使用列表推导式创建一个范围相同的数字列表:

import sys

myreallist=[x for x in range(0, 10000)]
print(sys.getsizeof(myreallist))
# 87632

6. 返回多个值

Python 中的函数可以返回一个以上的变量,而且还无需使用字典、列表或类。如下所示:

def get_user(id):
# fetch user from database
# ....
return name, birthdate

name, birthdate=get_user(4)

如果返回值的数量有限当然没问题。但是,如果返回值的数量超过3个,那么你就应该将返回值放入一个(数据)类中。

7. 使用数据类

Python从版本3.7开始提供数据类。与常规类或其他方法(比如返回多个值或字典)相比,数据类有几个明显的优势:

  • 数据类的代码量较少

  • 你可以比较数据类,因为数据类提供了 __eq__ 方法

  • 调试的时候,你可以轻松地输出数据类,因为数据类还提供了 __repr__ 方法

  • 数据类需要类型提示,因此可以减少Bug的发生几率

数据类的示例如下:

from dataclasses import dataclass

@dataclass
class Card:
rank: str
suit: str

card=Card("Q", "hearts")

print(card==card)
# True

print(card.rank)
# 'Q'

print(card)
Card(rank='Q', suit='hearts')

详细的使用指南请点击这里(https://realpython.com/python-data-classes/)。

8. 交换变量

如下的小技巧很巧妙,可以为你节省多行代码:

a=1
b=2
a, b=b, a
print (a)
# 2
print (b)
# 1

9. 合并字典(Python 3.5以上的版本)

从Python 3.5开始,合并字典的操作更加简单了:

dict1={ 'a': 1, 'b': 2 }
dict2={ 'b': 3, 'c': 4 }
merged={ **dict1, **dict2 }
print (merged)
# {'a': 1, 'b': 3, 'c': 4}

如果 key 重复,那么第一个字典中的 key 会被覆盖。

10. 字符串的首字母大写

如下技巧真是一个小可爱:

mystring="10 awesome python tricks"
print(mystring.title)
'10 Awesome Python Tricks'

11. 将字符串分割成列表

你可以将字符串分割成一个字符串列表。在如下示例中,我们利用空格分割各个单词:

mystring="The quick brown fox"
mylist=mystring.split(' ')
print(mylist)
# ['The', 'quick', 'brown', 'fox']

12. 根据字符串列表创建字符串

与上述技巧相反,我们可以根据字符串列表创建字符串,然后在各个单词之间加入空格:

mylist=['The', 'quick', 'brown', 'fox']
mystring=" ".join(mylist)
print(mystring)
# 'The quick brown fox'

你可能会问为什么不是 mylist.join(" "),这是个好问题!

根本原因在于,函数 String.join 不仅可以联接列表,而且还可以联接任何可迭代对象。将其放在String中是为了避免在多个地方重复实现同一个功能。

13. 表情符

有些人非常喜欢表情符,而有些人则深恶痛绝。我在此郑重声明:在分析社交媒体数据时,表情符可以派上大用场。

首先,我们来安装表情符模块:

pip3 install emoji

安装完成后,你可以按照如下方式使用:

import emoji
result=emoji.emojize('Python is :thumbs_up:')
print(result)
# 'Python is '

# You can also reverse this:
result=emoji.demojize('Python is ')
print(result)
# 'Python is :thumbs_up:'

更多有关表情符的示例和文档,请点击此处(https://pypi.org/project/emoji/)。

14. 列表切片

列表切片的基本语法如下:

a[start:stop:step]

start、stop 和 step 都是可选项。如果不指定,则会使用如下默认值:

  • start:0

  • end:字符串的结尾

  • step:1

示例如下:

# We can easily create a new list from 
# the first two elements of a list:
first_two=[1, 2, 3, 4, 5][0:2]
print(first_two)
# [1, 2]

# And if we use a step value of 2,
# we can skip over every second number
# like this:
steps=[1, 2, 3, 4, 5][0:5:2]
print(steps)
# [1, 3, 5]

# This works on strings too. In Python,
# you can treat a string like a list of
# letters:
mystring="abcdefdn nimt"[::2]
print(mystring)
# 'aced it'

15. 反转字符串和列表

你可以利用如上切片的方法来反转字符串或列表。只需指定 step 为 -1,就可以反转其中的元素:

revstring="abcdefg"[::-1]
print(revstring)
# 'gfedcba'

revarray=[1, 2, 3, 4, 5][::-1]
print(revarray)
# [5, 4, 3, 2, 1]

16. 显示猫猫

我终于找到了一个充分的借口可以在我的文章中显示猫猫了,哈哈!当然,你也可以利用它来显示图片。首先你需要安装 Pillow,这是一个 Python 图片库的分支:

pip3 install Pillow

接下来,你可以将如下图片下载到一个名叫 kittens.jpg 的文件中:

然后,你就可以通过如下 Python 代码显示上面的图片:

from PIL import Image

im=Image.open("kittens.jpg")
im.show
print(im.format, im.size, im.mode)
# JPEG (1920, 1357) RGB

Pillow 还有很多显示该图片之外的功能。它可以分析、调整大小、过滤、增强、变形等等。完整的文档,请点击这里(https://pillow.readthedocs.io/en/stable/)。

17. map

Python 有一个自带的函数叫做 map,语法如下:

map(function, something_iterable)

所以,你需要指定一个函数来执行,或者一些东西来执行。任何可迭代对象都可以。在如下示例中,我指定了一个列表:

def upper(s):
return s.upper

mylist=list(map(upper, ['sentence', 'fragment']))
print(mylist)
# ['SENTENCE', 'FRAGMENT']

# Convert a string representation of
# a number into a list of ints.
list_of_ints=list(map(int, "1234567")))
print(list_of_ints)
# [1, 2, 3, 4, 5, 6, 7]

你可以仔细看看自己的代码,看看能不能用 map 替代某处的循环。

18. 获取列表或字符串中的唯一元素

如果你利用函数 set 创建一个集合,就可以获取某个列表或类似于列表的对象的唯一元素:

mylist=[1, 1, 2, 3, 4, 5, 5, 5, 6, 6]
print (set(mylist))
# {1, 2, 3, 4, 5, 6}

# And since a string can be treated like a
# list of letters, you can also get the
# unique letters from a string this way:
print (set("aaabbbcccdddeeefff"))
# {'a', 'b', 'c', 'd', 'e', 'f'}

19. 查找出现频率最高的值

你可以通过如下方法查找出现频率最高的值:

test=[1, 2, 3, 4, 2, 2, 3, 1, 4, 4, 4]
print(max(set(test), key=test.count))
# 4

你能看懂上述代码吗?想法搞明白上述代码再往下读。

没看懂?我来告诉你吧:

  • max 会返回列表的最大值。参数 key 会接受一个参数函数来自定义排序,在本例中为 test.count。该函数会应用于迭代对象的每一项。

  • test.count 是 list 的内置函数。它接受一个参数,而且还会计算该参数的出现次数。因此,test.count(1) 将返回2,而 test.count(4) 将返回4。

  • set(test) 将返回 test 中所有的唯一值,也就是 {1, 2, 3, 4}。

因此,这一行代码完成的操作是:首先获取 test 所有的唯一值,即{1, 2, 3, 4};然后,max 会针对每一个值执行 list.count,并返回最大值。

这一行代码可不是我个人的发明。

20. 创建一个进度条

你可以创建自己的进度条,听起来很有意思。但是,更简单的方法是使用 progress 包:

pip3 install progress

接下来,你就可以轻松地创建进度条了:

from progress.bar import Bar

bar=Bar('Processing', max=20)
for i in range(20):
# Do some work
bar.next
bar.finish

21. 在交互式shell中使用_(下划线运算符)

你可以通过下划线运算符获取上一个表达式的结果,例如在 IPython 中,你可以这样操作:

In [1]: 3 * 3
Out[1]: 9In [2]: _ + 3
Out[2]: 12

Python Shell 中也可以这样使用。另外,在 IPython shell 中,你还可以通过 Out[n] 获取表达式 In[n] 的值。例如,在如上示例中,Out[1] 将返回数字9。

22. 快速创建Web服务器

你可以快速启动一个Web服务,并提供当前目录的内容:

python3 -m http.server

当你想与同事共享某个文件,或测试某个简单的HTML网站时,就可以考虑这个方法。

23. 多行字符串

虽然你可以用三重引号将代码中的多行字符串括起来,但是这种做法并不理想。所有放在三重引号之间的内容都会成为字符串,包括代码的格式,如下所示。

我更喜欢另一种方法,这种方法不仅可以将多行字符串连接在一起,而且还可以保证代码的整洁。唯一的缺点是你需要明确指定换行符。

s1="""Multi line strings can be put
between triple quotes. It's not ideal
when formatting your code though"""

print (s1)
# Multi line strings can be put
# between triple quotes. It's not ideal
# when formatting your code though

s2=("You can also concatenate multiple\n" +
"strings this way, but you'll have to\n"
"explicitly put in the newlines")

print(s2)
# You can also concatenate multiple
# strings this way, but you'll have to
# explicitly put in the newlines

24. 条件赋值中的三元运算符

这种方法可以让代码更简洁,同时又可以保证代码的可读性:

[on_true] if [expression] else [on_false]

示例如下:

x="Success!" if (y==2) else "Failed!"

25. 统计元素的出现次数

你可以使用集合库中的 Counter 来获取列表中所有唯一元素的出现次数,Counter 会返回一个字典:

from collections import Counter

mylist=[1, 1, 2, 3, 4, 5, 5, 5, 6, 6]
c=Counter(mylist)
print(c)
# Counter({1: 2, 2: 1, 3: 1, 4: 1, 5: 3, 6: 2})

# And it works on strings too:
print(Counter("aaaaabbbbbccccc"))
# Counter({'a': 5, 'b': 5, 'c': 5})

26. 比较运算符的链接

你可以在 Python 中将多个比较运算符链接到一起,如此就可以创建更易读、更简洁的代码:

x=10

# Instead of:
if x > 5 and x < 15:
print("Yes")
# yes

# You can also write:
if 5 < x < 15:
print("Yes")
# Yes

27. 添加颜色

你可以通过 Colorama,设置终端的显示颜色:

from colorama import Fore, Back, Style

print(Fore.RED + 'some red text')
print(Back.GREEN + 'and with a green background')
print(Style.DIM + 'and in dim text')
print(Style.RESET_ALL)
print('back to normal now')

28. 日期的处理

python-dateutil 模块作为标准日期模块的补充,提供了非常强大的扩展,你可以通过如下命令安装:

pip3 install python-dateutil 

你可以利用该库完成很多神奇的操作。在此我只举一个例子:模糊分析日志文件中的日期:

from dateutil.parser import parse

logline='INFO 2020-01-01T00:00:01 Happy new year, human.'
timestamp=parse(log_line, fuzzy=True)
print(timestamp)
# 2020-01-01 00:00:01

你只需记住:当遇到常规 Python 日期时间功能无法解决的问题时,就可以考虑 python-dateutil !

29.整数除法

在 Python 2 中,除法运算符(/)默认为整数除法,除非其中一个操作数是浮点数。因此,你可以这么写:

# Python 2
5 / 2=2
5 / 2.0=2.5

在 Python 3 中,除法运算符(/)默认为浮点除法,而整数除法的运算符为 //。因此,你需要这么写:

Python 3
5 / 2=2.5
5 // 2=2

这项变更背后的动机,请参阅 PEP-0238(https://www.python.org/dev/peps/pep-0238/)。

30. 通过chardet 来检测字符集

你可以使用 chardet 模块来检测文件的字符集。在分析大量随机文本时,这个模块十分实用。安装方法如下:

pip install chardet

安装完成后,你就可以使用命令行工具 chardetect 了,使用方法如下:

chardetect somefile.txt
somefile.txt: ascii with confidence 1.0

你也可以在编程中使用该库,完整的文档请点击这里(https://chardet.readthedocs.io/en/latest/usage.html)。

如上就是我为各位奉上的新年礼物,希望各位喜欢!如果你有其他的技巧、贴士和实践,请在下方留言!

原文:https://towardsdatascience.com/30-python-best-practices-tips-and-tricks-caefb9f8c5f5

本文为 CSDN 翻译,转载请注明来源出处。

者 | 李秋键

责编 | 伍杏玲

封图 | CSDN 付费下载自东方 IC

出品 | CSDN(ID:CSDNnews)

随着中国工业和科技的发展,中国的一些发达城市的空气质量问题变得越来越严重,其中最为严重的便是PM2.5带来的恶劣环境问题。

本文在根据网络公开空气质量数据的基础上进行爬取相关数据,主要针对环境较为恶劣的城市,天津、北京、广州等几个城市,尤其是针对天津的质量数据进行对比分析。在分析的基础上得出空气质量变化情况,提出一些意见。并借助机器学习算法根据数据预测空气质量,以达到分析预测的典型大数据分析模式效果。

整体分析的流程图如下:

实验前的准备

1.1 数据获取

我们这里所得到的数据来源于网络公开的空气质量数据,数据来源于“天气后报”网站,网址为:http://www.tianqihoubao.com/aqi/tianjin.html。网址内容如下图可见:

图1-1 网址数据图

整个数据的获取使用python进行爬取。流程如下:

(1) 导入爬虫所需要的的库:

在air_tianjin_2019.py程序中。

其中Requests 是用Python语言编写,基于urllib,采用 Apache2 Licensed开源协议的 HTTP 库。它比 urllib 更加方便,可以节约我们大量的工作,完全满足 HTTP 测试需求。

其中BeautifulSoup库是一个灵活又方便的网页解析库,处理高效,支持多种解析器。利用它就不用编写正则表达式也能方便的实现网页信息的抓取

对应代码如下:

import time

import requests

from bs4 import BeautifulSoup

(2)为了防止网站的反爬机制,我们设定模拟浏览器进行访问获取数据:

headers={ 'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36'}

(3)然后获取2019年全年的空气质量数据:

for i in range(1, 13):

time.sleep(5)

url='http://www.tianqihoubao.com/aqi/tianjin-2019' + str("%02d" % i) + '.html'

response=requests.get(url=url, headers=headers)

soup=BeautifulSoup(response.text, 'html.parser')

tr=soup.find_all('tr')

1.2 数据预处理

如果仅仅是从网站上得到的数据会有一些标签等干扰项,我们针对一些标签进行去除即可:

for j in tr[1:]:

td=j.find_all('td')

Date=td[0].get_text.strip

Quality_grade=td[1].get_text.strip

AQI=td[2].get_text.strip

AQI_rank=td[3].get_text.strip

PM=td[4].get_text

with open('air_tianjin_2019.csv', 'a+', encoding='utf-8-sig') as f:

f.write(Date + ',' + Quality_grade + ',' + AQI + ',' + AQI_rank + ',' + PM + '\n')

最终爬取下来的部分数据如下:

表1-1 部分天津爬取数据表

这几个数据分别对应着AQI指数、当天AQI排名和PM2.5值

数据分析

这里的数据分析主要通过可视化的方法得到图像来进行分析。

(1) 天津AQI全年走势图

代码在air_tianjin_2019_AQI.py中

通过导入pyecharts 库来进行绘制走势图

首先通过已经获取到的数据进行读取:

df=pd.read_csv('air_tianjin_2019.csv', header=None, names=["Date", "Quality_grade", "AQI", "AQI_rank", "PM"])

然后获取日期和AQI数据,储存在列表变量中,以方便绘制图像:

attr=df['Date']v1=df['AQI']

接着定义标题,绘制曲线并保存为网页即可:

line=Line("2019年天津AQI全年走势图", title_pos='center', title_top='18', width=800, height=400)

line.add("", attr, v1, mark_line=['average'], is_fill=True, area_color="#000", area_opacity=0.3, mark_point=["max", "min"], mark_point_symbol="circle", mark_point_symbolsize=25)

line.render("2019年天津AQI全年走势图.html")

最终的效果图如下可见

图2-2 2019年天津AQI全年走势图

根据图2-2可知,在2019年度,天津的空气质量峰值分别是在1月、2月、11月和12月,即主要集中在春冬季,考虑到可能是春冬季通风较差,且节日较多,过多的节日烟花和汽车人员流动造成了空气质量变差。

(2)天津月均AQI走势图

air_tianjin_2019_AQI_month.py

为了体现出每月的平均空气质量变化,我们绘制了月均走势图。

首先同样的是读取数据:

df=pd.read_csv('air_tianjin_2019.csv', header=None, names=["Date", "Quality_grade", "AQI", "AQI_rank", "PM"])

接着获取日期和空气质量数据,并加以处理,去除日期中间的“-”:

dom=df[['Date', 'AQI']]

list1=

for j in dom['Date']:

time=j.split('-')[1]

list1.append(time)

df['month']=list1

接着计算每月空气质量的平均值

month_message=df.groupby(['month'])

month_com=month_message['AQI'].agg(['mean'])

month_com.reset_index(inplace=True)

month_com_last=month_com.sort_index

attr=["{}".format(str(i) + '月') for i in range(1, 13)]

v1=np.array(month_com_last['mean'])

v1=["{}".format(int(i)) for i in v1]

然后绘制走势图:

line=Line("2019年天津月均AQI走势图", title_pos='center', title_top='18', width=800, height=400)

line.add("", attr, v1, mark_point=["max", "min"])

line.render("2019年天津月均AQI走势图.html")

最终的效果图如下可见:

图2-3 2019年天津月均AQI走势图

(3)天津季度AQI箱形图

代码在air_tianjin_2019_AQI_season.py中

绘制天津季度空气质量箱型图,步骤如下:

读取爬取下来的数据:

df=pd.read_csv('air_tianjin_2019.csv', header=None, names=["Date", "Quality_grade", "AQI", "AQI_rank", "PM"])

接着按照月份分季,可以分为四个季度:

dom=df[['Date', 'AQI']]

data=[[], , , ]

dom1, dom2, dom3, dom4=data

for i, j in zip(dom['Date'], dom['AQI']):

time=i.split('-')[1]

if time in ['01', '02', '03']:

dom1.append(j)

elif time in ['04', '05', '06']:

dom2.append(j)

elif time in ['07', '08', '09']:

dom3.append(j)

else:

dom4.append(j)

然后定义箱型图的标题,横纵坐标等绘制箱型图:

boxplot=Boxplot("2019年天津季度AQI箱形图", title_pos='center', title_top='18', width=800, height=400)

x_axis=['第一季度', '第二季度', '第三季度', '第四季度']

y_axis=[dom1, dom2, dom3, dom4]

_yaxis=boxplot.prepare_data(y_axis)

boxplot.add("", x_axis, _yaxis)

boxplot.render("2019年天津季度AQI箱形图.html")

最终得到绘制的箱型图如下可见:

图2-4 2019年天津季度AQI箱形图

KNN算法预测

整体的代码流程分为两个部分,一部分是建立test.py程序用来将CSV文件转为符合标准的TXT数据存储;另一部分是K均值聚类的数据分类。

(1) 数据生成TXT

代码在test.py中

首先读入数据,存出入列表为x何y。同时因为y的值为汉字,需要转换为数字:

# 文件的名字

FILENAME1="air_tianjin_2019.csv"

# 禁用科学计数法

pd.set_option('float_format', lambda x: '%.3f' % x)

np.set_printoptions(threshold=np.inf)

# 读取数据

data=pd.read_csv(FILENAME1)

rows, clos=data.shape

# DataFrame转化为array

DataArray=data.values

Y=

y=DataArray[:, 1]

for i in y:

if i=="良":

Y.append(0)

if i=="轻度污染":

Y.append(1)

if i=="优":

Y.append(2)

if i=="严重污染":

Y.append(3)

if i=="重度污染":

Y.append(4)

print(Y)

print(len(y))

X=DataArray[:, 2:5]

print(X[1])

然后将存储的数据写入TXT,其中要注意换行和加“,”:

for i in range(len(Y)):

f=open("data.txt","a+")

for j in range(3):

f.write(str(X[i][j])+",")

f.write(str(Y[i])+"\n")

print("data.txt数据生成")

(2)K均值聚类

代码在KNearestNeighbor.py中。

首先是读取数据:

def loadDataset(self,filename, split, trainingSet, testSet): # 加载数据集 split以某个值为界限分类train和test

with open(filename, 'r') as csvfile:

lines=csv.reader(csvfile) #读取所有的行

dataset=list(lines) #转化成列表

for x in range(len(dataset)-1):

for y in range(3):

dataset[x][y]=float(dataset[x][y])

if random.random < split: # 将所有数据加载到train和test中

trainingSet.append(dataset[x])

else:

testSet.append(dataset[x])

定义计算距离的函数

def calculateDistance(self,testdata, traindata, length): # 计算距离

distance=0 # length表示维度 数据共有几维

for x in range(length):

distance +=pow((int(testdata[x])-traindata[x]), 2)

return math.sqrt(distance)

对每个数据文档测量其到每个质心的距离,并把它归到最近的质心的类。

def getNeighbors(self,trainingSet, testInstance, k): # 返回最近的k个边距

distances=

length=len(testInstance)-1

for x in range(len(trainingSet)): #对训练集的每一个数计算其到测试集的实际距离

dist=self.calculateDistance(testInstance, trainingSet[x], length)

print('训练集:{}-距离:{}'.format(trainingSet[x], dist))

distances.append((trainingSet[x], dist))

distances.sort(key=operator.itemgetter(1)) # 把距离从小到大排列

print(distances)

neighbors=

for x in range(k): #排序完成后取前k个距离

neighbors.append(distances[x][0])

print(neighbors)

return neighbors

决策函数,根据少数服从多数,决定归类到哪一类:

def getResponse(self,neighbors): # 根据少数服从多数,决定归类到哪一类

classVotes={}

for x in range(len(neighbors)):

response=neighbors[x][-1] # 统计每一个分类的多少

if response in classVotes:

classVotes[response] +=1

else:

classVotes[response]=1

print(classVotes.items)

sortedVotes=sorted(classVotes.items, key=operator.itemgetter(1), reverse=True) #reverse按降序的方式排列

return sortedVotes[0][0]

计算模型准确度

def getAccuracy(self,testSet, predictions): # 准确率计算

correct=0

for x in range(len(testSet)):

if testSet[x][-1]==predictions[x]: #predictions是预测的和testset实际的比对

correct +=1

print('共有{}个预测正确,共有{}个测试数据'.format(correct,len(testSet)))

return (correct/float(len(testSet)))*100.0

接着整个模型的训练,种子数定义等等:

def Run(self):

trainingSet=

testSet=

split=0.75

self.loadDataset(r'data.txt', split, trainingSet, testSet) #数据划分

print('Train set: ' + str(len(trainingSet)))

print('Test set: ' + str(len(testSet)))

#generate predictions

predictions=

k=5 # 取最近的5个数据

# correct=

for x in range(len(testSet)): # 对所有的测试集进行测试

neighbors=self.getNeighbors(trainingSet, testSet[x], k) #找到5个最近的邻居

result=self.getResponse(neighbors) # 找这5个邻居归类到哪一类

predictions.append(result)

# print('predictions: ' + repr(predictions))

# print('>predicted=' + repr(result) + ', actual=' + repr(testSet[x][-1]))

# print(correct)

accuracy=self.getAccuracy(testSet,predictions)

print('Accuracy: ' + repr(accuracy) + '%')

最终模型的准确度为90%。

图2-10 模型运行结果图

源码地址:https://pan.baidu.com/s/1Vcc_bHQMHmQpe-F6A-mFdQ

提取码:qvy7

作者简介:李秋键,CSDN博客专家,CSDN达人课作者。硕士在读于中国矿业大学,开发有taptap竞赛获奖等。