Python-数据分析秘籍-全-

Python 数据分析秘籍(全)

原文:Mastering Python Data Analysis

协议:CC BY-NC-SA 4.0

零、前言

|   | “数据分析是 Python 的杀手锏 app” |   |
|   | - 未知 |

本书是 Python 数据分析的后续。显而易见的问题是,“这本新书增加了什么?”因为 Python 数据分析已经相当棒了(或者我喜欢相信)。这本书,Python 数据分析烹饪书,是针对稍微有经验的 Python。一年过去了,所以我们正在使用更新版本的软件和软件库,我在 Python 数据分析中没有涉及到。此外,我有时间重新思考和研究,因此我决定如下:

  • 我需要一个工具箱,以便让我的生活更轻松,增加可重复性。我调用了工具箱 dautil ,并通过 PyPi(可与pip / easy_install一起安装)使其可用。
  • 我的自我反省让我相信,我需要让获取和安装所需软件变得更容易。我发布了一个 Docker 容器(pydacbk),其中包含了我们通过 DockerHub 需要的一些软件。您可以在第 1 章为可再现数据分析奠定基础、和在线章节中了解更多设置。Docker 容器并不理想,因为它变得相当大,所以我不得不做出一些艰难的决定。由于容器并不是这本书的真正组成部分,所以如果你有任何问题,我认为你直接联系我是合适的。但是,请记住,我不能彻底改变形象。
  • 这本书使用了 IPython 笔记本,它已经成为分析的标准工具。我在网上一章和我写的其他书里给出了一些相关的提示。
  • 我用的是 Python 3,很少有例外,因为 Python 2 在 2020 年后不会被维护。

你为什么需要这本书?

有些人会告诉你,你不需要书,只需要给自己找一个有趣的项目,然后一边走一边想出剩下的。尽管有大量的资源,这可能是一条非常令人沮丧的道路。例如,如果你想做一道美味的汤,你当然可以问朋友和家人,搜索互联网,或者看烹饪节目。但是,您的朋友和家人不能为您提供全职服务,并且互联网内容的质量各不相同。而以我的拙见,Packt Publishing、审稿人和我在这本书上花费了如此多的时间和精力,如果你没有从中获得任何价值,我会感到惊讶。

数据分析、数据科学、大数据——有什么大不了的?

您可能已经看到了将数据科学描述为数学/统计、计算机科学和领域专业知识的交集的文氏图。数据分析是永恒的,在数据科学甚至计算机科学之前就已经存在了。你可以用纸笔进行数据分析,在现代,还可以用袖珍计算器。

数据分析有许多方面,目标是做出决策或提出新的假设和问题。围绕数据科学和大数据的炒作、地位和金融回报让我想起了数据仓库和商业智能成为热门词汇的时代。商业智能和数据仓库的最终目标是为管理构建仪表板。这涉及许多政治和组织方面,但在技术方面,主要是关于数据库。另一方面,数据科学不是以数据库为中心,而是严重依赖机器学习。由于数据量越来越大,机器学习技术变得很有必要。数据的增长是由世界人口的增长和新技术的兴起造成的,如社交媒体和移动设备。事实上,数据增长可能是我们唯一可以确定的趋势。构建仪表板和应用机器学习之间的区别类似于搜索引擎的发展方式。

搜索引擎(如果你可以这么称呼它们的话)最初只不过是手动创建的组织良好的链接集合。最终,自动化方法取得了胜利。因为随着时间的推移,更多的数据将被创建(而不是销毁),我们可以期待自动化数据分析的增加。

Python 数据分析历史简介

各种 Python 软件库的历史非常有趣。我不是历史学家,所以下面的笔记是从我自己的角度写的:

  • 1989 年:吉多·范·罗苏姆在荷兰 CWI 实施了第一个 Python 版本,作为圣诞节“爱好”项目。
  • 1995 年:吉姆·胡古宁创建了 Numeric——NumPy 的前身。
  • 1999 年:Pearu Peterson 写 f2py 作为 Fortran 和 Python 之间的桥梁。
  • 2000 年:Python 2.0 发布。
  • 2001 年:SciPy 库发布。此外,努马雷,一个竞争的数字库被创建。费尔南多·佩雷斯发布了 IPython,它最初是一个“下午黑客”。NLTK 作为一个研究项目发布。
  • 2002 年:约翰·亨特创建了 Matplotlib 库。
  • 2005 年:NumPy 由特拉维斯·奥列芬特发行。最初,NumPy 是 Numeric 的扩展,其功能受 Numarray 的启发。
  • 2006 年:NumPy 1.0 发布。SQLAlchemy 的第一个版本发布了。
  • 2007 年:scikit-learn 项目是由大卫·库纳波发起的谷歌代码之夏项目。Cython 是从派热克斯公司分出来的。Cython 后来被大量用于 Pandas 和 scikit-learn 以提高性能。
  • 2008 年:韦斯·麦金尼开始研究 Pandas。Python 3.0 发布。
  • 2011 年:IPython 0.12 版本推出了 IPython 笔记本。Packt Publishing 发布 NumPy 1.5 初学者指南
  • 2012 年:Packt Publishing 发布 NumPy 食谱
  • 2013 年:Packt Publishing 发布 NumPy 初学者指南第二版
  • 2014 年:费尔南多·佩雷斯宣布 Jupyter 项目,该项目旨在制作一款与语言无关的笔记本。Packt Publishing 发布学习 NumPy 数组Python 数据分析
  • 2015 年:Packt Publishing 发布 NumPy 初学者指南第三版NumPy 烹饪书第二版

对未来的推测

未来是一个光明的地方,在那里,令人难以置信的大量数据存在于云中,软件运行在任何可以想象的设备上,具有直观的可定制界面。(我知道年轻人不停地谈论他们的手机有多棒,以及有一天我们将如何通过拖放在平板电脑上编程)。Python 社区似乎对未来不再相关存在某种焦虑。当然,你在 Python 上投入的越多,它就越重要。

为了弄清楚该怎么做,我们需要知道 Python 的特别之处。一派认为 Python 是胶合 C、Fortran、R、Java 等语言的胶水语言;因此,我们只需要更好的胶水。这可能也意味着“借用”其他语言的特性。就我个人而言,我喜欢 Python 的工作方式,它的灵活性,它的数据结构,以及它有这么多库和特性的事实。我认为未来在于更美味的语法糖和即时编译器。不知何故,我们应该能够继续编写 Python 代码,这些代码会自动转换为并发(机器)代码。引擎盖下看不见的机器管理较低层次的细节,并向中央处理器、图形处理器或云发送数据和指令。代码应该能够轻松地与我们正在使用的任何存储后端进行通信。理想情况下,所有这些魔法都会像自动垃圾收集一样方便。这听起来可能是一个不可能实现的“点击一个按钮”的梦想,但我认为这是值得追求的。

这本书涵盖了什么

第 1 章为可再现数据分析奠定基础,是相当重要的一章,建议大家不要跳过。它解释了 Anaconda、Docker、单元测试、日志记录和其他可再现数据分析的基本元素。

第 2 章创建有吸引力的数据可视化,演示如何可视化数据,并提到经常遇到的陷阱。

第 3 章统计数据分析和概率,讨论两个变量之间的统计概率分布和相关性。

第 4 章处理数据和数值问题,是关于异常值和其他常见的数据问题。数据几乎从来都不是完美的,所以很大一部分分析工作都是为了处理数据的不完美。

第 5 章网络挖掘、数据库和大数据,对数学的关注度较低,但更侧重于技术主题,如数据库、网页抓取和大数据。

第六章信号处理与时间序列,讲的是时间序列数据,内容丰富,需要特殊的技术。通常,我们对趋势和季节性或周期性感兴趣。

第七章用金融数据分析选股,重点关注股票投资,因为股价数据丰富。这是关于金融的唯一一章,如果你对股票不感兴趣,内容应该至少是部分相关的。

第八章文本挖掘和社交网络分析,帮你应对文字和社交媒体信息的洪流。

第 9 章集成学习和降维,涵盖集成学习、分类和回归算法,以及层次聚类。

第 10 章评估分类器、回归器和聚类,评估前一章第 9 章集成学习和降维中的分类器和回归器。

第 11 章分析图像,用 OpenCV 库分析图像的相当多。

第 12 章并行性和性能,是关于软件性能的,我讨论各种提高性能的选项,包括缓存和即时编译器。

附录 A词汇表,是本书通篇使用的技术概念的简要词汇表。目标是有一个易于查找的参考。

附录 B功能参考是功能的简短参考,旨在为您暂时无法查找文档时提供额外帮助。

附录 C在线资源列出了包括演示文稿、文档链接以及免费提供的 IPython 笔记本和数据在内的资源。本附录作为在线章节提供。

附录 D命令行和其他工具的提示和技巧,在本书中我们使用了各种工具,如 IPython 笔记本、Docker 和 Unix shell 命令。我给出了一个简短的提示列表,但并不是详尽无遗的。本附录也可作为在线章节获得。

这本书你需要什么

首先,您需要 Python 3 发行版。我推荐完整的 Anaconda 发行版,因为它附带了我们需要的大多数软件。我用 Python 3.4 和以下包测试了代码:

  • joblib 0.8.4
  • IPython 3.2.1 版
  • 网络 1.9.1
  • NLTK 3.0.2 版
  • Numexpr 2.3.1
  • Pandas 0.16.2
  • SciPy 0.16.0 版
  • 海生 0.6.0
  • SQL anywhere 0 . 9 . 9
  • statsmodels 0.6.1
  • matplotlib 1.5.0
  • NumPy 1.10.1
  • scikit-learn 0.17
  • dautil 0 . 0 . 1 至 29

对于一些食谱,你需要安装额外的软件,但这在需要软件的时候都会解释。

这本书是给谁的

这本书是实践性的,缺乏理论。你应该比初学者有更好的 Python 知识,并且有一些线性代数、微积分、机器学习和统计学的知识。理想情况下,您应该已经阅读了 Python 数据分析,但这不是必需的。我还推荐以下书籍:

  • 用 Python 构建机器学习系统,作者:威利·里歇特和路易斯·佩德罗·科埃略,2013
  • 伊万·伊德里斯《学习 NumPy 数组》,2014
  • Learning scikit-learn:Python 中的机器学习作者:Guillermo Moncecchi,2013
  • 数值和科学计算的学习科学,弗朗西斯科·j·布兰科-席尔瓦,2013 年
  • 面向 Python 开发人员的 Matplotlib】作者:Sandro Tosi,2009
  • NumPy 初学者指南-第三版伊万·伊德里斯,2015
  • 伊万·伊德里斯的《NumPy 烹饪书–第二版》,2015 年
  • Python 并行编程Jan Palach,2014
  • 伊戈尔·米洛瓦诺维奇的 Python 数据可视化烹饪书,2013 年
  • 金融巨蟒闫宇星,2014
  • 用 NLTK 2.0 食谱处理 Python 文本雅各布·帕金斯,2010

路段

在这本书里,你会发现几个经常出现的标题(准备,如何做,如何工作,还有更多,另见)。

为了给出如何完成配方的明确说明,我们使用以下部分:

做好准备

本节告诉您配方中的预期内容,并描述如何设置配方所需的任何软件或任何初步设置。

怎么做…

本节包含遵循配方所需的步骤。

它是如何工作的…

这一部分通常包括对前一部分发生的事情的详细解释。

还有更多…

本节包含关于配方的附加信息,以便读者更好地了解配方。

另见

本节提供了该配方的其他有用信息的有用链接。

惯例

在这本书里,你会发现许多区分不同种类信息的文本样式。以下是这些风格的一些例子和对它们的意义的解释。

文本中的码字、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟网址、用户输入和推特句柄如下所示:“绘制数据并与 Seaborn lmplot()函数进行相应的线性拟合。”

代码块设置如下:

population = dawb.download(indicator=[dawb.get_name('pop_grow'), dawb.get_name('gdp_pcap'),
                                    dawb.get_name('primary_education')],
                         country=countries['iso2c'], start=2014, end=2014)

population = dawb.rename_columns(population)

当我们希望将您的注意力吸引到代码块的特定部分时,相关的行或项目以粗体显示:

plt.figure()
plt.title('Rainy Weather vs Wind Speed')
categorical = df
categorical['RAIN'] = categorical['RAIN'] > 0
ax = sns.violinplot(x="RAIN", y="WIND_SPEED",
 data=categorical)

任何命令行输入或输出都编写如下:

$ conda install -c scitools cartopy

新名词重要词语以粗体显示。你在屏幕上看到的单词,例如,在菜单或对话框中,出现在文本中,如下所示:“在下一个截图中,一年 31 日文本来自工具提示:”

警告或重要提示会出现在这样的框中。

型式

提示和技巧是这样出现的。

读者反馈

我们随时欢迎读者的反馈。让我们知道你对这本书的看法——你喜欢或不喜欢什么。读者反馈对我们来说很重要,因为它有助于我们开发出你真正能从中获益的标题。

要给我们发送一般反馈,只需发送电子邮件<[feedback@packtpub.com](mailto:feedback@packtpub.com)>,并在您的邮件主题中提及书名。

如果你对某个主题有专业知识,并且对写作或投稿感兴趣,请参见我们位于www.packtpub.com/authors的作者指南。

客户支持

现在,您已经自豪地拥有了一本书,我们有许多东西可以帮助您从购买中获得最大收益。

下载示例代码

你可以从你在http://www.packtpub.com的账户下载这本书的示例代码文件。如果您在其他地方购买了这本书,您可以访问http://www.packtpub.com/support并注册,以便将文件直接通过电子邮件发送给您。

您可以按照以下步骤下载代码文件:

  1. 使用您的电子邮件地址和密码登录或注册我们的网站。
  2. 将鼠标指针悬停在顶部的 SUPPORT 选项卡上。
  3. 点击代码下载&勘误表
  4. 搜索框中输入图书名称。
  5. 选择要下载代码文件的书籍。
  6. 从您购买这本书的下拉菜单中选择。
  7. 点击代码下载

下载文件后,请确保使用最新版本的解压缩文件夹:

  • 视窗系统的 WinRAR / 7-Zip
  • zipeg/izp/un ARX for MAC
  • 适用于 Linux 的 7-Zip / PeaZip

这本书的代码包也托管在 GitHub 上,网址为https://GitHub . com/packt publishing/PitOnDaanalysiskokbook。我们还有来自丰富的图书和视频目录的其他代码包,可在https://github.com/PacktPublishing/获得。看看他们!

勘误表

尽管我们尽了最大努力来确保我们内容的准确性,但错误还是会发生。如果你在我们的某本书里发现了错误——可能是文本或代码中的错误——如果你能向我们报告,我们将不胜感激。通过这样做,你可以让其他读者免受挫折,并帮助我们改进这本书的后续版本。如果您发现任何勘误表,请访问http://www.packtpub.com/submit-errata,选择您的书籍,点击勘误表提交表链接,并输入您的勘误表的详细信息。一旦您的勘误表得到验证,您的提交将被接受,勘误表将上传到我们的网站或添加到该标题勘误表部分下的任何现有勘误表列表中。

要查看之前提交的勘误表,请前往https://www.packtpub.com/books/content/support并在搜索栏中输入图书名称。所需信息将出现在勘误表部分。

盗版

互联网上版权材料的盗版是所有媒体的一个持续问题。在 Packt,我们非常重视版权和许可证的保护。如果您在互联网上遇到任何形式的我们作品的非法拷贝,请立即向我们提供位置地址或网站名称,以便我们寻求补救。

请通过<[copyright@packtpub.com](mailto:copyright@packtpub.com)>联系我们,获取疑似盗版资料的链接。

我们感谢您在保护我们的作者方面的帮助,以及我们为您带来有价值内容的能力。

问题

如果您对本书的任何方面有问题,可以在<[questions@packtpub.com](mailto:questions@packtpub.com)>联系我们,我们将尽最大努力解决问题。

一、为可重现的数据分析奠定基础

在本章中,我们将介绍以下食谱:

  • 设置 Python
  • 安装数据科学工具箱
  • 使用 virtualenv 和 virtualenvwrapper 创建虚拟环境
  • 用 Docker 映像沙箱化 Python 应用程序
  • 跟踪 IPython 笔记本中的软件包版本和历史记录
  • 配置 IPython
  • 学习记录以进行可靠的错误检查
  • 单元测试你的代码
  • 配置 Pandas
  • 配置 matplotlib
  • 播种随机数生成器和 NumPy 打印选项
  • 标准化报告、代码样式和数据访问

简介

可重现的数据分析是好科学的基石。在当今飞速发展的科学和技术世界中,再现性是一个热门话题。再现性是降低其他人的障碍。这可能看起来很奇怪或没有必要,但可重复的分析对于让你的工作得到别人的认可至关重要。如果有很多人肯定你的成绩,对你的事业会有积极的影响。然而,可再现的分析是困难的。它有重要的经济后果,正如你可以在弗里德曼有限公司,考克伯恩即时消息,西蒙科 TS (2015)临床前研究的可重复性经济学中读到的。PLoS Biol 13(6): e1002165。doi:10.1371/journal . pbio . 1002165。

所以再现性对社会和你都很重要,但是它如何应用于 Python 用户呢?我们希望通过以下方式降低其他人的壁垒:

  • 提供关于我们使用的软件和硬件的信息,包括版本。
  • 共享虚拟环境。
  • 记录程序行为。
  • 单元测试代码。这也是一种文档。
  • 共享配置文件。
  • 植入随机生成器并确保程序行为尽可能具有确定性。
  • 标准化报告、数据访问和代码风格。

我为这本书创建了dautil包,你可以用 pip 安装,或者从这本书的代码包中提供的源文件中安装。如果赶时间,运行$ python install_ch1.py安装本章大部分软件,包括dautil。我创建了一个测试 Docker 映像,如果你不想安装除 Docker 之外的任何东西,你可以使用它(见食谱,用 Docker 映像沙箱化 Python 应用程序)。

建立 Python

Anaconda 是一个用于数据分析和科学计算的免费 Python 发行版。它有自己的包装经理,康达。发行版包括 200 多个 Python 包,非常方便。对于休闲用户来说, Miniconda 配送可能是更好的选择。Miniconda 包含 conda 包管理器和 Python。技术编辑使用 Anaconda,我也是。但是不要担心,我将在本书中为不使用 Anaconda 的读者描述替代安装说明。在这个食谱中,我们将安装 Anaconda 和 Miniconda,并创建一个虚拟环境。

做好准备

安装 Anaconda 和 Miniconda 的步骤类似。显然,Anaconda 需要更多的磁盘空间。按照阿纳康达网站http://conda.pydata.org/docs/install/quick.html上的说明进行操作(2016 年 3 月检索)。首先,您必须下载适合您的操作系统和 Python 版本的安装程序。有时,您可以在图形用户界面和命令行安装程序之间进行选择。我使用了 Python 3.4 安装程序,尽管我的系统 Python 版本是 v2.7。这是可能的,因为 Anaconda 自带 Python。在我的机器上,Anaconda 安装程序在我的主目录中创建了一个anaconda目录,需要大约 900 MB。Miniconda 安装程序会在您的主目录中安装一个miniconda目录。

怎么做...

  1. 现在安装了 Anaconda 或 Miniconda,用以下命令列出包:

    $ conda list
    
    
  2. 为了重现性,最好知道我们可以出口包装:

    $ conda list --export
    
    
  3. The preceding command prints packages and versions on the screen, which you can save in a file. You can install these packages with the following command:

    $ conda create -n ch1env --file <export file>
    
    

    该命令还会创建一个名为ch1env的环境。

  4. 以下命令创建一个简单的testenv环境:

    $ conda create --name testenv python=3
    
    
  5. 在 Linux 和 Mac OS X 上,使用以下命令切换到该环境:

    $ source activate testenv
    
    
  6. 在 Windows 上,我们不需要source。切换回来的语法类似:

    $ [source] deactivate
    
    
  7. 以下命令打印 YAML 环境的导出信息(在下一节中解释)格式:

    $ conda env export -n testenv
    
    
  8. 要删除环境,请键入以下内容(注意,即使删除后,环境的名称仍存在于~/.conda/environments.txt ):

    $ conda remove -n testenv --all
    
    
  9. Search for a package as follows:

    $ conda search numpy
    
    

    在这个例子中,我们搜索了 NumPy 包。如果 NumPy 已经存在,Anaconda 会在相应条目的输出中显示一个星号。

  10. 更新分布如下:

```py
$ conda update conda

```

还有更多...

.condarc配置文件遵循 YAML 语法。

YAML 是一种人类可读的配置文件格式,扩展名为.yaml.yml。《YAML》最初于 2011 年上映,最新一部于 2009 年上映。 YAML 主页位于http://yaml.org/(2015 年 7 月检索)。

可以在http://conda.pydata.org/docs/install/sample-condarc.html找到一个配置文件样本(2015 年 7 月检索)。相关文件在http://conda.pydata.org/docs/install/config.html(2015 年 7 月检索)。

另见

安装数据科学工具箱

数据科学工具箱 ( DST )是基于 Ubuntu 的虚拟环境,使用 Python 和 r 进行数据分析,由于 DST 是虚拟环境,我们可以安装在各种操作系统上。我们会在本地安装 DST,需要VirtualBox****游民。VirtualBox 是一个虚拟机应用程序,最初由 Innotek GmbH 在 2007 年创建。游民是虚拟机应用程序的包装器,比如 Mitchell Hashimoto 创建的 VirtualBox 。

做好准备

你需要有大约 2 到 3 GB 的空闲空间给 VirtualBox、游民和 DST 本身。这可能因操作系统而异。

怎么做...

安装钻杆测试需要以下步骤:

  1. 安装 VirtualBox 方法是从https://www.virtualbox.org/wiki/Downloads(2015 年 7 月检索)下载操作系统和架构的安装程序并运行。我自己安装了 VirtualBox 4.3.28-100309,但是你可以只安装当时最新的 VirtualBox 版本。

  2. 通过从https://www.vagrantup.com/downloads.html下载操作系统和架构的安装程序来安装游民(2015 年 7 月检索)。我安装了游民 1.7.2,如果可以的话,你可以再安装一个更新的版本。

  3. Create a directory to hold the DST and navigate to it with a terminal. Run the following command:

    $ vagrant init data-science-toolbox/dst
    $ vagrant up
    
    

    第一个命令创建一个VagrantFile配置文件。大部分内容都被注释掉了,但是该文件确实包含到可能有用的文档的链接。第二个命令创建 DST 并启动下载,这可能需要几分钟的时间。

  4. 按照以下方式连接到虚拟环境(在 Windows 上使用 putty):

    $ vagrant ssh
    
    
  5. View the preinstalled Python packages with the following command:

    vagrant@data-science-toolbox:~$ pip freeze
    
    

    名单挺长的;就我而言,它包含 32 个包裹。截至 2015 年 7 月的 DST Python 版本为 2.7.6。

  6. 完成夏令时后,注销并暂停(也可以完全暂停)虚拟机:

    vagrant@data-science-toolbox:~$ logout
    Connection to 127.0.0.1 closed.
    $ vagrant suspend
    ==> default: Saving VM state and suspending execution...
    
    

它是如何工作的...

虚拟机在软件中模拟计算机。 VirtualBox 是一个创建和管理虚拟机的应用程序。VirtualBox 将其虚拟机存储在您的主文件夹中,该特定虚拟机占用约 2.2 GB 的存储空间。

Ubuntu 是一个开源的 Linux 操作系统,它的许可证允许我们创建虚拟机。Ubuntu 有几个版本;我们可以通过lsb_release命令获得更多信息:

vagrant@data-science-toolbox:~$ lsb_release -a
No LSB modules are available.
Distributor ID:    Ubuntu
Description:    Ubuntu 14.04 LTS
Release:    14.04
Codename:    trusty

游民过去只使用 VirtualBox,但目前它也支持 VMware、KVM、Docker 和 Amazon EC2。游民称虚拟机为盒子。这些盒子中的一部分在 http://www.vagrantbox.es/(2015 年 7 月检索)提供给所有人。

另见

使用 virtualenv 和 virtualenvwrapper 创建虚拟环境

虚拟环境为小项目提供依赖隔离。他们也保持你的site-packages目录小。从 Python 3.3 开始,virtualenv成为了标准 Python 发行版的一部分。virtualenvwrapper Python 项目为虚拟环境管理提供了一些额外的便利功能。我将在这个食谱中演示 virtualenv 和 virtualenvwrapper 功能。

做好准备

您需要 Python 3.3 或更高版本。您可以使用pip命令安装virtualenvwrapper,如下所示:

$ [sudo] pip install virtualenvwrapper

在 Linux 和 Mac 上,有必要做一些额外的工作——为虚拟环境指定一个目录并获取脚本:

$ export WORKON_HOME=/tmp/envs
$ source /usr/local/bin/virtualenvwrapper.sh

Windows 有一个单独的版本,您可以使用以下命令安装:

$ pip install virtualenvwrapper-win

怎么做...

  1. 使用 Python 发行版的pyvenv脚本部分为给定目录创建一个虚拟环境:

    $ pyvenv /tmp/testenv
    $ ls
    bin        include        lib        pyvenv.cfg
    
    
  2. 在这个例子中,我们在/tmp目录中创建了一个testenv目录,其中有几个目录和一个配置文件。配置文件pyvenv.cfg包含 Python 版本和 Python 发行版的主目录。

  3. Activate the environment on Linux or Mac by sourcing the activate script, for example, with the following command:

    $ source bin/activate
    
    

    在 Windows 上,使用activate.bat文件。

  4. You can now install packages in this environment in isolation. When you are done with the environment, switch back on Linux or Mac with the following command:

    $ deactivate
    
    

    在 Windows 上,使用deactivate.bat文件。

  5. 或者,您可以使用 virtualenvwrapper。使用以下命令创建并切换到虚拟环境:

    vagrant@data-science-toolbox:~$ mkvirtualenv env2
    
    
  6. 使用deactivate命令

    (env2)vagrant@data-science-toolbox:~$ deactivate
    
    

    关闭环境

  7. rmvirtualenv命令删除环境:

    vagrant@data-science-toolbox:~$ rmvirtualenv env2
    
    

另见

用 Docker 映像沙箱化 Python 应用程序

Docker 使用 Linux 内核特性来提供额外的虚拟化层。Docker 由所罗门·海克斯于 2013 年创建。 Boot2Docker 也允许我们在 Windows 和 Mac OS X 上安装 Docker。Boot2Docker 使用一个 VirtualBox 虚拟机,该虚拟机包含一个带有 Docker 的 Linux 环境。在本食谱中,我们将设置 Docker 并下载continuumio/miniconda3 Docker 图像。

做好准备

Docker 安装文档保存在https://docs.docker.com/index.html(2015 年 7 月检索)。我用 Boot2Docker 安装了 Docker 1.7.0。安装程序需要大约 133 兆字节。然而,如果你想遵循整个食谱,你将需要几千兆字节。

怎么做...

  1. 安装 Boot2Docker 后,您需要初始化环境。这个只需要一次,Linux 用户不需要这一步:

    $ boot2docker init
    Latest release for github.com/boot2docker/boot2docker is v1.7.0
    Downloading boot2docker ISO image...
    Success: downloaded https://github.com/boot2docker/boot2docker/releases/download/v1.7.0/boot2docker.iso
    
    
  2. In the preceding step, you downloaded a VirtualBox VM to a directory such as /VirtualBox\ VMs/boot2docker-vm/.

    Mac OS X 和 Windows 用户的下一步是启动虚拟机:

    $ boot2docker start
    
    
  3. Check the Docker environment by starting a sample container:

    $ docker run hello-world
    
    

    一些人报告了一个有希望的暂时问题,即无法连接。这个问题可以通过发出带有额外参数的命令来解决,例如:

    $ docker [--tlsverify=false] run hello-world
    
    
  4. Docker 图像可以公开。我们可以搜索这样的图片并下载。在设置 Python中,我们安装了 Python;但是,Anaconda 和 Miniconda Docker 映像也存在。使用以下命令:

    $ docker search continuumio
    
    
  5. 前面的命令显示了来自开发 Anaconda 和 Miniconda 的公司连续体分析的 Docker 图像列表。下载 Miniconda 3 Docker 镜像如下(如果你喜欢使用我的容器,跳过这个):

    $ docker pull continuumio/miniconda3
    
    
  6. Start the image with the following command:

    $ docker run -t -i continuumio/miniconda3 /bin/bash
    
    

    我们从图像中的根开始。

  7. 命令$ docker images也应该列出continuumio/miniconda3图像。如果你不想为这本书安装太多软件(可能只有 Docker 和 Boot2Docker),你应该使用我创建的映像。它使用continuumio/miniconda3图像作为模板。此映像允许您在计算机的当前工作目录中执行 Python 脚本,同时使用 Docker 映像中已安装的软件:

    $ docker run -it -p 8888:8888 -v $(pwd):/usr/data -w /usr/data "ivanidris/pydacbk:latest" python <somefile>.py 
    
    
  8. 您也可以使用以下命令在当前工作目录中运行 IPython 笔记本:

    $ docker run -it -p 8888:8888 -v $(pwd):/usr/data -w /usr/data "ivanidris/pydacbk:latest" sh -c "ipython notebook --ip=0.0.0.0 --no-browser"
    
    
  9. Then, go to either http://192.168.59.103:8888 or http://localhost:8888 to view the IPython home screen. You might have noticed that the command lines are quite long, so I will post additional tips and tricks to make life easier on https://pythonhosted.org/dautil (work in progress).

    Boot2Docker 虚拟机共享 Mac OS X 上的/Users目录和 Windows 上的C:\Users目录。一般来说,在其他操作系统上,我们可以从容器中装载目录和复制文件,如https://docs.docker.com/userguide/dockervolumes/中所述(2015 年 7 月检索)。

  10. 使用以下命令关闭虚拟机(除非您在 Linux 上,而是使用docker命令):

```py
$ boot2docker down

```

它是如何工作的...

Docker Hub 充当公共和私有 Docker 映像的中央注册中心。在这个食谱中,我们通过这个注册表下载了图像。要将图像推送到 Docker Hub,我们需要首先创建一个本地注册表。Docker Hub 的工作方式在许多方面与 GitHub 等源代码存储库的工作方式类似。您可以提交更改以及推送、拉取和标记图像。continuumio/miniconda3图像配置了一个特殊文件,可以在https://github . com/ContinuumIO/docker-img/blob/master/miniconda 3/docker file找到(2015 年 7 月检索)。在这个文件中,您可以读取哪个映像被用作基础,维护者的名称,以及用于构建映像的命令。

另见

在 IPython 笔记本中记录软件包版本和历史

IPython 笔记本 于 2011 年 12 月加入 IPython 0.12。许多 Python 觉得 IPython 笔记本对于可再现的数据分析至关重要。 IPython 笔记本可与 Mathematica、MATLAB 和 Maple 等商用产品相媲美。这是一个基于交互式网络浏览器的环境。在本食谱中,我们将看到如何在可再现数据分析的上下文中跟踪包版本和存储 IPython 会话。对了,IPython 笔记本已经改名为 Jupyter 笔记本。

做好准备

对于此配方,您将需要最近的 IPython 安装。安装 IPython 的说明在http://ipython.org/install.html(检索于 2015 年 7 月)。使用 pip 命令安装它:

$ [sudo] pip install ipython/jupyter

如果您已经通过 Anaconda 安装了 IPython,请使用以下命令检查更新:

$ conda update conda
$ conda update ipython ipython-notebook ipython-qtconsole

我有 IPython 3.2.0 作为 Anaconda 发行版的一部分。

怎么做...

我们将安装一个 Python 会话日志,并使用水印扩展来跟踪包版本和其他信息。启动 IPython 外壳或笔记本。当我们开始一个会话时,我们可以使用命令行开关--logfile=<file name>.py。在本食谱中,我们使用%logstart魔法(IPython 术语)功能:

In [1]: %logstart cookbook_log.py rotate
Activating auto-logging. Current session state plus future input saved.
Filename       : cookbook_log.py
Mode           : rotate
Output logging : False
Raw input log  : False
Timestamping   : False
State          : active

本例调用开始以旋转模式将记录到文件中。文件名和模式都是可选的。按照以下步骤关闭并重新打开日志记录:

In [2]: %logoff
Switching logging OFF

In [3]: %logon
Switching logging ON

使用以下命令从 Github 安装watermark魔法:

In [4]: %install_ext https://raw.githubusercontent.com/rasbt/watermark/master/watermark.py

前一行下载了一个 Python 文件,在我的例子中,下载到~/.ipython/extensions/watermark.py。通过键入以下行来加载扩展:

%load_ext watermark

该扩展可以放置时间戳以及软件和硬件信息。使用以下命令获取其他使用文档和版本(我安装了水印 1.2.2):

%watermark?

例如,在没有任何参数的情况下调用watermark:

In [7]: %watermark
… Omitting time stamp …

CPython 3.4.3
IPython 3.2.0

compiler   : Omitting
system     : Omitting
release    : 14.3.0
machine    : x86_64
processor  : i386
CPU cores  : 8
interpreter: 64bit

出于个人原因,我省略了时间戳和其他信息。更完整的示例如下:作者姓名(-a)、以逗号分隔的字符串形式指定的软件包版本(-p)以及基于strftime()格式的自定义时间(-c):

In [8]: %watermark -a "Ivan Idris" -v -p numpy,scipy,matplotlib -c '%b %Y' -w
Ivan Idris 'Jul 2015'

CPython 3.4.3
IPython 3.2.0

numpy 1.9.2
scipy 0.15.1
matplotlib 1.4.3
watermark v. 1.2.2

它是如何工作的...

IPython 记录器将您键入的命令写入 Python 文件。大多数行采用以下格式:

get_ipython().magic('STRING_YOU_TYPED')

您可以使用%load <log file>重新播放会话。下表描述了日志记录模式:

|

方式

|

描述

|
| --- | --- |
| 超过 | 此模式会覆盖现有的日志文件。 |
| 支持 | 如果存在同名的日志文件,旧文件将被重命名。 |
| 附加 | 这种模式将行附加到已经存在的文件中。 |
| 辐状的 | 此模式通过递增数字来旋转日志文件,这样日志文件就不会变得太大。 |

我们使用了互联网上提供的自定义魔法功能。该函数的代码在一个 Python 文件中,应该很容易理解。如果你想要不同的行为,你只需要修改文件。

另见

配置 IPython

IPython 有一个精心的配置和定制系统。该系统的组成如下:

  • IPython 提供默认配置文件,但是我们可以创建自己的配置文件
  • 外壳、内核、Qt 控制台和笔记本的各种可设置选项
  • 提示和颜色的自定义
  • 我们在中看到的扩展在 IPython 笔记本中记录包版本和历史
  • 每个配置文件的启动文件

我将在这个食谱中演示其中的一些成分。

做好准备

这个食谱需要 IPython,所以(如有必要)看一下准备部分,在 IPython 笔记本中记录包版本和历史。

怎么做...

让我们从一个启动文件开始。我在.ipython/profile_default/startup的主目录中有一个目录,属于默认配置文件。这个目录是用来存放启动文件的。IPython 检测这个目录中的 Python 文件,并按照文件名的词法顺序执行它们。由于词法顺序,用数字和字符串的组合来命名启动文件很方便,例如0000-watermark.py。将以下代码放入启动文件:

get_ipython().magic('%load_ext watermark')
get_ipython().magic('watermark -a "Ivan Idris" -v -p numpy,scipy,matplotlib -c \'%b %Y\' -w')

这个启动文件加载了我们在中使用的扩展名,跟踪 IPython 笔记本中的包版本和历史,并显示关于包版本的信息。其他用例包括导入模块和定义函数。IPython 将命令存储在 SQLite 数据库中,因此您可以收集统计数据来查找常见的使用模式。以下脚本从数据库中打印源代码行和按计数排序的默认配置文件的相关计数(代码在本书代码包的ipython_history.py文件中):

import sqlite3
from IPython.utils.path import get_ipython_dir
import pprint
import os

def print_history(file):
    with sqlite3.connect(file) as con:
        c = con.cursor()
 c.execute("SELECT count(source_raw) as csr,\
 source_raw FROM history\
 GROUP BY source_raw\
 ORDER BY csr")
        result = c.fetchall()
        pprint.pprint(result)
        c.close()

hist_file = '%s/profile_default/history.sqlite' % get_ipython_dir()

if os.path.exists(hist_file):
    print_history(hist_file)
else:
    print("%s doesn't exist" % hist_file)

高亮显示的 SQL 查询完成了大部分工作。代码是不言自明的。不清楚的话,推荐阅读我的书【Python 数据分析】的第八章文本挖掘和社交网络,Packt 出版。

我提到的另一个配置选项是配置文件。我们可以使用默认配置文件,也可以根据每个项目或功能创建自己的配置文件。配置文件充当沙箱,您可以单独配置它们。以下是创建配置文件的命令:

$ ipython profile create [newprofile]

配置文件为 Python 文件,名称以_config.py结尾。在这些文件中,您可以设置各种 IPython 选项。设置自动记录 IPython 会话的选项,如下所示:

c = get_config()

c.TerminalInteractiveShell.logstart=True

第一行通常包含在配置文件中,并获取根 IPython 配置对象。最后一行告诉 IPython,我们希望在启动时立即开始登录,这样您就不必键入%logstart

或者,您也可以使用以下命令设置日志文件名:

c.TerminalInteractiveShell.logfile='mylog_file.py'

您也可以使用下面的配置行来确保以追加模式登录:

c.TerminalInteractiveShell.logappend='mylog_file.py'

另见

学习记录以进行可靠的错误检查

笔记本有助于记录你做了什么和哪里出错了。日志以类似的方式工作,我们可以用标准的 Python logging库记录错误和其他有用的信息。

对于可重现的数据分析,了解我们的 Python 脚本导入的模块是很好的。在这个食谱中,我将介绍一个来自dautil的最小 API,它以最大努力的方式记录导入模块的包版本。

做好准备

在这个食谱中,我们进口 NumPy 和 Pandas,所以你可能需要进口它们。Pandas 安装说明见配置 Pandas食谱。NumPy 的安装说明可以在http://docs.scipy.org/doc/numpy/user/install.html找到(2015 年 7 月检索)。或者,使用以下命令用 pip 安装 NumPy:

$ [sudo] pip install numpy

Anaconda 用户的命令如下:

$ conda install numpy

我已经通过 Anaconda 安装了 NumPy 1.9.2。我们还要求AppDirs找到合适的目录来存储日志。使用以下命令安装它:

$ [sudo] pip install appdirs

我的系统上有 AppDirs 1.4.0。

怎么做...

要记录日志,我们需要创建和设置记录器。我们可以用代码设置记录器,也可以使用配置文件。用代码配置记录器是更灵活的选择,但是配置文件更容易阅读。我使用dautil中的配置文件:

[loggers]
keys=root

[handlers]
keys=consoleHandler,fileHandler

[formatters]
keys=simpleFormatter

[logger_root]
level=DEBUG
handlers=consoleHandler,fileHandler

[handler_consoleHandler]
class=StreamHandler
level=INFO
formatter=simpleFormatter
args=(sys.stdout,)

[handler_fileHandler]
class=dautil.log_api.VersionsLogFileHandler
formatter=simpleFormatter
args=('versions.log',)

[formatter_simpleFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
datefmt=%d-%b-%Y

该文件配置一个记录器以记录到一个具有DEBUG级别的文件和一个具有INFO级别的屏幕。因此,记录器记录到文件中的比记录到屏幕上的多。该文件还指定了日志消息的格式。我在dautil创建了一个微小的应用编程接口,它创建了一个带有get_logger()功能的记录器,并使用它来记录带有log()功能的客户端程序的包版本。代码在dautillog_api.py文件中:

from pkg_resources import get_distribution
from pkg_resources import resource_filename
import logging
import logging.config
import pprint
from appdirs import AppDirs
import os

def get_logger(name):
    log_config = resource_filename(__name__, 'log.conf')
    logging.config.fileConfig(log_config)
    logger = logging.getLogger(name)

    return logger

def shorten(module_name):
    dot_i = module_name.find('.')

    return module_name[:dot_i]

def log(modules, name):
    skiplist = ['pkg_resources', 'distutils']

    logger = get_logger(name)
    logger.debug('Inside the log function')

    for k in modules.keys():
        str_k = str(k)

        if '.version' in str_k:
            short = shorten(str_k)

            if short in skiplist:
                continue

            try:
                logger.info('%s=%s' % (short,    
                            get_distribution(short).version))
            except ImportError:
                logger.warn('Could not impport', short)

class VersionsLogFileHandler(logging.FileHandler):
    def __init__(self, fName):
        dirs = AppDirs("PythonDataAnalysisCookbook", 
                       "Ivan Idris")
        path = dirs.user_log_dir
        print(path)

        if not os.path.exists(path):
            os.mkdir(path)

        super(VersionsLogFileHandler, self).__init__(
              os.path.join(path, fName))

使用该应用编程接口的程序位于本书代码包的log_demo.py文件中:

import sys
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
from dautil import log_api

log_api.log(sys.modules, sys.argv[0])

它是如何工作的...

我们配置了一个写入文件的处理程序(VersionsLogFileHandler)和一个在屏幕上显示消息的处理程序(StreamHandler)。StreamHandler是 Python 标准库中的一个类。为了配置日志消息的格式,我们使用了 Python 标准库中的SimpleFormater类。

我制作的应用编程接口遍历sys.modules变量中列出的模块,并试图获得模块的版本。有些模块与数据分析无关,所以我们跳过它们。应用编程接口的log()功能使用debug()方法记录一条DEBUG级别的消息。info()方法在INFO级别记录包版本。

另见

单元测试你的代码

如果代码没有做到你想要的,就很难进行可重现的数据分析。控制代码的一种方法是测试它。如果你已经手动测试过代码,你就会知道它是重复和无聊的。当一项任务无聊且重复时,你应该自动化它。

单元测试自动化测试,希望你熟悉。当你第一次学习单元测试时,你从简单的测试开始,比如比较字符串或数字。然而,当文件输入/输出或其他资源进入画面时,你就碰壁了。事实证明,在 Python 中,我们可以很容易地模拟资源或外部 API。所需的包甚至是标准 Python 库的一部分。在学习记录健壮的错误检查食谱中,我们将消息记录到一个文件中。如果我们对这段代码进行单元测试,我们不想从测试代码中触发日志记录。在这个食谱中,我将向您展示如何模拟记录器和我们需要的其他软件组件。

做好准备

熟悉log_api.py中的待测代码。

怎么做...

该配方的代码在dautiltest_log_api.py文件中。我们从导入被测模块和单元测试所需的 Python 功能开始:

from dautil import log_api
import unittest
from unittest.mock import create_autospec
from unittest.mock import patch

定义包含测试代码的类:

class TestLogApi(unittest.TestCase):

使用以下代码行使单元测试可执行:

if __name__ == '__main__':
    unittest.main()

如果我们用错误的参数数调用 Python 函数,我们期望得到一个TypeError。以下测试对此进行了检查:

    def test_get_logger_args(self):
 mock_get_logger =       create_autospec(log_api.get_logger, return_value=None)
        mock_get_logger('test')
        mock_get_logger.assert_called_once_with('test')

    def test_log_args(self):
 mock_log = create_autospec(log_api.log, return_value=None)
        mock_log([], 'test')
        mock_log.assert_called_once_with([], 'test')

        with self.assertRaises(TypeError):
            mock_log()

        with self.assertRaises(TypeError):
            mock_log('test')

我们使用unittest.create_autospec()函数来模拟测试中的函数。模拟 Python logging包如下:

 @patch('dautil.log_api.logging')
    def test_get_logger_fileConfig(self, mock_logging):
        log_api.get_logger('test')
        self.assertTrue(mock_logging.config.fileConfig.called)

装饰者用模拟代替了记录。我们也可以用类似命名的函数进行修补。修补技巧非常有用。用以下方法测试我们的get_logger()功能:

 @patch('dautil.log_api.get_logger')
    def test_log_debug(self, amock):
        log_api.log({}, 'test')
        self.assertTrue(amock.return_value.debug.called)
        amock.return_value.debug.assert_called_once_with(
                'Inside the log function')

前面几行检查是否调用了debug()以及使用了哪些参数。以下两种测试方法演示了如何使用多个@patch装饰器:

    @patch('dautil.log_api.get_distribution')
    @patch('dautil.log_api.get_logger')
    def test_numpy(self, m_get_logger, m_get_distribution):
        log_api.log({'numpy.version': ''}, 'test')
        m_get_distribution.assert_called_once_with('numpy')
        self.assertTrue(m_get_logger.return_value.info.called)

    @patch('dautil.log_api.get_distribution')
    @patch('dautil.log_api.get_logger')
    def test_distutils(self, amock, m_get_distribution):
        log_api.log({'distutils.version': ''}, 'test')
        self.assertFalse(m_get_distribution.called)

它是如何工作的...

嘲讽是一种窥探对象和功能的技巧。我们用我们自己的间谍代替他们,我们给他们足够的信息来避免被发现。间谍向我们报告谁联系了他们以及他们收到的任何有用的信息。

另见

配置 Pandas

Pandas 库有十几个配置选项,如http://pandas.pydata.org/pandas-docs/dev/options.html所述(2015 年 7 月检索)。

Pandas 库是 Python 开源软件,最初是为计量经济学数据分析而创建的。它使用受 R 编程语言启发的数据结构。

您可以使用虚线符号或通过函数来设置和获取属性。还可以将选项重置为默认值,并获取相关信息。option_context()函数允许您使用 Python with语句将选项的范围限制在一个上下文中。在这个食谱中,我将演示 Pandas 的配置和一个简单的应用编程接口来设置和重置我认为有用的选项。两个选项分别是precisionmax_rows。第一个选项指定输出的浮点精度。第二个选项指定了一只 PandasDataFrame在屏幕上打印的最大行数。

做好准备

这个食谱需要 Pandas 和 NumPy。安装 NumPy 的说明在中给出,学习记录以进行可靠的错误检查。Pandas 安装文档可在http://pandas.pydata.org/pandas-docs/dev/install.html找到(2015 年 7 月检索)。安装 Pandas 的推荐方式是通过 Python。我已经通过 Anaconda 安装了 Pandas 0.16.2。您可以使用以下命令更新您的 PythonPandas:

$ conda update pandas

怎么做...

来自dautiloptions.py文件的以下代码定义了一个简单的应用编程接口来设置和重置选项:

import pandas as pd

def set_pd_options():
    pd.set_option('precision', 4) 
    pd.set_option('max_rows', 5)

def reset_pd_options():
    pd.reset_option('precision') 
    pd.reset_option('max_rows')

本书代码包configure_pd.py中的脚本使用了以下 API:

from dautil import options
import pandas as pd
import numpy as np
from dautil import log_api

printer = log_api.Printer()
print(pd.describe_option('precision'))
print(pd.describe_option('max_rows'))

printer.print('Initial precision', pd.get_option('precision'))
printer.print('Initial max_rows', pd.get_option('max_rows'))

# Random pi's, should use random state if possible
np.random.seed(42)
df = pd.DataFrame(np.pi * np.random.rand(6, 2))
printer.print('Initial df', df)

options.set_pd_options()
printer.print('df with different options', df)

options.reset_pd_options()
printer.print('df after reset', df)

如果您运行脚本,您会得到选项的描述,这些描述太长了,无法在这里显示。getter 给出以下输出:

'Initial precision'
7

'Initial max_rows'
60

然后,我们用随机数据创建一个 PandasDataFrame表。初始打印输出如下所示:

'Initial df'
 0         1
0  1.176652  2.986757
1  2.299627  1.880741
2  0.490147  0.490071
3  0.182475  2.721173
4  1.888459  2.224476
5  0.064668  3.047062 

打印输出来自log_api.py中的以下类:

class Printer():
    def __init__(self, modules=None, name=None):
        if modules and name:
            log(modules, name)

    def print(self, *args):
        for arg in args:
            pprint.pprint(arg)

使用dautil API 设置选项后,pandas 隐藏了一些行,浮点数看起来也不同:

'df with different options'
 0      1
0   1.177  2.987
1   2.300  1.881
..    ...    ...
4   1.888  2.224
5   0.065  3.047

[6 rows x 2 columns]

由于行被截断,Pandas 告诉我们DataFrame表有多少行和列。重置选项后,我们会得到原始打印输出。

配置 matplotlib

matplotlib 库允许通过matplotlibrc文件和 Python 代码进行配置。最后一个选项是我们将在这个食谱中做什么。如果您的数据分析能力很强,小的配置调整应该无关紧要。然而,有一致的、吸引人的情节也无妨。另一种选择是应用样式表,样式表是与matplotlibrc文件类似的文件。然而,在我看来,最好的选择是在 matplotlib 之上使用 Seaborn。我将在第 2 章创建有吸引力的数据可视化中更详细地讨论 Seaborn 和 matplotlib。

做好准备

你需要为这个食谱安装 matplotlib。更多信息请访问http://matplotlib.org/users/installing.html(2015 年 7 月检索)。我通过 Anaconda 拥有 matplotlib 1.4.3。使用 Anaconda 安装 Seaborn:

$ conda install seaborn

我已经通过 Anaconda 安装了 Seaborn 0.6.0。

怎么做...

我们可以通过类似字典的变量来设置选项。以下功能从dautil中的options.py文件设置三个选项:

def set_mpl_options():
    mpl.rcParams['legend.fancybox'] = True
    mpl.rcParams['legend.shadow'] = True
    mpl.rcParams['legend.framealpha'] = 0.7

前三个选项和传说有关。第一个选项指定图例的圆角,第二个选项允许显示阴影,第三个选项使图例稍微透明。matplotlib rcdefaults()功能重置配置。

为了演示这些选项,让我们使用 matplotlib 提供的示例数据。进口情况如下:

import matplotlib.cbook as cbook
import pandas as pd
import matplotlib.pyplot as plt
from dautil import options
import matplotlib as mpl
from dautil import plotting
import seaborn as sns 

数据在 CSV 文件中,包含 AAPL 的股价数据。使用以下命令读取数据并将其存储在 Pandas 中DataFrame:

data = cbook.get_sample_data('aapl.csv', asfileobj=True)
df = pd.read_csv(data, parse_dates=True, index_col=0)

将数据重新采样为月平均值,如下所示:

df = df.resample('M')

完整的代码在本书代码包的configure_matplotlib.ipynb文件中是:

import matplotlib.cbook as cbook
import pandas as pd
import matplotlib.pyplot as plt
from dautil import options
import matplotlib as mpl
from dautil import plotting
import seaborn as sns

data = cbook.get_sample_data('aapl.csv', asfileobj=True)
df = pd.read_csv(data, parse_dates=True, index_col=0)
df = df.resample('M')
close = df['Close'].values
dates = df.index.values
fig, axes = plt.subplots(4)

def plot(title, ax):
    ax.set_title(title)
    ax.set_xlabel('Date')

    plotter = plotting.CyclePlotter(ax)
    plotter.plot(dates, close, label='Close')
    plotter.plot(dates, 0.75 * close, label='0.75 * Close')
    plotter.plot(dates, 1.25 * close, label='1.25 * Close')

    ax.set_ylabel('Price ($)')
    ax.legend(loc='best')

plot('Initial', axes[0])
sns.reset_orig()
options.set_mpl_options()

plot('After setting options', axes[1])

sns.reset_defaults()
plot('After resetting options', axes[2])

with plt.style.context(('dark_background')):
    plot('With dark_background stylesheet', axes[3])
    fig.autofmt_xdate()
    plt.show()

该程序使用默认选项、自定义选项和重置选项后,绘制数据和任意上下带。我从dautilplotting.py文件中使用了以下助手类:

from itertools import cycle

class CyclePlotter():
    def __init__(self, ax):
        self.STYLES = cycle(["-", "--", "-.", ":"])
        self.LW = cycle([1, 2])
        self.ax = ax

    def plot(self, x, y, *args, **kwargs):
        self.ax.plot(x, y, next(self.STYLES),
                     lw=next(self.LW), *args, **kwargs)

该类循环显示不同的线条样式和线条宽度。最终结果见下图:

How to do it...

它是如何工作的...

导入 Seaborn 极大地改变了 matplotlib 剧情的观感。只是暂时评论一下seaborn 线出来说服自己。然而,Seaborn 似乎不能很好地使用我们设置的 matplotlib 选项,除非我们使用 Seaborn 函数reset_orig()reset_defaults()

另见

播种随机数生成器和 NumPy 打印选项

对于可再现的数据分析,我们应该更喜欢确定性算法。有些算法使用随机数,但实际上我们很少使用完全随机数。在numpy.random中提供的算法允许我们指定一个种子值。为了再现性,始终提供种子值很重要,但这很容易忘记。sklearn.utils中的一个实用功能为这个问题提供了解决方案。

NumPy 有一个set_printoptions()功能,控制 NumPy 如何打印数组。显然,打印不应该太影响你的分析质量。然而,如果你想让人们理解并再现你的结果,可读性是很重要的。

做好准备

使用学习日志中的说明安装 NumPy,以进行可靠的错误检查配方。我们需要 scikit-learn,所以看看http://scikit-learn.org/dev/install.html(2015 年 7 月检索)。我已经通过 Anaconda 安装了 scikit-learn 0.16.1。

怎么做...

这个例子的代码在本书代码包的configure_numpy.py文件中:

from sklearn.utils import check_random_state
import numpy as np
from dautil import options
from dautil import log_api

random_state = check_random_state(42)
a = random_state.randn(5)

random_state = check_random_state(42)
b = random_state.randn(5)

np.testing.assert_array_equal(a, b)

printer = log_api.Printer()
printer.print("Default options", np.get_printoptions())

pi_array = np.pi * np.ones(30)
options.set_np_options()
print(pi_array)

# Reset
options.reset_np_options()
print(pi_array)

高亮显示的线显示了如何获得以42为种子的 NumPy RandomState对象。在这个例子中,数组ab是相等的,因为我们使用了相同的种子和相同的过程来绘制数字。前面程序的第二部分使用了我在options.py中定义的以下函数:

def set_np_options():
    np.set_printoptions(precision=4, threshold=5,
                        linewidth=65)

def reset_np_options():
    np.set_printoptions(precision=8, threshold=1000,
                        linewidth=75)

以下是设置选项后的输出:

[ 3.1416  3.1416  3.1416 ...,  3.1416  3.1416  3.1416] 

如您所见,NumPy 用省略号替换了一些值,并且它只在十进制符号后显示四位数字。NumPy 默认值如下:

'Default options'
{'edgeitems': 3,
 'formatter': None,
 'infstr': 'inf',
 'linewidth': 75,
 'nanstr': 'nan',
 'precision': 8,
 'suppress': False,
 'threshold': 1000}

另见

标准化报告、代码风格和数据访问

遵循代码风格指南有助于提高代码质量。如果你想让人们轻松地重现你的分析,拥有高质量的代码是很重要的。遵守编码标准的一种方法是用静态代码分析器扫描代码。您可以使用许多代码分析器。在本食谱中,我们将使用 pep8 分析仪。一般来说,代码分析器相互补充,或者可能略有重叠,因此您并不局限于 pep8。

方便的数据访问对于可重现的分析至关重要。在我看来,最好的数据访问类型是使用专门的应用编程接口和本地数据。我将介绍我创建的一个dautil模块,用于加载荷兰 KNMI 提供的天气数据。

报告通常是数据分析项目的最后阶段。我们可以使用各种格式报告我们的发现。在本食谱中,我们将着重于用tabulate模块将我们的报告制成表格。landslide工具根据各种格式(如重组文本)创建幻灯片。

做好准备

你需要 pep8 和表格。pep8 的快速指南可在https://pep8.readthedocs.org/en/latest/intro.html获得(2015 年 7 月检索)。我已经通过 Anaconda 安装了 pep8 1.6.2。您可以使用pip命令安装 joblib、制表和滑坡。

我把 0.7.5 和滑坡 1.1.3 列成了表格。

怎么做...

下面是 pep8 会话的一个示例:

$ pep8 --first log_api.py
log_api.py:21:1: E302 expected 2 blank lines, found 1
log_api.py:44:33: W291 trailing whitespace
log_api.py:50:60: E225 missing whitespace around operator

–-first开关发现第一次出现错误。在前面的示例中,pep8 报告了发生错误的行号、错误代码和错误的简短描述。我准备了一个模块,专门用于数据集的数据访问,我们将在几章中使用。我们从访问存储在泡菜中的 PandasDataFrame开始,泡菜中包含来自荷兰德比尔特气象站的选定天气数据。我通过下载一个 zip 文件,提取数据文件,并将数据加载到 PandasDataFrame表中来创建泡菜。我应用了最少的数据转换,包括值的乘法和将空字段转换为 NaNs。代码在dautildata.py文件中。我不会详细讨论这段代码,因为我们只需要从 pickle 加载数据。但是如果你想自己下载数据,可以用我在data.py中定义的静态方法。下载数据当然会给你更多最近的数据,但是如果你用我的泡菜代替,你会得到稍微不同的结果。以下代码显示了本书代码包中report_weather.py文件中pandas.DataFrame.describe()方法的基本描述性统计:

from dautil import data
from dautil import report
import pandas as pd
import numpy as np
from tabulate import tabulate

df = data.Weather.load()
headers = [data.Weather.get_header(header) 
           for header in df.columns.values.tolist()]
df = df.describe()

然后,代码用dautil.RSTWriter创建一个重构文本格式的slides.rst文件。这只是简单的字符串连接和写入文件的问题。以下代码中突出显示的行显示了从pandas.DataFrame对象创建表格网格的tabulate()调用:

writer = report.RSTWriter()
writer.h1('Weather Statistics')
writer.add(tabulate(df, headers=headers, 
 tablefmt='grid', floatfmt='.2f'))
writer.divider()
headers = [data.Weather.get_header(header) 
           for header in df.columns.values.tolist()]
builder = report.DFBuilder(df.columns)
builder.row(df.iloc[7].values - df.iloc[3].values)
builder.row(df.iloc[6].values - df.iloc[4].values)
df = builder.build(['ptp', 'iqr'])]

writer.h1('Peak-to-peak and Interquartile Range')

writer.add(tabulate(df, headers=headers, 
 tablefmt='grid', floatfmt='.2f'))
writer.write('slides.rst')
generator = report.Generator('slides.rst', 'weather_report.html')
generator.generate() 

我使用dautil.reportDFBuilder类使用字典来增量创建pandas.DataFrame对象,其中键是最终DataFrame表的列,值是行:

import pandas as pd

class DFBuilder():
    def __init__(self, cols, *args):
        self.columns = cols
        self.df = {}

        for col in self.columns:
            self.df.update({col: []})

        for arg in args:
            self.row(arg)

    def row(self, row):
        assert len(row) == len(self.columns)

        for col, val in zip(self.columns, row):
            self.df[col].append(val)

        return self.df

    def build(self, index=None):
        self.df = pd.DataFrame(self.df)

        if index:
            self.df.index = index

        return self.df

我最终使用landslide和我自己的自定义 CSS 生成了一个 HTML 文件。如果你打开weather_report.html,你会看到第一张幻灯片,有基本的描述性统计:

How to do it...

第二张幻灯片如下所示,包含峰峰值(最小值和最大值之间的差值)和四分位数范围(第三个四分位数和第一个四分位数之间的差值):

How to do it...

另见

二、创建有吸引力的数据可视化

在本章中,我们将介绍:

  • 绘制安斯科姆的四重奏
  • 选择海鸟调色板
  • 选择 matplotlib 颜色映射
  • 与 IPython 笔记本小部件交互
  • 查看散点图矩阵
  • 通过 mpld3 可视化 d3.js
  • 创建热图
  • 将箱式图和核密度图与小提琴图相结合
  • 用蜂巢图可视化网络图
  • 显示地理地图
  • 使用类似 ggplot2 的图
  • 用影响图突出显示数据点

简介

数据分析与其说是一门科学,不如说是一门艺术。创造有吸引力的视觉效果是这门艺术不可或缺的一部分。显然,一个人觉得有吸引力的东西,其他人可能会觉得完全不可接受。就像在艺术中一样,在快速发展的数据分析世界中,观点和品味会随着时间而变化;然而,原则上,没有人是绝对正确或错误的。作为数据艺术家和皮托尼斯塔斯,我们可以从几个库中进行选择,我将介绍 matplotlib、seaborn、Bokeh 和 ggplot。我们在本章中使用的一些软件包的安装说明已经包含在第 1 章为可再现数据分析奠定基础中,因此我不再重复。我将为本章提供一个安装脚本(仅使用 pip 您甚至可以使用我在上一章中描述的 Docker 图像。我决定不把 Proj 制图库和 R 相关的库包括在图像中,因为它们的大小。所以对于本章涉及的两个食谱,你可能要做额外的工作。

描绘安斯科姆的四重奏

安斯科姆的四重奏是一个经典的例子,说明了为什么可视化数据很重要。四重奏由四个具有相似统计属性的数据集组成。每个数据集都有一系列的 x 值和从属的 y 值。我们将在 IPython 笔记本上列出这些指标。但是,如果绘制数据集,它们看起来会有惊人的不同。

怎么做...

对于此配方,您需要执行以下步骤:

  1. 从以下进口开始:

    import pandas as pd
    import seaborn as sns
    import matplotlib.pyplot as plt
    import matplotlib as mpl
    from dautil import report
    from dautil import plotting
    import numpy as np
    from tabulate import tabulate
    
  2. 定义以下函数来计算数据集内xy的平均值、方差和相关性,以及每个数据集线性拟合的斜率和截距:

    df = sns.load_dataset("anscombe")
    
     agg = df.groupby('dataset')\
     .agg([np.mean, np.var])\
     .transpose()
        groups = df.groupby('dataset')
    
        corr = [g.corr()['x'][1] for _, g in groups]
        builder = report.DFBuilder(agg.columns)
        builder.row(corr)
    
        fits = [np.polyfit(g['x'], g['y'], 1) for _, g in groups]
        builder.row([f[0] for f in fits])
        builder.row([f[1] for f in fits])
        bottom = builder.build(['corr', 'slope', 'intercept'])
    
        return df, pd.concat((agg, bottom))
    
  3. 下面的函数返回一个字符串,部分是 Markdown,部分是重组文本,部分是 HTML,因为核心 Markdown 不正式支持表:

    def generate(table):
        writer = report.RSTWriter()
        writer.h1('Anscombe Statistics')
        writer.add(tabulate(table, tablefmt='html', floatfmt='.3f'))
    
        return writer.rst
    
  4. 用 Seaborn lmplot()函数

    def plot(df):
        sns.set(style="ticks")
        g = sns.lmplot(x="x", y="y", col="dataset", 
             hue="dataset", data=df,
             col_wrap=2, ci=None, palette="muted", size=4,
             scatter_kws={"s": 50, "alpha": 1})
    
        plotting.embellish(g.fig.axes)
    

    绘制数据和相应的线性拟合

  5. Display a table with statistics, as follows:

    df, table = aggregate()
    from IPython.display import display_markdown
    display_markdown(generate(table), raw=True)
    

    下表显示了每个数据集几乎相同的统计数据(我修改了 IPython 配置文件中的custom.css文件以获得颜色):

    How to do it...

  6. 以下行绘制数据集:

    %matplotlib inline
    plot(df)
    

最终结果见下图:

How to do it...

一张图胜过千言万语。源代码在本书代码包的anscombe.ipynb文件中。

另见

选择海鸟调色板

Seaborn 调色板类似于 matplotlib 颜色图。颜色可以帮助您发现数据中的模式,并且是一个重要的可视化组件。Seaborn 有各种各样的调色板,我将在这个食谱中尝试可视化。

怎么做...

  1. 进口如下:

    import seaborn as sns
    import matplotlib.pyplot as plt
    import matplotlib as mpl
    import numpy as np
    from dautil import plotting
    
  2. 使用以下有助于绘制调色板的功能:

    def plot_palette(ax, plotter, pal, i, label, ncol=1):
        n = len(pal)
        x = np.linspace(0.0, 1.0, n)
        y = np.arange(n) + i * n
        ax.scatter(x, y, c=x, 
                    cmap=mpl.colors.ListedColormap(list(pal)), 
                    s=200)
        plotter.plot(x,y, label=label)
        handles, labels = ax.get_legend_handles_labels()
        ax.legend(loc='best', ncol=ncol, fontsize=18)
    
  3. 分类调色板对分类数据很有用,例如,性别或血型。以下函数绘制了一些 Seaborn 分类调色板:

    def plot_categorical_palettes(ax):
        palettes = ['deep', 'muted', 'pastel', 'bright', 'dark', 'colorblind']
        plotter = plotting.CyclePlotter(ax)
        ax.set_title('Categorical Palettes')
    
        for i, p in enumerate(palettes):
            pal = sns.color_palette(p)
         plot_palette(ax, plotter, pal, i, p, 4)
    
  4. 圆形颜色系统通常使用 HLS ( 色相明度饱和度)代替 RGB ( 红绿蓝)颜色空间。如果你有很多类别,它们是有用的。以下功能使用 HSL 系统绘制调色板:

    def plot_circular_palettes(ax):
        ax.set_title('Circular Palettes')
        plotter = plotting.CyclePlotter(ax)
    
        pal = sns.color_palette("hls", 6)
        plot_palette(ax, plotter, pal, 0, 'hls')
    
        sns.hls_palette(6, l=.3, s=.8)
        plot_palette(ax, plotter, pal, 1, 'hls l=.3 s=.8')
    
        pal = sns.color_palette("husl", 6)
        plot_palette(ax, plotter, pal, 2, 'husl')
    
        sns.husl_palette(6, l=.3, s=.8)
        plot_palette(ax, plotter, pal, 3, 'husl l=.3 s=.8')
    
  5. Seaborn 也有调色板,基于在线 ColorBrewer 工具(http://colorbrewer2.org/)。如下图所示:

    def plot_brewer_palettes(ax):
        ax.set_title('Brewer Palettes')
        plotter = plotting.CyclePlotter(ax)
    
        pal = sns.color_palette("Paired")
        plot_palette(ax, plotter, pal, 0, 'Paired')
    
        pal = sns.color_palette("Set2", 6)
        plot_palette(ax, plotter, pal, 1, 'Set2')
    
  6. 顺序调色板对于大范围的数据很有用,例如,数量级不同的数据。使用以下函数绘制它们:

    def plot_sequential_palettes(ax):
        ax.set_title('Sequential Palettes')
        plotter = plotting.CyclePlotter(ax)
    
        pal = sns.color_palette("Blues")
        plot_palette(ax, plotter, pal, 0, 'Blues')
    
        pal = sns.color_palette("BuGn_r")
        plot_palette(ax, plotter, pal, 1, 'BuGn_r')
    
        pal = sns.color_palette("GnBu_d")
        plot_palette(ax, plotter, pal, 2, 'GnBu_d')
    
        pal = sns.color_palette("cubehelix", 6)
        plot_palette(ax, plotter, pal, 3, 'cubehelix')
    
  7. 下面几行调用我们定义的函数:

    %matplotlib inline
    
    fig, axes = plt.subplots(2, 2, figsize=(16, 12))
    plot_categorical_palettes(axes[0][0])
    plot_circular_palettes(axes[0][1])
    plot_brewer_palettes(axes[1][0])
    plot_sequential_palettes(axes[1][1])
    plotting.hide_axes(axes)
    plt.tight_layout()
    

完整的代码可在本书的代码包中的choosing_palettes.ipynb文件中找到。最终结果见下图:

How to do it...

另见

选择 matplotlib 颜色图

matplotlib 彩色地图最近受到了很多批评,因为它们可能会误导人;然而,在我看来,大多数彩色地图都很好。默认情况是在 matplotlib 2.0 中得到一个改造,正如在http://matplotlib.org/style_changes.html宣布的那样(检索于 2015 年 7 月)。当然,有一些很好的论点不支持使用某些 matplotlib 颜色图,比如jet。在艺术中,就像在数据分析中一样,几乎没有什么是绝对真实的,所以我让你来决定。实际上,我认为重要的是考虑如何处理印刷出版物和各种类型的色盲。在这个食谱中,我设想了相对安全的带有彩条的彩色地图。这是 matplotlib 中许多颜色图的一小部分。

怎么做...

  1. 进口情况如下:

    import matplotlib.pyplot as plt
    import matplotlib as mpl
    from dautil import plotting
    
  2. 使用以下代码绘制数据集:

    fig, axes = plt.subplots(4, 4)
    cmaps = ['autumn', 'spring', 'summer', 'winter',
             'Reds', 'Blues', 'Greens', 'Purples',
             'Oranges', 'pink', 'Greys', 'gray',
             'binary', 'bone', 'hot', 'cool']
    
    for ax, cm in zip(axes.ravel(), cmaps):
        cmap = plt.cm.get_cmap(cm)
        cb = mpl.colorbar.ColorbarBase(ax, cmap=cmap, 
                                       orientation='horizontal')
        cb.set_label(cm)
        ax.xaxis.set_ticklabels([])
    
    plt.tight_layout()
    plt.show()
    

最终结果见下图:

How to do it...

笔记本在本书代码包的 choosing_colormaps.ipynb文件中。在这本书里,彩色地图被用在各种各样的可视化中。

另见

与 IPython 笔记本小部件交互

交互式 IPython 笔记本小部件在撰写本文时(2015 年 7 月),是一项实验性功能。我,以及据我所知的许多其他人,都希望这一特征能够保留下来。简而言之,小部件可以让你像选择 HTML 表单一样选择值。这包括滑块、下拉框和复选框。如您所见,这些小部件非常便于可视化我在第 1 章中介绍的天气数据,为可再现数据分析奠定基础。

怎么做...

  1. 导入以下内容:

    import seaborn as sns
    import numpy as np
    import pandas as pd
    import matplotlib.pyplot as plt
    from IPython.html.widgets import interact
    from dautil import data
    from dautil import ts
    
  2. 加载数据并请求内联图:

    %matplotlib inline
    df = data.Weather.load()
    
  3. 定义以下显示气泡图的功能:

    def plot_data(x='TEMP', y='RAIN', z='WIND_SPEED', f='A', size=10, cmap='Blues'):
        dfx = df[x].resample(f)
        dfy = df[y].resample(f)
        dfz = df[z].resample(f)
    
        bubbles = (dfz - dfz.min())/(dfz.max() - dfz.min())
        years = dfz.index.year
        sc = plt.scatter(dfx, dfy, s= size * bubbles + 9, c = years,
                    cmap=cmap, label=data.Weather.get_header(z), alpha=0.5)
        plt.colorbar(sc, label='Year')
    
        freqs = {'A': 'Annual', 'M': 'Monthly', 'D': 'Daily'}
        plt.title(freqs[f] + ' Averages')
        plt.xlabel(data.Weather.get_header(x))
        plt.ylabel(data.Weather.get_header(y))
        plt.legend(loc='best')
    
  4. 用下面的代码调用我们刚刚定义的函数:

    vars = df.columns.tolist()
    freqs = ('A', 'M', 'D')
    cmaps = [cmap for cmap in plt.cm.datad if not cmap.endswith("_r")]
    cmaps.sort()
    interact(plot_data, x=vars, y=vars, z=vars, f=freqs, size=(100,700), cmap=cmaps)
    
  5. This is one of the recipes where you really should play with the code to understand how it works. The following is an example bubble plot:

    How to do it...

  6. 定义另一个函数(其实是同名的),但这次函数是按年或月的天数分组数据:

    def plot_data(x='TEMP', y='RAIN', z='WIND_SPEED', groupby='ts.groupby_yday', size=10, cmap='Blues'):
        if groupby == 'ts.groupby_yday':
            groupby = ts.groupby_yday
        elif groupby == 'ts.groupby_month':
            groupby = ts.groupby_month
        else:
            raise AssertionError('Unknown groupby ' + groupby)
    
        dfx = groupby(df[x]).mean()
        dfy = groupby(df[y]).mean()
        dfz = groupby(df[z]).mean()
    
        bubbles = (dfz - dfz.min())/(dfz.max() - dfz.min())
        colors = dfx.index.values
        sc = plt.scatter(dfx, dfy, s= size * bubbles + 9, c = colors,
                    cmap=cmap, label=data.Weather.get_header(z), alpha=0.5)
        plt.colorbar(sc, label='Day of Year')
    
        by_dict = {ts.groupby_yday: 'Day of Year', ts.groupby_month: 'Month'}
        plt.title('Grouped by ' + by_dict[groupby])
        plt.xlabel(data.Weather.get_header(x))
        plt.ylabel(data.Weather.get_header(y))
        plt.legend(loc='best')
    
  7. 用下面的代码片段调用这个函数【T1:

    groupbys = ('ts.groupby_yday', 'ts.groupby_month')
    interact(plot_data, x=vars, y=vars, z=vars, groupby=groupbys, size=(100,700), cmap=cmaps)
    

最终结果见下图:

How to do it...

我对这个图的第一印象是温度和风速似乎是相关的。源代码在本书的代码包中的Interactive.ipynb文件中。

另见

查看散点图矩阵

如果你的数据集中没有很多变量,那么查看你的数据所有可能的散点图是一个好主意。你可以通过一个函数调用来完成这个任务。这些函数显示一个矩阵图,对角线上有核密度估计图或直方图。

怎么做...

  1. 导入以下内容:

    import pandas as pd
    from dautil import data
    from dautil import ts
    import matplotlib.pyplot as plt
    import seaborn as sns
    import matplotlib as mpl
    
  2. 用以下行加载天气数据:

    df = data.Weather.load()
    df = ts.groupby_yday(df).mean()
    df.columns = [data.Weather.get_header(c) for c in df.columns]
    
  3. Plot with the Seaborn pairplot() function, which plots histograms on the diagonal by default:

    %matplotlib inline
    
    # Seaborn plotting, issues due to NaNs
    sns.pairplot(df.fillna(0))
    

    结果如下:

    How to do it...

  4. 使用 Pandasscatter_matrix()功能绘制类似的图,并在对角线上请求内核密度估计图:

    sns.set({'figure.figsize': '16, 12'})
    mpl.rcParams['axes.linewidth'] = 9
    mpl.rcParams['lines.linewidth'] = 2
    plots = pd.scatter_matrix(df, marker='o', diagonal='kde')
    plt.show()
    

有关最终结果,请参考以下图表:

How to do it...

完整的代码可在本书的代码包中的scatter_matrix.ipynb文件中找到。

通过 mpld3 用 d3.js 可视化

D3.js 是 2011 年发布的 JavaScript 数据可视化库,我们也可以在 IPython 笔记本中使用。我们将在常规 matplotlib 图中添加悬停工具提示。作为桥梁,我们需要mpld3套餐。这个食谱不需要任何 JavaScript 编码。

做好准备

我使用以下命令安装了 mpld3 0.2:

$ [sudo] pip install mpld3

怎么做...

  1. 从导入开始,启用 mpld3:

    %matplotlib inline
    import matplotlib.pyplot as plt
    import mpld3
    mpld3.enable_notebook()
    from mpld3 import plugins
    import seaborn as sns
    from dautil import data
    from dautil import ts
    
  2. 加载天气数据并将其绘制为如下:

    df = data.Weather.load()
    df = df[['TEMP', 'WIND_SPEED']]
    df = ts.groupby_yday(df).mean()
    
    fig, ax = plt.subplots()
    ax.set_title('Averages Grouped by Day of Year')
    points = ax.scatter(df['TEMP'], df['WIND_SPEED'],
                        s=30, alpha=0.3)
    ax.set_xlabel(data.Weather.get_header('TEMP'))
    ax.set_ylabel(data.Weather.get_header('WIND_SPEED'))
    labels = ["Day of year {0}".format(i) for i in range(366)]
    tooltip = plugins.PointLabelTooltip(points, labels)
    
    plugins.connect(fig, tooltip)
    
    

高亮显示的线负责工具提示。在下面的截图中,31 年文本来自工具提示:

How to do it...

如您所见,在图的底部,您还有用于平移和缩放的小部件(参考本书代码包中的mpld3_demo.ipynb文件)。

创建热图

热图使用一组颜色将矩阵中的数据可视化。最初,热图用于表示金融资产的价格,如股票。Bokeh 是一个 Python 包,可以在 IPython 笔记本中显示热图,或者生成独立的 HTML 文件。

做好准备

我通过 Anaconda 获得了 Bokeh 0.9.1。博克安装说明可在http://bokeh.pydata.org/en/latest/docs/installation.html获得(2015 年 7 月检索)。

怎么做...

  1. 进口如下:

    from collections import OrderedDict
    from dautil import data
    from dautil import ts
    from dautil import plotting
    import numpy as np
    import bokeh.plotting as bkh_plt
    from bokeh.models import HoverTool
    
  2. 以下函数加载温度数据,并按年、月分组:

    def load():
        df = data.Weather.load()['TEMP']
        return ts.groupby_year_month(df)
    
  3. 定义一个在特殊 Bokeh 结构中重新排列数据的函数:

    def create_source():
        colors = plotting.sample_hex_cmap()
    
        month = []
        year = []
        color = []
        avg = []
    
        for year_month, group in load():
            month.append(ts.short_month(year_month[1]))
            year.append(str(year_month[0]))
            monthly_avg = np.nanmean(group.values)
            avg.append(monthly_avg)
            color.append(colors[min(int(abs(monthly_avg)) - 2, 8)])
    
        source = bkh_plt.ColumnDataSource(
            data=dict(month=month, year=year, color=color, avg=avg)
        )
    
        return year, source
    
  4. 定义一个返回横轴标签的函数:

    def all_years():
        years = set(year)
        start_year = min(years)
        end_year = max(years)
    
        return [str(y) for y in range(int(start_year), int(end_year), 5)]
    
  5. 为热图定义绘图功能,该功能还设置悬停工具提示:

    def plot(year, source):
        fig = bkh_plt.figure(title="De Bilt, NL Temperature (1901 - 2014)",
                             x_range=all_years(), 
                             y_range=list(reversed(ts.short_months())),
                             toolbar_location="left", 
                             tools="resize,hover,save,pan,box_zoom,wheel_zoom")
    
        fig.rect("year", "month", 1, 1, source=source,
            color="color", line_color=None)
    
        fig.xaxis.major_label_orientation = np.pi/3
    
        hover = fig.select(dict(type=HoverTool))
        hover.tooltips = OrderedDict([
            ('date', '@month @year'),
            ('avg', '@avg'),
        ])
    
        bkh_plt.output_notebook()
        bkh_plt.show(fig)
    
  6. 调用你定义的函数:

    year, source = create_source()
    plot(year, source)
    

最终结果见下图:

How to do it...

源代码可以在本书的代码包中的heat_map.ipynb文件中找到。

另见

盒子图、核密度图与小提琴图相结合

Violin 图将 box 图和核密度图或直方图组合成一种类型的图。Seaborn 和 matplotlib 都提供小提琴情节。我们将在这个食谱中使用 Seaborn 对的天气数据进行 z 评分。z 评分并不重要,但没有它,小提琴会更分散。

怎么做...

  1. 导入所需的库,如下所示:

    import seaborn as sns
    from dautil import data
    import matplotlib.pyplot as plt
    
  2. 加载天气数据,计算 z 分:

    df = data.Weather.load()
    zscores = (df - df.mean())/df.std()
    
  3. Plot a violin plot of the z-scores:

    %matplotlib inline
    plt.figure()
    plt.title('Weather Violin Plot')
    sns.violinplot(zscores.resample('M'))
    plt.ylabel('Z-scores')
    

    第一段小提琴剧情参考以下剧情:

    How to do it...

  4. 绘制阴雨天和干燥天(与雨天相反)与风速的小提琴图:

    plt.figure()
    plt.title('Rainy Weather vs Wind Speed')
    categorical = df
    categorical['RAIN'] = categorical['RAIN'] > 0
    ax = sns.violinplot(x="RAIN", y="WIND_SPEED", 
     data=categorical)
    
    

第二段小提琴剧情参考以下剧情:

How to do it...

源代码可以在本书代码包的violins.ipynb文件中找到。

另见

用蜂巢图可视化网络图

一个蜂巢图是一种可视化技术,用于绘制网络图。在蜂房图中,我们把边画成曲线。我们通过一些属性将节点分组,并在径向轴上显示它们。NetworkX 是最著名的 Python 网络图库之一;然而,它还不支持蜂房地块(2015 年 7 月)。幸运的是,有几个专门研究蜂房地块的图书馆。此外,我们将使用一个应用编程接口来划分在https://snap.stanford.edu/data/egonets-Facebook.html可用的脸书用户的图表(2015 年 7 月检索)。数据属于 斯坦福网络分析项目 ( SNAP ),其中也有 Python API。可惜的是 SNAP API 还不支持 Python 3。

做好准备

我通过 Anaconda 获得了 NetworkX 1.9.1。安装网络的说明在(2015 年 7 月检索)。我们还需要 https://bitbucket.org/taynaud/python-louvaincommunity套餐(2015 年 7 月取回)。PyPi 上还有另外一个同名的包,完全不相关。安装在https://github.com/ericmjl/hiveplot托管的 hiveplot包(2015 年 7 月检索到):

$ [sudo] pip install hiveplot

我用 hiveplot 0.1.7.4 编写了代码。

怎么做...

  1. 进口为如下:

    import networkx as nx
    import community
    import matplotlib.pyplot as plt
    from hiveplot import HivePlot
    from collections import defaultdict
    from dautil import plotting
    from dautil import data
    
  2. 加载数据并创建一个网络对象:

    fb_file = data.SPANFB().load()
    G = nx.read_edgelist(fb_file, 
                         create_using = nx.Graph(), 
                         nodetype = int)
    print(nx.info(G))
    
  3. 分割图形并创建nodes字典,如下所示:

    parts = community.best_partition(G)
    nodes = defaultdict(list)
    
    for n, d in parts.items():
        nodes[d].append(n)
    
  4. 这个图相当大,所以我们将只创建三组边:

    edges = defaultdict(list)
    
    for u, v in nx.edges(G, nodes[0]):
        edges[0].append((u, v, 0))
    
    for u, v in nx.edges(G, nodes[1]):
        edges[1].append((u, v, 1))
    
    for u, v in nx.edges(G, nodes[2]):
        edges[2].append((u, v, 2))
    
  5. 标绘大约需要六分钟:

    %matplotlib inline
    cmap = plotting.sample_hex_cmap(name='hot', ncolors=len(nodes.keys()))
    h = HivePlot(nodes, edges, cmap, cmap)
    h.draw()
    plt.title('Facebook Network Hive Plot')
    

等待期结束后,我们得到如下图:

How to do it...

代码在本书代码包的hive_plot.ipynb文件中。

显示地理地图

无论是处理全球数据的局部,地理地图都是一种合适的可视化。要在地图上绘制数据,我们需要坐标,通常以纬度和经度值的形式。有几种文件格式,我们可以用它们来保存地理数据。在本食谱中,我们将使用特殊的形状文件格式和更常见的标签分隔值 ( TSV )格式。shapefile 格式由 Esri 公司创建,使用三个扩展名为.shp.shx.dbf的强制文件。.dbf文件包含一个数据库,其中包含 shapefile 中每个地理位置的额外信息。我们将使用的 shapefile 包含关于国家边界、人口和 国内生产总值 ( 国内生产总值)的信息。我们可以从cartopy图书馆下载形状文件。TSV 文件以时间序列的形式保存了 4000 多个城市的人口数据。它来自https://nord pil . com/resources/world-database-of-city-of-city/(2015 年 7 月检索)。

做好准备

首先,我们需要从源代码安装 Proj.4,或者,如果你幸运的话,使用来自https://github.com/OSGeo/proj.4/wiki的二进制发行版(检索于 2015 年 7 月)。安装项目 4 的说明可在https://github.com/OSGeo/proj.4获得(2015 年 7 月检索)。然后,用 pip 安装cartopy——我用 cartopy-0.13.0 写的代码。或者,我们可以运行以下命令:

$ conda install -c scitools cartopy

怎么做...

  1. 进口为如下:

    import cartopy.crs as ccrs
    import matplotlib.pyplot as plt
    import cartopy.io.shapereader as shpreader
    import matplotlib as mpl
    import pandas as pd
    from dautil import options
    from dautil import data
    
  2. 我们将使用颜色来可视化乡村人口和人口众多的城市。加载数据如下:

    countries = shpreader.natural_earth(resolution='110m',
                                        category='cultural',
                                        name='admin_0_countries')
    
    cities = pd.read_csv(data.Nordpil().load_urban_tsv(),
                         sep='\t', encoding='ISO-8859-1')
    mill_cities = cities[cities['pop2005'] > 1000]
    
  3. 画一张地图,一个相应的颜色条,用下面的代码在地图上标出人口众多的城市:

    %matplotlib inline
    plt.figure(figsize=(16, 12))
    gs = mpl.gridspec.GridSpec(2, 1,
                               height_ratios=[20, 1])
    ax = plt.subplot(gs[0], projection=ccrs.PlateCarree())
    
    norm = mpl.colors.Normalize(vmin=0, vmax=2 * 10 ** 9)
    cmap = plt.cm.Blues
    ax.set_title('Population Estimates by Country')
    
    for country in shpreader.Reader(countries).records():
        ax.add_geometries(country.geometry, ccrs.PlateCarree(),
                          facecolor=cmap(
                              norm(country.attributes['pop_est'])))
    
    plt.plot(mill_cities['Longitude'],
             mill_cities['Latitude'], 'r.',
             label='Populous city',
             transform=ccrs.PlateCarree())
    
    options.set_mpl_options()
    plt.legend(loc='lower left')
    
    cax = plt.subplot(gs[1])
    cb = mpl.colorbar.ColorbarBase(cax,
                                   cmap=cmap,
                                   norm=norm,
                                   orientation='horizontal')
    
    cb.set_label('Population Estimate')
    plt.tight_layout()
    

最终结果参见以下图:

How to do it...

你可以在本书的代码包plot_map.ipynb文件中找到代码。

使用类似 ggplot2 的图

Ggplot2 是一个在 R 用户中流行的数据可视化的 R 库。ggplot2 的主要思想是数据可视化的产品由许多层组成。就像画家一样,我们从一张空画布开始,然后逐渐添加颜料层。通常情况下,我们使用来自 Python 的 R 代码与rpy2进行接口(我将在我的书 Python 数据分析的第 11 章中讨论几个互操作性选项)。但是,如果我们只想使用ggplot2,那么使用pyggplot图书馆更方便。在本食谱中,我们将使用可通过pandas检索的世界银行数据来可视化三个国家的人口增长。数据由各种指标和相关元数据组成。http://api.worldbank.org/v2/en/topic/19?的电子表格 download format = excel(2015 年 7 月检索)有指标说明。我认为我们可以认为世界银行数据集是静态的;然而,相似的数据集经常变化,足以让分析师忙得不可开交。显然,更改指示器的名称(可能)会破坏代码,所以我决定通过joblib库缓存数据。joblib库与 scikit-learn 相关,我们将在第 9 章集成学习和降维中详细讨论。不幸的是,这种方法有一些局限性;特别是,我们无法腌制所有 Python 对象。

做好准备

首先,您需要安装了 ggplot2 的 R。如果你不打算认真使用 ggplot2,也许你应该完全跳过这个食谱。R 的主页是http://www.r-project.org/(2015 年 7 月检索)。ggplot2 的文件位于http://docs.ggplot2.org/current/index.html(2015 年 7 月检索)。你可以用 pip 安装 pyg plot——我用的是 pyg plot-23。要安装joblib,请访问https://pythonhosted.org/joblib/installing.html(2015 年 7 月检索)。通过 Python 我有joblib 0.8.4。

怎么做...

  1. 进口情况如下:

    import pyggplot
    from dautil import data
    
  2. 用以下代码加载数据:

    dawb = data.Worldbank()
    pop_grow = dawb.get_name('pop_grow')
    df = dawb.download(indicator=pop_grow, start=1984, end=2014)
    df = dawb.rename_columns(df, use_longnames=True)
    
  3. 下面一行用我们创建的 PandasDataFrame对象初始化 pyggplot:

    p = pyggplot.Plot(df)
    
  4. 用以下线条添加条形图:

    p.add_bar('country', dawb.get_longname(pop_grow), color='year')
    
  5. 翻转图表,使条形图指向右侧并渲染:

    p.coord_flip()
    p.render_notebook()
    

最终结果见下图:

How to do it...

代码在本书代码包的 using_ggplot.ipynb文件中。

用影响图突出显示数据点

影响图考虑了拟合、影响和杠杆作用后的残差,类似于气泡图。残差的大小绘制在垂直轴上,可以指示数据点是异常值。要理解影响图,请看下面的等式:

Highlighting data points with influence plots

根据statsmodels文件,残差用标准偏差 (2.1) 进行换算。在 (2.2) 中, n 为观察数, p 为回归数。我们有一个所谓的 帽子矩阵,由 (2.3) 给出。

帽子矩阵的对角线元素给出了特殊的度量,称为杠杆。杠杆以为横轴,表示影响地块的潜在影响。在影响图中,影响决定了绘制点的大小。影响点往往有很高的残差和杠杆。要测量影响,statsmodels可以使用库克距离 (2.4)DFFITS (2.5)

怎么做...

  1. 进口情况如下:

    import matplotlib.pyplot as plt
    import statsmodels.api as sm
    from statsmodels.formula.api import ols
    from dautil import data
    
  2. 获取可用的国家代码:

    dawb = data.Worldbank()
    
    countries = dawb.get_countries()[['name', 'iso2c']]
    
  3. 从世界银行加载数据:

    population = dawb.download(indicator=[dawb.get_name('pop_grow'), dawb.get_name('gdp_pcap'),
                                        dawb.get_name('primary_education')],
                             country=countries['iso2c'], start=2014, end=2014)
    
    population = dawb.rename_columns(population)
    
  4. 定义一个普通的最小二乘模型,如下:

    population_model = ols("pop_grow ~ gdp_pcap + primary_education",
                           data=population).fit()
    
  5. 使用库克距离

    %matplotlib inline
    fig, ax = plt.subplots(figsize=(19.2, 14.4))
    fig = sm.graphics.influence_plot(population_model, ax=ax, criterion="cooks")
    plt.grid()
    

    显示模型的影响图

最终结果见下图:

How to do it...

代码在本书代码包的 highlighting_influence.ipynb文件中。

另见

三、统计数据分析与概率

我们将在本章介绍以下食谱:

  • 将数据拟合到指数分布
  • 将聚合数据拟合到伽马分布
  • 将聚合计数拟合到泊松分布
  • 确定偏差
  • 估计核密度
  • 确定均值、方差和标准差的置信区间
  • 概率加权抽样
  • 探索极端价值观
  • 用皮尔逊相关系数关联变量
  • spearman 秩相关的相关变量
  • 将二元和连续变量与点双序列相关联系起来
  • 用方差分析评估变量之间的关系

简介

已经发明了各种统计分布,这相当于数据分析师的轮子。正如无论我想到什么都会以不同的方式打印出来一样,我们世界中的数据并不遵循严格的数学定律。然而,在可视化我们的数据之后,我们可以看到数据遵循(在一定程度上)一个分布。即使没有可视化,我们也可以使用经验法则找到候选分布。下一步是尝试将数据调整到已知的分布。如果数据非常复杂,可能是由于大量的变量,估计它的核密度是有用的(对于一个变量也是有用的)。在所有情况下,估计我们结果的置信区间或 p 值是很好的。当我们至少有两个变量时,有时看一看变量之间的相关性是合适的。在本章中,我们将应用三种类型的相关性。

将数据拟合到指数分布

指数分布伽玛分布的一个特例,我们也会在这一章遇到。指数分布可用于分析降雨极值。它也可以用来模拟为排队的顾客提供服务所需的时间。对于零值和负值,指数分布的概率分布函数 ( PDF )为零。对于正值,PDF 指数衰减:

Fitting data to the exponential distribution

我们将使用降雨数据作为例子,这是指数分布拟合的一个很好的候选。显然,降雨量不能是负数,我们知道大雨比完全不下雨的可能性小。事实上,一天不下雨是很有可能的。

怎么做...

以下步骤使雨量数据符合指数分布:

  1. 进口情况如下:

    from scipy.stats.distributions import expon
    import matplotlib.pyplot as plt
    import dautil as dl
    from IPython.display import HTML
    
  2. 我做了一个包装类,调用scipy.stats.expon方法。首先,调用fit()方法:

    rain = dl.data.Weather.load()['RAIN'].dropna()
    dist = dl.stats.Distribution(rain, expon)
    dl.options.set_pd_options()
    html_builder = dl.report.HTMLBuilder()
    html_builder.h1('Fitting Data to the Exponential Distribution')
    loc, scale = dist.fit()
    table = dl.report.DFBuilder(['loc', 'scale'])
    table.row([loc, scale])
    html_builder.h2('Distribution Parameters')
    html_builder.add_df(table.build())
    
  3. 下面的代码在拟合残差上调用scipy.stats.expon.pdf()方法和scipy.stats.describe()函数:

    pdf = dist.pdf(loc, scale)
    html_builder.h2('Residuals of the Fit')
    residuals = dist.describe_residuals()
    html_builder.add(residuals.to_html())
    
  4. 为了评估拟合,我们可以使用度量。使用以下代码片段计算适合度指标:

    table2 = dl.report.DFBuilder(['Mean_AD', 'RMSE'])
    table2.row([dist.mean_ad(), dist.rmse()])
    html_builder.h2('Fit Metrics')
    html_builder.add_df(table2.build())
    
  5. 绘制拟合图并显示分析报告,如下所示:

    plt.hist(rain, bins=dist.nbins, normed=True, label='Rain')
    plt.plot(dist.x, pdf, label='PDF')
    plt.title('Fitting to the exponential distribution')
    
    # Limiting the x-asis for a better plot
    plt.xlim([0, 15])
    plt.xlabel(dl.data.Weather.get_header('RAIN'))
    plt.ylabel('Probability')
    plt.legend(loc='best')
    HTML(html_builder.html)
    

最终结果见下图截图(代码在本书代码包的fitting_expon.ipynb文件中):

How to do it...

它是如何工作的…

scipy.stats.expon.fit()返回的scale参数是 (3.1) 的衰变参数的倒数。我们得到大约 2 的scale值,所以衰减大约是一半。因此不下雨的概率约为一半。拟合残差的平均值和偏斜度应该接近于 0。如果我们有一个非零的偏斜,一定会有奇怪的事情发生,因为我们不希望残差向任何方向偏斜。 平均绝对偏差 ( MAD )和均方根误差 ( RMSE )是回归度量,我们将在第 10 章评估分类器、回归器和聚类中详细介绍。

另见

将聚集数据拟合到伽马分布

伽马分布可以用来模拟保险索赔的规模、降雨量和大脑中尖峰间隔的分布。伽马分布的 PDF 由形状 k 和比例 θ 定义如下:

Fitting aggregated data to the gamma distribution

还有一个定义使用了反比例参数(由 SciPy 使用)。伽马分布的平均值和方差由(3.3)和(3.4)描述。如您所见,我们可以使用简单的代数从均值和方差估计形状参数。

怎么做...

让我们将 1 月份雨水数据的总量与伽马分布进行拟合:

  1. 从以下进口开始:

    from scipy.stats.distributions import gamma
    import matplotlib.pyplot as plt
    import dautil as dl
    import pandas as pd
    from IPython.display import HTML
    
  2. 加载数据并选择 1 月份的聚合:

    rain = dl.data.Weather.load()['RAIN'].resample('M').dropna()
    rain = dl.ts.groupby_month(rain)
    rain = rain.get_group(1)
    
  3. 从分布的均值和方差导出 k 的值,并使用它拟合数据:

    dist = dl.stats.Distribution(rain, gamma)
    
    a = (dist.mean() ** 2)/dist.var()
    shape, loc, scale = dist.fit(a)
    

代码的其余部分类似于中的代码,将数据拟合到指数分布。最终结果参见下面的截图(代码在本书代码包的fitting_gamma.ipynb文件中):

How to do it...

另见

将聚集计数拟合到泊松分布

泊松分布是以法国数学家泊松命名的,他在 1837 年发表了一篇关于它的论文。泊松分布是一种离散分布,通常与固定时间或空间间隔的计数相关联。它只为整数值 k 定义。例如,我们可以将其应用于每月的雨天数。在这种情况下,我们隐含地假设雨天的事件以固定的月速率发生。将数据拟合到泊松分布的目标是找到固定利率。

以下等式描述了泊松分布的概率质量函数 (3.5) 和速率参数 (3.6) :

Fitting aggregated counts to the Poisson distribution

怎么做...

以下步骤使用最大似然估计 ( 最大似然估计)方法拟合:

  1. 进口情况如下:

    from scipy.stats.distributions import poisson
    import matplotlib.pyplot as plt
    import dautil as dl
    from scipy.optimize import minimize
    from IPython.html.widgets.interaction import interactive
    from IPython.core.display import display
    from IPython.core.display import HTML
    
  2. 定义功能最大化:

    def log_likelihood(k, mu):
        return poisson.logpmf(k, mu).sum()
    
  3. 加载数据并按月分组:

    def count_rain_days(month):
        rain = dl.data.Weather.load()['RAIN']
        rain = (rain > 0).resample('M', how='sum')
        rain = dl.ts.groupby_month(rain)
        rain = rain.get_group(month)
    
        return rain
    
  4. 定义如下可视化功能:

    def plot(rain, dist, params, month):
        fig, ax = plt.subplots()
        plt.title('Fitting to the Poisson distribution ({})'.format(dl.ts.short_month(month)))
    
        # Limiting the x-asis for a better plot
        plt.xlim([0, 15])
        plt.figtext(0.5, 0.7, 'rate {:.3f}'.format(params.x[0]), alpha=0.7,
                    fontsize=14)
        plt.xlabel('# Rainy days in a month')
        plt.ylabel('Probability')
        ax.hist(dist.train, bins=dist.nbins, normed=True, label='Data')
        ax.plot(dist.x, poisson.pmf(dist.x, params.x))
    
  5. 定义一个函数作为入口点:

    def fit_poisson(month):
        month_index = dl.ts.month_index(month)
        rain = count_rain_days(month_index)
    
        dist = dl.stats.Distribution(rain, poisson, range=[-0.5, 19.5])
        params = minimize(log_likelihood, x0=rain.mean(), args=(rain,))
        plot(rain, dist, params, month_index)
    
  6. 使用交互式小部件,这样我们可以显示每个月的图表:

    display(interactive(fit_poisson, month=dl.nb.create_month_widget(month='May')))
    HTML(dl.report.HTMLBuilder().watermark())
    

最终结果参见下面的截图(参见本书代码包中的fitting_poisson.ipynb文件):

How to do it...

另见

确定偏差

在教授概率时,习惯给出抛硬币的例子。天要不要下雨或多或少有点像抛硬币。如果我们有两种可能的结果,T2 二项分布是合适的。这种分布需要两个参数:概率和样本量。

在统计学中,有两种普遍接受的方法。在常客方法中,我们测量投掷硬币的次数,并使用该频率进行进一步分析。贝叶斯分析以其创始人托马斯·贝叶斯牧师的名字命名。贝叶斯方法是更多的增量,需要优先分布,这是我们在进行实验之前假设的分布。 后验分布是我们感兴趣的分布,是我们从实验中获得新数据后得到的分布。让我们先来看看下面的等式:

Determining bias

(3.7)****(3.8)描述了二项式分布的概率质量函数。 (3.9) 出自贝叶斯发表的一篇文章。该方程是关于 m 成功和 n 失败的实验,并假设二项式分布的概率参数具有均匀的先验分布。

怎么做...

在这个食谱中,我们将运用频繁者和贝叶斯方法来获取降雨数据:

  1. 进口情况如下:

    import dautil as dl
    from scipy import stats
    import matplotlib.pyplot as plt
    import numpy as np
    from IPython.html.widgets.interaction import interact
    from IPython.display import HTML
    
  2. 定义以下函数加载数据:

    def load():
        rainy = dl.data.Weather.rain_values() > 0
        n = len(rainy)
        nrains = np.cumsum(rainy)
    
        return n, nrains
    
  3. 定义以下函数计算后验值:

    def posterior(i, u, data):
        return stats.binom(i, u).pmf(data[i])
    
  4. 定义以下函数来绘制数据子集的后验值:

    def plot_posterior(ax, day, u, nrains):
        ax.set_title('Posterior distribution for day {}'.format(day))
        ax.plot(posterior(day, u, nrains),
                label='rainy days in period={}'.format(nrains[day]))
        ax.set_xlabel('Uniform prior parameter')
        ax.set_ylabel('Probability rain')
        ax.legend(loc='best')
    
  5. 定义以下功能进行绘图:

    def plot(day1=1, day2=30):
        fig, [[upleft, upright], [downleft, downright]] = plt.subplots(2, 2)
        plt.suptitle('Determining bias of rain data')
        x = np.arange(n) + 1
        upleft.set_title('Frequentist Approach')
        upleft.plot(x, nrains/x, label='Probability rain')
        upleft.set_xlabel('Days')
        set_ylabel(upleft)
    
        max_p = np.zeros(n)
        u = np.linspace(0, 1, 100)
    
        for i in x - 1:
            max_p[i] = posterior(i, u, nrains).argmax()/100
    
        downleft.set_title('Bayesian Approach')
        downleft.plot(x, max_p)
        downleft.set_xlabel('Days')
        set_ylabel(downleft)
    
        plot_posterior(upright, day1, u, nrains)
        plot_posterior(downright, day2, u, nrains)
        plt.tight_layout()
    
  6. 以下几行调用其他函数并放置水印:

    interact(plot, day1=(1, n), day2=(1, n))
    HTML(dl.report.HTMLBuilder().watermark())
    

最终结果参见下面的截图(参见本书代码包中的determining_bias.ipynb文件):

How to do it...

另见

  • 关于本食谱中提到的论文的维基百科页面在

估计核密度

通常,我们对适合我们数据的分布类型有一个概念。如果不是这样,我们可以应用一个叫做核密度估计的过程。这个方法不做任何假设并且是非参数的。我们基本上平滑了数据,试图处理概率密度。为了平滑数据,我们可以使用各种函数。在这种情况下,这些函数被称为内核函数。以下等式定义了估计量:

Estimating kernel density

在前面的公式中, K 是核函数,一个属性类似于 PDF 的函数。带宽 h 参数控制平滑过程,可以保持固定或变化。一些库使用经验法则来计算 h ,而另一些库让你指定它的值。SciPy、statsmodels、scikit-learn 和 Seaborn 使用不同的算法实现内核密度估计。

怎么做...

在本食谱中,我们将使用天气数据估计二元内核密度:

  1. 进口情况如下:

    import seaborn as sns
    import matplotlib.pyplot as plt
    import dautil as dl
    from dautil.stats import zscores
    import statsmodels.api as sm
    from sklearn.neighbors import KernelDensity
    import numpy as np
    from scipy import stats
    from IPython.html import widgets
    from IPython.core.display import display
    from IPython.display import HTML
    
  2. 定义以下函数来绘制估计的内核密度:

    def plot(ax, a, b, c, xlabel, ylabel):
        dl.plotting.scatter_with_bar(ax, 'Kernel Density', a.values, b.values, c=c, cmap='Blues')
        ax.set_xlabel(xlabel)
        ax.set_ylabel(ylabel)
    
  3. 在下面的笔记本单元格中,加载数据并定义用于选择天气变量的小部件:

    df = dl.data.Weather.load().resample('M').dropna()
    columns = [str(c) for c in df.columns.values]
    var1 = widgets.Dropdown(options=columns, selected_label='RAIN')
    display(var1)
    var2 = widgets.Dropdown(options=columns, selected_label='TEMP')
    display(var2)
    
  4. 在下一个笔记本单元格中,使用我们创建的小部件的值定义变量:

    x = df[var1.value]
    xlabel = dl.data.Weather.get_header(var1.value)
    y = df[var2.value]
    ylabel = dl.data.Weather.get_header(var2.value)
    X = [x, y]
    
  5. 下一个笔记本电脑单元执行繁重的工作,突出显示最重要的行:

    # need to use zscores to avoid errors
    Z = [zscores(x), zscores(y)]
    kde = stats.gaussian_kde(Z)
    
    _, [[sp_ax, sm_ax], [sk_ax, sns_ax]] = plt.subplots(2, 2)
    plot(sp_ax, x, y, kde.pdf(Z), xlabel, ylabel)
    sp_ax.set_title('SciPy')
    
    sm_kde = sm.nonparametric.KDEMultivariate(data=X, var_type='cc',
                                              bw='normal_reference')
    sm_ax.set_title('statsmodels')
    plot(sm_ax, x, y, sm_kde.pdf(X), xlabel, ylabel)
    
    XT = np.array(X).T
    sk_kde = KernelDensity(kernel='gaussian', bandwidth=0.2).fit(XT)
    sk_ax.set_title('Scikit Learn')
    plot(sk_ax, x, y, sk_kde.score_samples(XT), xlabel, ylabel)
    
    sns_ax.set_title('Seaborn')
    sns.kdeplot(x, y, ax=sns_ax)
    sns.rugplot(x, color="b", ax=sns_ax)
    sns.rugplot(y, vertical=True, ax=sns_ax)
    sns_ax.set_xlabel(xlabel)
    sns_ax.set_ylabel(ylabel)
    
    plt.tight_layout()
    HTML(dl.report.HTMLBuilder().watermark())
    

最终结果参见以下截图(参见本书代码包中的kernel_density_estimation.ipynb文件):

How to do it...

另见

确定平均值、方差和标准偏差的置信区间

想象我们观察到的数据只是冰山一角有时是有用的。如果你进入这种心态,那么你可能会想知道这座冰山到底有多大。显然,如果你看不到整件事,你还是可以试着从你所拥有的数据中进行外推。在统计学中,我们试图估计置信区间,置信区间是一个估计范围,通常与百分比引用的某个置信水平相关。

scipy.stats.bayes_mvs()函数估计平均值、方差和标准偏差的置信区间。该函数使用贝叶斯统计估计置信度,假设数据是独立的和正态分布的。折刀是估计置信区间的替代确定性算法。它属于重采样算法家族。通常,我们通过删除一个值(我们也可以删除两个或更多的值)在折刀算法下生成新的数据集。我们生成数据 N 次,其中 N 是数据集中的数值个数。通常,如果我们想要 5%的置信水平,我们估计新数据集的平均值或方差,并确定 2.5 和 97.5 百分位值。

怎么做...

在这个配方中,我们使用scipy.stats.bayes_mvs()函数和折刀来估计置信区间:

  1. 进口情况如下:

    from scipy import stats
    import dautil as dl
    from dautil.stats import jackknife
    import pandas as pd
    import matplotlib.pyplot as plt
    import numpy as np
    from IPython.html.widgets.interaction import interact
    from IPython.display import HTML
    
  2. 定义以下功能,使用误差线可视化 Scipy 结果:

    def plot_bayes(ax, metric, var, df):
        vals = np.array([[v.statistic, v.minmax[0], v.minmax[1]] for v in
                         df[metric].values])
    
        ax.set_title('Bayes {}'.format(metric))
        ax.errorbar(np.arange(len(vals)), vals.T[0], yerr=(vals.T[1], vals.T[2]))
        set_labels(ax, var)
    
  3. 定义功能后的使用误差线可视化折刀结果:

    def plot_jackknife(ax, metric, func, var, df):
        vals = df.apply(lambda x: jackknife(x, func, alpha=0.95))
        vals = np.array([[v[0], v[1], v[2]] for v in vals.values])
    
        ax.set_title('Jackknife {}'.format(metric))
        ax.errorbar(np.arange(len(vals)), vals.T[0], yerr=(vals.T[1], vals.T[2]))
        set_labels(ax, var)
    
  4. 定义以下函数,该函数将在 IPython 交互小部件的帮助下调用:

    def confidence_interval(var='TEMP'):
        df = dl.data.Weather.load().dropna()
        df = dl.ts.groupby_yday(df)
    
        def f(x):
            return stats.bayes_mvs(x, alpha=0.95)
    
        bayes_df = pd.DataFrame([[v[0], v[1], v[2]] for v in
                                 df[var].apply(f).values], columns=['Mean', 'Var',
                                                                    'Std'])
    
        fig, axes = plt.subplots(2, 2)
        fig.suptitle('Confidence Intervals')
    
        plot_bayes(axes[0][0], 'Mean', var, bayes_df)
        plot_bayes(axes[0][1], 'Var', var, bayes_df)
        plot_jackknife(axes[1][0], 'Mean', np.mean, var, df[var])
        plot_jackknife(axes[1][1], 'Mean', np.var, var, df[var])
    
        plt.tight_layout()
    
  5. 设置一个交互式 IPython 小部件:

    interact(confidence_interval, var=dl.data.Weather.get_headers())
    HTML(dl.report.HTMLBuilder().watermark())
    

最终结果参见以下截图(参见本书代码包中的bayes_confidence.ipynb文件):

How to do it...

另见

概率权重抽样

为了在第二次世界大战期间制造核弹,物理学家需要进行相当复杂的计算。斯坦尼斯瓦夫·乌兰产生了将这一挑战视为一场碰运气的游戏的想法。后来,他想出的方法被命名为蒙特卡洛。概率游戏通常有非常简单的规则,但是以最佳方式玩可能很困难。根据量子力学,亚原子粒子也是不可预测的。如果我们用亚原子粒子模拟许多实验,我们仍然可以了解它们可能的行为。蒙特卡罗方法不是确定性的,但是对于足够多的模拟,它接近于复杂计算的正确结果。

statsmodels.distributions.empirical_distribution.ECDF类定义了数据数组的累积分布函数。我们可以用它的输出来模拟一个复杂的过程。这个模拟并不完美,因为我们在这个过程中丢失了信息。

怎么做...

在这个食谱中,我们将模拟天气过程。我尤其对年气温值感兴趣。我有兴趣了解模拟数据集是否也呈现上升趋势:

  1. 进口情况如下:

    from statsmodels.distributions.empirical_distribution import ECDF
    import dautil as dl
    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.utils import check_random_state
    from IPython.html.widgets.interaction import interact
    from IPython.core.display import HTML
    
  2. 定义以下函数计算斜率:

    def slope(x, y):
        return np.polyfit(x, y, 1)[0]
    
  3. 定义以下函数生成单年数据:

    def simulate(x, years, rs, p):
        N = len(years)
        means = np.zeros(N)
    
        for i in range(N):
            sample = rs.choice(x, size=365, p=p)
            means[i] = sample.mean()
    
        return means, np.diff(means).mean(), slope(years, means)
    
  4. 定义以下函数来运行多个模拟:

    def run_multiple(times, x, years, p):
        sims = []
        rs = check_random_state(20)
    
        for i in range(times):
            sims.append(simulate(x, years, rs, p))
    
        return np.array(sims)
    
  5. 定义以下函数,默认加载温度值:

    def main(var='TEMP'):
        df = dl.data.Weather.load().dropna()[var]
        cdf = ECDF(df)
        x = cdf.x[1:]
        p = np.diff(cdf.y)
    
        df = df.resample('A')
        years = df.index.year
        sims = run_multiple(500, x, years, p)
    
        sp = dl.plotting.Subplotter(2, 1, context)
        plotter = dl.plotting.CyclePlotter(sp.ax)
        plotter.plot(years, df.values, label='Data')
        plotter.plot(years, sims[0][0], label='Sim 1')
        plotter.plot(years, sims[1][0], label='Sim 2')
        header = dl.data.Weather.get_header(var)
        sp.label(title_params=header, ylabel_params=header)
        sp.ax.legend(loc='best')
    
        sp.next_ax()
        sp.label()
        sp.ax.hist(sims.T[2], normed=True)
        plt.figtext(0.2, 0.3, 'Slope of the Data {:.3f}'.format(slope(years, df.values)))
        plt.tight_layout()
    

存储在本书代码包中sampling_weights.ipynb文件中的笔记本也为您提供了选择其他天气变量的选项。有关最终结果,请参考以下屏幕截图:

How to do it...

另见

探索极值

全世界有近一百万座大坝,其中大约 5%高于 15 米。设计大坝的土木工程师必须考虑许多因素,包括降雨量。为了简单起见,让我们假设工程师想知道累计年降雨量。我们也可以取月最大值,并将其拟合到一个 广义极值 ( GEV ) 分布。利用这个分布,我们可以引导得到我们的估计。相反,我选择的数值高于食谱中的第 95 个百分位数。

GEV 分布在scipy.stats中实现,并且是 Gumbel 分布、Frechet 分布和 Weibull 分布的混合。以下等式描述了累积分布函数 (3.11) 和相关约束 (3.12) :

Exploring extreme values

在这些方程中, μ 为位置参数, σ 为尺度参数, ξ 为形状参数。

怎么做...

让我们使用 GEV 分布来分析数据:

  1. 进口情况如下:

    from scipy.stats.distributions import genextreme
    import matplotlib.pyplot as plt
    import dautil as dl
    import numpy as np
    from IPython.display import HTML
    
  2. 定义以下函数对 GEV 分布进行采样:

    def run_sims(nsims):
        sums = []
    
        np.random.seed(19)
    
        for i in range(nsims):
            for j in range(len(years)):
                sample_sum = dist.rvs(shape, loc, scale, size=365).sum()
                sums.append(sample_sum)
    
        a = np.array(sums)
        low, high = dl.stats.ci(a)
    
        return a, low, high
    
  3. 加载数据,选择极值:

    rain = dl.data.Weather.load()['RAIN'].dropna()
    annual_sums = rain.resample('A', how=np.sum)
    years = np.unique(rain.index.year)
    limit = np.percentile(rain, 95)
    rain = rain[rain > limit]
    dist = dl.stats.Distribution(rain, genextreme)
    
  4. 将极值拟合到 GEV 分布:

    shape, loc, scale = dist.fit()
    table = dl.report.DFBuilder(['shape', 'loc', 'scale'])
    table.row([shape, loc, scale])
    dl.options.set_pd_options()
    html_builder = dl.report.HTMLBuilder()
    html_builder.h1('Exploring Extreme Values')
    html_builder.h2('Distribution Parameters')
    html_builder.add_df(table.build())
    
  5. 获取拟合残差的统计数据:

    pdf = dist.pdf(shape, loc, scale)
    html_builder.h2('Residuals of the Fit')
    residuals = dist.describe_residuals()
    html_builder.add(residuals.to_html())
    
  6. 获取适合度指标:

    table2 = dl.report.DFBuilder(['Mean_AD', 'RMSE'])
    table2.row([dist.mean_ad(), dist.rmse()])
    html_builder.h2('Fit Metrics')
    html_builder.add_df(table2.build())
    
  7. 绘制数据和引导的结果:

    sp = dl.plotting.Subplotter(2, 2, context)
    
    sp.ax.hist(annual_sums, normed=True, bins=dl.stats.sqrt_bins(annual_sums))
    sp.label()
    set_labels(sp.ax)
    
    sp.next_ax()
    sp.label()
    sp.ax.set_xlim([5000, 10000])
    sims = []
    nsims = [25, 50, 100, 200]
    
    for n in nsims:
        sims.append(run_sims(n))
    
    sims = np.array(sims)
    sp.ax.hist(sims[2][0], normed=True, bins=dl.stats.sqrt_bins(sims[2][0]))
    set_labels(sp.ax)
    
    sp.next_ax()
    sp.label()
    sp.ax.set_xlim([10, 40])
    sp.ax.hist(rain, bins=dist.nbins, normed=True, label='Rain')
    sp.ax.plot(dist.x, pdf, label='PDF')
    set_labels(sp.ax)
    sp.ax.legend(loc='best')
    
    sp.next_ax()
    sp.ax.plot(nsims, sims.T[1], 'o', label='2.5 percentile')
    sp.ax.plot(nsims, sims.T[2], 'x', label='97.5 percentile')
    sp.ax.legend(loc='center')
    sp.label(ylabel_params=dl.data.Weather.get_header('RAIN'))
    
    plt.tight_layout()
    HTML(html_builder.html)
    

最终结果参见以下截图(参见本书代码包中的extreme_values.ipynb文件):

How to do it...

另见

  • GEV 发行版的维基百科页面,位于(2015 年 8 月检索)。

用皮尔逊相关法关联变量

皮尔森的 r ,以其开发者卡尔·皮尔逊(1896)的名字命名为,测量两个变量之间的线性相关性。让我们看看下面的等式:

Correlating variables with Pearson's correlation

(3.13) 定义系数, (3.14) 描述用于计算置信区间的 费希尔变换(3.15) 给出相关的标准误差。 (3.16) 是关于费希尔变换相关的 z 分数。如果我们假设正态分布,我们可以使用 z 分数来计算置信区间。或者,我们可以通过替换对值对进行重采样来引导。此外,scipy.stats.pearsonr()函数返回一个 p 值,该值(根据文档)对于小于 500 个值的样本是不准确的。不幸的是,我们将在这个食谱中使用这样一个小样本。我们将把世界银行的二氧化碳排放数据与荷兰的相关温度数据联系起来。

怎么做...

在本食谱中,我们将通过以下步骤,使用 z 分数和自举计算相关系数并估计置信区间:

  1. 进口情况如下:

    import dautil as dl
    import pandas as pd
    from scipy import stats
    import numpy as np
    import math
    from sklearn.utils import check_random_state
    import matplotlib.pyplot as plt
    from IPython.display import HTML
    from IPython.display import display
    
  2. 下载数据并设置适当的数据结构:

    wb = dl.data.Worldbank()
    indicator = wb.get_name('co2')
    co2 = wb.download(country='NL', indicator=indicator, start=1900,
                      end=2014)
    co2.index = [int(year) for year in co2.index.get_level_values(1)]
    temp = pd.DataFrame(dl.data.Weather.load()['TEMP'].resample('A'))
    temp.index = temp.index.year
    temp.index.name = 'year'
    df = pd.merge(co2, temp, left_index=True, right_index=True).dropna()
    
  3. 计算相关性如下:

    stats_corr = stats.pearsonr(df[indicator].values, df['TEMP'].values)
    print('Correlation={0:.4g}, p-value={1:.4g}'.format(stats_corr[0], stats_corr[1]))
    
  4. 用费希尔变换计算置信区间:

    z = np.arctanh(stats_corr[0])
    n = len(df.index)
    se = 1/(math.sqrt(n - 3))
    ci = z + np.array([-1, 1]) * se * stats.norm.ppf((1 + 0.95)/2)
    
    ci = np.tanh(ci)
    dl.options.set_pd_options()
    ci_table = dl.report.DFBuilder(['Low', 'High'])
    ci_table.row([ci[0], ci[1]])
    
  5. 通过对替换对进行重采样来引导:

    rs = check_random_state(34)
    
    ranges = []
    
    for j in range(200):
        corrs = []
    
        for i in range(100):
            indices = rs.choice(n, size=n)
            pairs = df.values
            gen_pairs = pairs[indices]
            corrs.append(stats.pearsonr(gen_pairs.T[0], gen_pairs.T[1])[0])
    
        ranges.append(dl.stats.ci(corrs))
    
    ranges = np.array(ranges)
    bootstrap_ci = dl.stats.ci(corrs)
    ci_table.row([bootstrap_ci[0], bootstrap_ci[1]])
    ci_table = ci_table.build(index=['Formula', 'Bootstrap'])
    
  6. 绘制结果并生成报告:

    x = np.arange(len(ranges)) * 100
    plt.plot(x, ranges.T[0], label='Low')
    plt.plot(x, ranges.T[1], label='High')
    plt.plot(x, stats_corr[0] * np.ones_like(x), label='SciPy estimate')
    plt.ylabel('Pearson Correlation')
    plt.xlabel('Number of bootstraps')
    plt.title('Bootstrapped Pearson Correlation')
    plt.legend(loc='best')
    result = dl.report.HTMLBuilder()
    result.h1('Pearson Correlation Confidence intervals')
    result.h2('Confidence Intervals')
    result.add(ci_table.to_html())
    HTML(result.html)
    

最终结果参见下面的截图(参见本书代码包中的correlating_pearson.ipynb文件):

How to do it...

另见

将变量与斯皮尔曼等级相关进行关联

斯皮尔曼等级相关性使用等级将两个变量与皮尔逊相关性相关联。等级是值在排序顺序中的位置。值相等的项目会获得一个等级,这是它们位置的平均值。例如,如果我们有两个值相等的项目分配到位置 2 和 3,两个项目的排名都是 2.5。看看下面的等式:

Correlating variables with the Spearman rank correlation

在这些方程中, n 是样本量。 (3.17) 显示了如何计算相关性。 (3.19) 给出标准误差。 (3.20) 是关于的 z 分数,我们假设是正态分布。 F(r) 在这里与 (3.14) 相同,因为它是相同的相关性,但适用于等级。

怎么做...

在这个食谱中,我们计算了风速和温度之间的斯皮尔曼相关性,通过一年中的某一天和相应的置信区间进行汇总。然后,我们显示所有天气数据的相关矩阵。步骤如下:

  1. 进口情况如下:

    import dautil as dl
    from scipy import stats
    import numpy as np
    import math
    import seaborn as sns
    import matplotlib.pyplot as plt
    from IPython.html import widgets
    from IPython.display import display
    from IPython.display import HTML
    
  2. 定义以下函数计算置信区间:

    def get_ci(n, corr):
        z = math.sqrt((n - 3)/1.06) * np.arctanh(corr)
        se = 0.6325/(math.sqrt(n - 1))
        ci = z + np.array([-1, 1]) * se * stats.norm.ppf((1 + 0.95)/2)
    
        return np.tanh(ci)
    
  3. 加载数据并显示小部件,以便您可以根据需要关联不同的配对:

    df = dl.data.Weather.load().dropna()
    df = dl.ts.groupby_yday(df).mean()
    
    drop1 = widgets.Dropdown(options=dl.data.Weather.get_headers(), 
                             selected_label='TEMP', description='Variable 1')
    drop2 = widgets.Dropdown(options=dl.data.Weather.get_headers(), 
                             selected_label='WIND_SPEED', description='Variable 2')
    display(drop1)
    display(drop2)
    
  4. 计算斯皮尔曼等级与 SciPy 的相关性:

    var1 = df[drop1.value].values
    var2 = df[drop2.value].values
    stats_corr = stats.spearmanr(var1, var2)
    dl.options.set_pd_options()
    html_builder = dl.report.HTMLBuilder()
    html_builder.h1('Spearman Correlation between {0} and {1}'.format(
        dl.data.Weather.get_header(drop1.value), dl.data.Weather.get_header(drop2.value)))
    html_builder.h2('scipy.stats.spearmanr()')
    dfb = dl.report.DFBuilder(['Correlation', 'p-value'])
    dfb.row([stats_corr[0], stats_corr[1]])
    html_builder.add_df(dfb.build())
    
  5. 计算置信区间如下:

    n = len(df.index)
    ci = get_ci(n, stats_corr)
    html_builder.h2('Confidence intervale')
    dfb = dl.report.DFBuilder(['2.5 percentile', '97.5 percentile'])
    dfb.row(ci)
    html_builder.add_df(dfb.build())
    
  6. 将相关矩阵显示为 Seaborn 热图:

    corr = df.corr(method='spearman')
    
    %matplotlib inline
    plt.title('Spearman Correlation Matrix')
    sns.heatmap(corr)
    HTML(html_builder.html)
    

最终结果见下面截图(见本书代码包中的correlating_spearman.ipynb文件):

How to do it...

另见

将二元和连续变量与点双列相关进行关联

点-双列关联关联一个二元变量 Y 和一个连续变量 X 。系数计算如下:

Correlating a binary and a continuous variable with the point biserial correlation

(3.21) 中的下标对应两组二元变量。 M1X 的平均值,对应于 Y 组 1 的值。 M2X 对应于 Y 第 0 组数值的平均值。

在这个食谱中,我们将使用的二进制变量是下雨或不下雨。我们将把这个变量和温度联系起来。

怎么做...

我们将计算与scipy.stats.pointbiserialr()函数的相关性。我们还将使用带有np.roll()功能的 2 年窗口计算滚动相关性。步骤如下:

  1. 进口情况如下:

    import dautil as dl
    from scipy import stats
    import numpy as np
    import matplotlib.pyplot as plt
    import pandas as pd
    from IPython.display import HTML
    
  2. 加载数据并关联两个相关数组:

    df = dl.data.Weather.load().dropna()
    df['RAIN'] = df['RAIN'] > 0
    
    stats_corr = stats.pointbiserialr(df['RAIN'].values, df['TEMP'].values)
    
  3. 计算 2 年滚动相关性如下:

    N = 2 * 365
    corrs = []
    
    for i in range(len(df.index) - N):
        x = np.roll(df['RAIN'].values, i)[:N]
        y = np.roll(df['TEMP'].values, i)[:N]
        corrs.append(stats.pointbiserialr(x, y)[0])
    
    corrs = pd.DataFrame(corrs,
                         index=df.index[N:],
                         columns=['Correlation']).resample('A')
    
  4. 用以下代码绘制结果:

    plt.plot(corrs.index.values, corrs.values)
    plt.hlines(stats_corr[0], corrs.index.values[0], corrs.index.values[-1],
               label='Correlation using the whole data set')
    plt.title('Rolling Point-biserial Correlation of Rain and Temperature with a 2 Year Window')
    plt.xlabel('Year')
    plt.ylabel('Correlation')
    plt.legend(loc='best')
    HTML(dl.report.HTMLBuilder().watermark())
    

最终结果见以下截图(见本书代码包中correlating_pointbiserial.ipynb文件):

How to do it...

另见

用方差分析评价变量之间的关系

方差分析 ( 方差分析)是统计学家罗纳德·费雪发明的一种统计数据分析方法。该方法使用一个或多个对应分类变量的值分割连续变量的数据,以分析方差。方差分析是线性建模的一种形式。如果我们用一个分类变量建模,我们说的单向方差分析。在这个食谱中,我们将使用两个分类变量,因此我们有双向方差分析。在双向方差分析中,我们创建了一个列联表—一个包含两个分类变量所有组合的计数的表(我们很快会看到一个列联表的例子)。线性模型由下式给出:

Evaluating relations between variables with ANOVA

这是一个加法模型,其中 μ ij 是对应于列联表的一个单元格的连续变量的平均值, μ 是整个数据集的平均值, α i 是第一个分类变量的贡献, β j 是第二个分类变量的贡献, ɣ ij 我们将把这个模型应用于天气数据。

怎么做...

以下步骤将双向方差分析应用于作为连续变量的风速、作为二元变量的雨和作为分类变量的风向:

  1. 进口情况如下:

    from statsmodels.formula.api import ols
    import dautil as dl
    from statsmodels.stats.anova import anova_lm
    import seaborn as sns
    import matplotlib.pyplot as plt
    from IPython.display import HTML
    
  2. 加载数据,用statsmodels :

    df = dl.data.Weather.load().dropna()
    df['RAIN'] = df['RAIN'] > 0
    formula = 'WIND_SPEED ~ C(RAIN) + C(WIND_DIR)'
    lm = ols(formula, df).fit()
    hb = dl.HTMLBuilder()
    hb.h1('ANOVA Applied to Weather Data')
    hb.h2('ANOVA results')
    hb.add_df(anova_lm(lm), index=True)
    

    拟合模型

  3. 显示一个截断的列联表,并用 Seaborn 可视化数据:

    df['WIND_DIR'] = dl.data.Weather.categorize_wind_dir(df)
    hb.h2('Truncated Contingency table')
    hb.add_df(df.groupby([df['RAIN'], df['WIND_DIR']]).count().head(3),index=True)
    
    sns.pointplot(y='WIND_SPEED', x='WIND_DIR',
                  hue='RAIN', data=df[['WIND_SPEED', 'RAIN', 'WIND_DIR']])
    HTML(hb.html)
    

最终结果参见以下截图(参见本书代码包中的anova.ipynb文件):

How to do it...

另见

四、处理数据和数字问题

本章中的配方如下:

  • 裁剪和过滤异常值
  • 正在获取数据
  • 测量噪声数据的中心趋势
  • 用 Box-Cox 变换归一化
  • 用权力阶梯转换数据
  • 用对数转换数据
  • 重新绑定数据
  • 应用logit()变换比例
  • 拟合稳健的线性模型
  • 用加权最小二乘法考虑方差
  • 使用任意精度进行优化
  • 线性代数使用任意精度

简介

在现实世界中,数据很少与教科书的定义和例子相匹配。我们必须处理诸如硬件故障、不合作的客户和不满的同事等问题。很难预测你会遇到什么样的问题,但可以肯定的是,这些问题会非常丰富和具有挑战性。在本章中,我将概述一些处理有噪声数据的常见方法,这些方法更多地基于经验法则,而不是严格的科学。幸运的是,数据分析的试错部分是有限的。

本章的大部分内容是关于异常值管理的。离群值是我们认为不正常的值。当然,这不是你唯一会遇到的问题,但这是一个偷偷摸摸的问题。一个常见的问题是缺少或无效的值,所以我将简要地提到屏蔽数组和 pandas 特性,例如我在本书中使用的dropna()函数。

我还写了两个关于使用 mpmath 进行任意精度计算的食谱。我不建议使用 mpmath,除非你真的必须这样做,因为你必须付出性能代价。通常我们可以解决数值问题,所以很少需要任意精度的库。

裁剪和过滤异常值

异常值是数据分析中常见的问题。虽然不存在异常值的精确定义,但我们知道异常值会影响均值和回归结果。异常值是异常的值。通常,异常值是由测量误差引起的,但异常值有时是真实的。在第二种情况下,我们可能要处理两种或两种以上与不同现象相关的数据。

该配方的数据在中描述。它由某一星团中 47 颗恒星的对数有效温度和对数光强组成。任何一个天文学家读了这一段都会知道 T4【赫茨普鲁-罗素图。从数据分析的角度来看,该图是一个散点图,但对于天文学家来说,它当然不止于此。赫茨普龙罗素图是在 1910 年左右定义的,其特征是一条对角线(不完全是直线),称为主序列。我们数据集中的大多数恒星应该在主序列上,左上角有四个异常值。这些异常值被归类为巨人。

我们有许多策略来处理异常值。在这个食谱中,我们将使用两个最简单的策略:用 NumPy clip()函数裁剪和完全去除异常值。对于本例,我将异常值定义为从第一个和第三个四分位数定义的框中移除的 1.5 个四分位数区间的值。

怎么做...

以下步骤显示了如何裁剪和过滤异常值:

  1. 进口情况如下:

    import statsmodels.api as sm
    import matplotlib.pyplot as plt
    import numpy as np
    import seaborn as sns
    import dautil as dl
    from IPython.display import HTML
    
  2. 定义以下函数过滤异常值:

    def filter_outliers(a):
        b = a.copy()
        bmin, bmax = dl.stats.outliers(b)
        b[bmin > b] = np.nan
        b[b > bmax] = np.nan
    
        return b
    
  3. 如下所示加载和裁剪异常值:

    starsCYG = sm.datasets.get_rdataset("starsCYG", "robustbase", cache=True).data
    
    clipped = starsCYG.apply(dl.stats.clip_outliers)
    
  4. 过滤异常值如下:

    filtered = starsCYG.copy()
    filtered['log.Te'] = filter_outliers(filtered['log.Te'].values)
    filtered['log.light'] = filter_outliers(filtered['log.light'].values)
    filtered.dropna()
    
  5. 用以下代码绘制结果:

    sp = dl.plotting.Subplotter(3, 1, context)
    sp.label()
    sns.regplot(x='log.Te', y='log.light', data=starsCYG, ax=sp.ax)
    sp.label(advance=True)
    sns.regplot(x='log.Te', y='log.light', data=clipped, ax=sp.ax)
    sp.label(advance=True)
    sns.regplot(x='log.Te', y='log.light', data=filtered, ax=sp.ax)
    plt.tight_layout()
    HTML(dl.report.HTMLBuilder().watermark())
    

最终结果参见下面的截图(参见本书代码包中的outliers.ipynb文件):

How to do it...

另见

winsoring 数据

winsoring是处理异常值的另一种技术,以 Charles Winsor 命名。实际上,Winsorization 以对称的方式将异常值裁剪到给定的百分位数。例如,我们可以剪辑到第 5 和第 95 百分位。SciPy 有一个winsorize()功能,执行这个程序。该配方的数据与裁剪和过滤异常值配方的数据相同。

怎么做...

使用以下程序读取数据:

  1. 进口情况如下:

    rom scipy.stats.mstats import winsorize
    import statsmodels.api as sm
    import seaborn as sns
    import matplotlib.pyplot as plt
    import dautil as dl
    from IPython.display import HTML
    
  2. 加载并保存有效温度的数据(限制设置为 15%):

    starsCYG = sm.datasets.get_rdataset("starsCYG", "robustbase", cache=True).data
    limit = 0.15
    winsorized_x = starsCYG.copy()
    winsorized_x['log.Te'] = winsorize(starsCYG['log.Te'], limits=limit)
    
  3. 按照以下方式调节光强:

    winsorized_y = starsCYG.copy()
    winsorized_y['log.light'] = winsorize(starsCYG['log.light'], limits=limit)
    winsorized_xy = starsCYG.apply(winsorize, limits=[limit, limit])
    
  4. 用回归线绘制赫茨普鲁格-罗素图(不是通常天文图的一部分):

    sp = dl.plotting.Subplotter(2, 2, context)
    sp.label()
    sns.regplot(x='log.Te', y='log.light', data=starsCYG, ax=sp.ax)
    
    sp.label(advance=True)
    sns.regplot(x='log.Te', y='log.light', data=winsorized_x, ax=sp.ax)
    
    sp.label(advance=True)
    sns.regplot(x='log.Te', y='log.light', data=winsorized_y, ax=sp.ax)
    
    sp.label(advance=True)
    sns.regplot(x='log.Te', y='log.light', data=winsorized_xy, ax=sp.ax)
    plt.tight_layout()
    HTML(dl.report.HTMLBuilder().watermark())
    

最终结果见下图截图(参见本书代码包中的winsorising_data.ipynb文件):

How to do it...

另见

测量噪声数据的中心趋势

我们可以用平均值和中位数来测量中心趋势。这些措施使用所有可用的数据。通过丢弃数据集中较高端和较低端的数据来消除异常值是一个普遍接受的想法。截断表示修剪表示,其派生词如 四分位数间平均值 ( IQM )和曲美、也采用这种思路。看看下面的等式:

Measuring central tendency of noisy data

截断平均值在给定的百分位数丢弃数据,例如,从最低值到第 5 个百分位数,从第 95 个百分位数到最高值。曲美安 (4.1)是中位数、第一四分位数和第三四分位数的加权平均值。对于 IQM (4.2),我们丢弃数据的最低和最高四分位数,因此这是截断平均值的特殊情况。我们将使用 SciPy tmean()trima()函数计算这些度量。

怎么做...

我们将通过以下步骤研究不同截断水平的主要趋势:

  1. 进口情况如下:

    import matplotlib.pyplot as plt
    from scipy.stats import tmean
    from scipy.stats.mstats import trima
    import numpy as np
    import dautil as dl
    import seaborn as sns
    from IPython.display import HTML
    
    context = dl.nb.Context('central_tendency')
    
  2. 定义以下函数计算四分位数平均值:

    def iqm(a):
        return truncated_mean(a, 25)
    
  3. 定义以下函数来绘制分布:

    def plotdists(var, ax):
        displot_label = 'From {0} to {1} percentiles'
        cyc = dl.plotting.Cycler()
    
        for i in range(1, 9, 3):
            limits = dl.stats.outliers(var, method='percentiles', 
                                   percentiles=(i, 100 - i))
            truncated = trima(var, limits=limits).compressed()
            sns.distplot(truncated, ax=ax, color=cyc.color(),
                         hist_kws={'histtype': 'stepfilled', 'alpha': 1/i,
                                   'linewidth': cyc.lw()},
                         label=displot_label.format(i, 100 - i))
    
  4. 定义以下函数计算截断平均值:

    def truncated_mean(a, percentile):
        limits = dl.stats.outliers(a, method='percentiles', 
                                   percentiles=(percentile, 100 - percentile))
    
        return tmean(a, limits=limits)
    
  5. 加载数据,计算方式如下:

    df = dl.data.Weather.load().resample('M').dropna()
    x = range(9)
    temp_means = [truncated_mean(df['TEMP'], i) for i in x]
    ws_means = [truncated_mean(df['WIND_SPEED'], i) for i in x]
    
  6. 用以下代码绘制平均值和分布:

    sp = dl.plotting.Subplotter(2, 2, context)
    cp = dl.plotting.CyclePlotter(sp.ax)
    cp.plot(x, temp_means, label='Truncated mean')
    cp.plot(x, dl.stats.trimean(df['TEMP']) * np.ones_like(x), label='Trimean')
    cp.plot(x, iqm(df['TEMP']) * np.ones_like(x), label='IQM')
    sp.label(ylabel_params=dl.data.Weather.get_header('TEMP'))
    
    cp = dl.plotting.CyclePlotter(sp.next_ax())
    cp.plot(x, ws_means, label='Truncated mean')
    cp.plot(x, dl.stats.trimean(df['WIND_SPEED']) * np.ones_like(x),
            label='Trimean')
    cp.plot(x, iqm(df['WIND_SPEED']) * np.ones_like(x), label='IQM')
    sp.label(ylabel_params=dl.data.Weather.get_header('WIND_SPEED'))
    
    plotdists(df['TEMP'], sp.next_ax())
    sp.label(xlabel_params=dl.data.Weather.get_header('TEMP'))
    
    plotdists(df['WIND_SPEED'], sp.next_ax())
    sp.label(xlabel_params=dl.data.Weather.get_header('WIND_SPEED'))
    plt.tight_layout()
    HTML(dl.report.HTMLBuilder().watermark())
    

最终结果参见以下截图(参见本书代码包中的central_tendency.ipynb文件):

How to do it...

另见

用 Box-Cox 变换归一化

不遵循已知分布(如正态分布)的数据通常难以管理。控制数据的一个流行策略是应用 Box-Cox 变换。它由以下等式给出:

Normalizing with the Box-Cox transformation

scipy.stats.boxcox()函数可以对正数据应用转换。我们将使用与裁剪和过滤异常值配方中相同的数据。通过 Q-Q 图,我们将表明 Box-Cox 变换确实使数据看起来更正常。

怎么做...

以下步骤显示了如何使用 Box-Cox 转换对数据进行规范化:

  1. 进口情况如下:

    import statsmodels.api as sm
    import matplotlib.pyplot as plt
    from scipy.stats import boxcox
    import seaborn as sns
    import dautil as dl
    from IPython.display import HTML
    
  2. 加载数据并转换如下:

    context = dl.nb.Context('normalizing_boxcox')
    
    starsCYG = sm.datasets.get_rdataset("starsCYG", "robustbase", cache=True).data
    
    var = 'log.Te'
    
    # Data must be positive
    transformed, _ = boxcox(starsCYG[var])
    
  3. 显示 Q-Q 图和分布图如下:

    sp = dl.plotting.Subplotter(2, 2, context)
    sp.label()
    sm.qqplot(starsCYG[var], fit=True, line='s', ax=sp.ax)
    
    sp.label(advance=True)
    sm.qqplot(transformed, fit=True, line='s', ax=sp.ax)
    
    sp.label(advance=True)
    sns.distplot(starsCYG[var], ax=sp.ax)
    
    sp.label(advance=True)
    sns.distplot(transformed, ax=sp.ax)                                       
    plt.tight_layout()
    HTML(dl.report.HTMLBuilder().watermark())
    

最终结果参见下面的截图(参见本书代码包中的normalizing_boxcox.ipynb文件):

How to do it...

它是如何工作的

在之前的截图中,Q-Q 图将正态分布的理论分位数与实际数据的分位数进行了对比。为了帮助评估是否符合正态分布,我显示了一条线,它应该与完全正常的数据一致。数据越符合直线,就越正常。如您所见,转换后的数据更符合直线,因此更正常。分布图应该有助于你确认这一点。

另见

用权力阶梯转换数据

线性关系在科学和数据分析中很常见。显然,线性模型比非线性模型更容易理解。所以历史上,线性模型的工具最先被开发出来。在某些情况下,线性化(使数据线性)以简化分析是有好处的。一个简单的策略有时会奏效,那就是将一个或多个变量平方或立方。同样,我们可以通过取平方根或立方根,将数据转换成一个假想的幂阶梯。

在本食谱中,我们将使用中描述的邓肯数据集的数据。这些数据是在 1961 年前后收集的,大约有 45 种职业,分为四栏——类型、收入、教育和声望。我们来看看收入和声望。这些变量似乎通过三次多项式联系在一起,所以我们可以取收入的立方根或声望的立方根。为了检查结果,我们将把回归的残差可视化。期望是残差是随机分布的,这意味着我们不期望它们遵循可识别的模式。

怎么做...

在以下步骤中,我将演示基本的数据转换:

  1. 进口情况如下:

    import matplotlib.pyplot as plt
    import numpy as np
    import dautil as dl
    import seaborn as sns
    import statsmodels.api as sm
    from IPython.display import HTML
    
  2. 加载并转换数据如下:

    df = sm.datasets.get_rdataset("Duncan", "car", cache=True).data
    transformed = df.copy()
    transformed['income'] = np.power(transformed['income'], 1.0/3)
    
  3. 用 Seaborn 回归图(三次多项式)绘制原始数据,如下所示:

    sp = dl.plotting.Subplotter(2, 2, context)
    sp.label()
    sns.regplot(x='income', y='prestige', data=df, order=3, ax=sp.ax)
    
  4. 用以下线条绘制转换后的数据:

    sp.label(advance=True)
    sns.regplot(x='income', y='prestige', data=transformed, ax=sp.ax)
    
  5. 绘制三次多项式的残差图:

    sp.label(advance=True)
    sns.residplot(x='income', y='prestige', data=df, order=3, ax=sp.ax)
    
  6. 绘制残差图绘制转换数据如下:

    sp.label(advance=True)
    sp.ax.set_xlim([1, 5])
    sns.residplot(x='income', y='prestige', data=transformed, ax=sp.ax)
    plt.tight_layout()
    HTML(dl.report.HTMLBuilder().watermark())
    

最终结果参见以下截图(参见本书代码包中的transforming_up.ipynb文件):

How to do it...

用对数转换数据

当数据按数量级变化时,用对数转换数据是一个显而易见的策略。在我的经验中,用指数函数做相反的变换不太常见。通常在探索时,我们将成对变量的对数或半对数散点图可视化。

为了展示这一转变,我们将使用世界银行的数据,计算每 1000 例活产婴儿死亡率和可用国家的人均国内生产总值(T2)(T4)。如果我们对两个变量都应用以 10 为底的对数,我们通过拟合数据得到的直线的斜率有一个有用的性质。一个变量增加 1%对应于另一个变量斜率给出的百分比变化。

怎么做...

按照以下步骤使用对数转换数据:

  1. 进口情况如下:

    import dautil as dl
    import matplotlib.pyplot as plt
    import numpy as np
    from IPython.display import HTML
    
  2. 下载 2010 年数据,代码如下:

    wb = dl.data.Worldbank()
    countries = wb.get_countries()[['name', 'iso2c']]
    inf_mort = wb.get_name('inf_mort')
    gdp_pcap = wb.get_name('gdp_pcap')
    df = wb.download(country=countries['iso2c'],
                     indicator=[inf_mort, gdp_pcap],
                     start=2010, end=2010).dropna()
    
  3. 使用以下代码片段应用日志转换:

    loglog = df.applymap(np.log10)
    x = loglog[gdp_pcap]
    y = loglog[inf_mort]
    
  4. 绘制变换前后的数据:

    sp = dl.plotting.Subplotter(2, 1, context)
    xvar = 'GDP per capita'
    sp.label(xlabel_params=xvar)
    sp.ax.set_ylim([0, 200])
    sp.ax.scatter(df[gdp_pcap], df[inf_mort])
    
    sp.next_ax()
    sp.ax.scatter(x, y, label='Transformed')
    dl.plotting.plot_polyfit(sp.ax, x, y)
    sp.label(xlabel_params=xvar)
    plt.tight_layout()
    HTML(dl.report.HTMLBuilder().watermark())
    

最终结果参见下面的截图(参见本书代码包中的transforming_down.ipynb文件):

How to do it...

重新绑定数据

通常,我们拥有的数据并不是按照我们想要的方式构建的。我们可以使用的一种结构化技术叫做(统计)数据宁滨逆势。该策略用一个代表值替换一个区间(一个箱)内的值。在这个过程中,我们可能会丢失信息;但是,我们可以更好地控制数据和效率。

在天气数据集中,我们有以度为单位的风向和以米/秒为单位的风速,它们可以用不同的方式表示。在这个食谱中,我选择用基本方向(北、南等)来表示风向。对于风速,我使用了博福特标度(访问https://en.wikipedia.org/wiki/Beaufort_scale)。

怎么做...

按照以下说明重新绑定数据:

  1. 进口情况如下:

    import dautil as dl
    import seaborn as sns
    import matplotlib.pyplot as plt
    import pandas as pd
    import numpy as np
    from IPython.display import HTML
    
  2. 按照以下方式加载和重新绑定数据(风向为 0-360 度;我们重新绑定到基本方向,如北、西南等):

    df = dl.data.Weather.load()[['WIND_SPEED', 'WIND_DIR']].dropna()
    categorized = df.copy()
    categorized['WIND_DIR'] = dl.data.Weather.categorize_wind_dir(df)
    categorized['WIND_SPEED'] = dl.data.Weather.beaufort_scale(df)
    
  3. 用以下代码显示分布和计数图:

    sp = dl.plotting.Subplotter(2, 2, context)
    sns.distplot(df['WIND_SPEED'], ax=sp.ax)
    sp.label(xlabel_params=dl.data.Weather.get_header('WIND_SPEED'))
    
    sns.distplot(df['WIND_DIR'], ax=sp.next_ax())
    sp.label(xlabel_params=dl.data.Weather.get_header('WIND_DIR'))
    
    sns.countplot(x='WIND_SPEED', data=categorized, ax=sp.next_ax())
    sp.label()
    
    sns.countplot(x='WIND_DIR', data=categorized, ax=sp.next_ax())
    sp.label()
    plt.tight_layout()
    HTML(dl.report.HTMLBuilder().watermark())
    

最终结果参见下面的截图(参见本书代码包中的rebinning_data.ipynb文件):

How to do it...

应用 logit()变换比例

我们可以使用 SciPy logit()函数转换比例或比率。结果应该是更高斯的分布。该函数是由以下等式定义的:

Applying logit() to transform proportions

正如你在等式(4.4)中看到的,logit 是赔率的对数。我们希望通过这种转换获得更对称的分布——接近于零的偏斜。当比例接近 0 和 1 时,logit 渐近地接近负无穷大和无穷大,所以在这些情况下我们必须小心。

作为一个比例的例子,我们将采取每月的雨天比例。我们通过将降雨量转化为二进制变量,然后对每个月进行平均,得到这些比例。

怎么做...

按照本指南通过转换比率:

  1. 进口情况如下:

    import dautil as dl
    import seaborn as sns
    import matplotlib.pyplot as plt
    import pandas as pd
    import math
    import statsmodels.api as sm
    from scipy.special import logit
    from IPython.display import HTML
    
  2. 加载数据并用以下代码转换:

    rain = dl.data.Weather.load()['RAIN'].dropna()
    rain = rain > 0
    rain = rain.resample('M').dropna()
    transformed = rain.apply(logit)
    transformed = dl.data.dropinf(transformed.values)
    
  3. 用分布图和 Q-Q 图绘制转换的结果:

    sp = dl.plotting.Subplotter(2, 2, context)
    sns.distplot(rain, ax=sp.ax)
    sp.label()
    
    sp.label(advance=True)
    sns.distplot(transformed, ax=sp.ax)
    
    sp.label(advance=True)
    sm.qqplot(rain, line='s', ax=sp.ax)
    
    sp.label(advance=True)
    sm.qqplot(transformed, line='s', ax=sp.ax)
    plt.tight_layout()
    HTML(dl.report.HTMLBuilder().watermark())
    

最终结果见下图截图(参见本书代码包中的transforming_ratios.ipynb文件):

How to do it...

拟合稳健线性模型

稳健回归旨在比普通回归更好地处理数据中的异常值。这种类型的回归使用特殊的稳健估计量,这也受到状态模型的支持。显然,没有最佳估计量,所以估计量的选择取决于数据和模型。

在这个配方中,我们将拟合 statsmodels 中可用的年太阳黑子数数据。我们将定义一个简单的模型,其中当前计数线性依赖于先前的值。为了演示异常值的影响,我添加了一个相当大的值,我们将比较稳健回归模型和普通最小二乘模型。

怎么做...

以下步骤描述了如何应用稳健线性模型:

  1. 进口情况如下:

    import statsmodels.api as sm
    import matplotlib.pyplot as plt
    import dautil as dl
    from IPython.display import HTML
    
  2. 定义以下功能来设置地块的标签:

    def set_labels(ax):
        ax.set_xlabel('Year')
        ax.set_ylabel('Sunactivity')
    
  3. 定义以下功能来绘制模型拟合:

    def plot_fit(df, ax, results):
        x = df['YEAR']
        cp = dl.plotting.CyclePlotter(ax)
        cp.plot(x[1:], df['SUNACTIVITY'][1:], label='Data')
        cp.plot(x[2:], results.predict()[1:], label='Fit')
        ax.legend(loc='best')
    
  4. 加载数据并添加一个异常值用于演示目的:

    df = sm.datasets.sunspots.load_pandas().data
    vals = df['SUNACTIVITY'].values
    
    # Outlier added by malicious person, because noone
    # laughs at his jokes.
    vals[0] = 100
    
  5. 如下所示拟合稳健模型:

    rlm_model = sm.RLM(vals[1:], sm.add_constant(vals[:-1]),
                       M=sm.robust.norms.TrimmedMean())
    
    rlm_results = rlm_model.fit()
    hb = dl.report.HTMLBuilder()
    hb.h1('Fitting a robust linear model')
    hb.h2('Robust Linear Model')
    hb.add(rlm_results.summary().tables[1].as_html())
    
  6. 拟合一个普通的最小二乘模型:

    hb.h2('Ordinary Linear Model')
    ols_model = sm.OLS(vals[1:], sm.add_constant(vals[:-1]))
    ols_results = ols_model.fit()
    hb.add(ols_results.summary().tables[1].as_html())
    
  7. 用以下代码绘制数据和模型结果:

    fig, [ax, ax2] = plt.subplots(2, 1)
    
    plot_fit(df, ax, rlm_results)
    ax.set_title('Robust Linear Model')
    set_labels(ax)
    
    ax2.set_title('Ordinary Least Squares')
    plot_fit(df, ax2, ols_results)
    set_labels(ax2)
    plt.tight_layout()
    HTML(hb.html)
    

最终结果参见下面的截图(参见本书代码包中的rlm_demo.ipynb文件):

How to do it...

另见

用加权最小二乘法考虑方差

statsmodels 库允许我们为回归定义每个数据点的任意权重。用简单的拇指规则有时很容易发现异常值。其中一个经验法则是基于四分位数范围,即第一个四分位数和第三个四分位数数据之间的差异。通过四分位数范围,我们可以定义加权最小二乘回归的权重。

我们将使用来自的数据和模型来拟合鲁棒的线性模式,但是具有任意的权重。我们怀疑是异常值的点将获得较低的权重,这是刚才提到的四分位数范围值的倒数。

怎么做...

使用以下方法用加权最小二乘法拟合数据:

  1. 进口情况如下:

    import dautil as dl
    import matplotlib.pyplot as plt
    import statsmodels.api as sm
    import numpy as np
    from IPython.display import HTML
    
  2. 加载数据并添加一个异常值:

    temp = dl.data.Weather.load()['TEMP'].dropna()
    temp = dl.ts.groupby_yday(temp).mean()
    
    # Outlier added by malicious person, because noone
    # laughs at his jokes.
    temp.values[0] = 100
    
  3. 使用普通最小二乘模型拟合:

    ntemp = len(temp)
    x = np.arange(1, ntemp + 1)
    factor = 2 * np.pi/365.25
    cos_x = sm.add_constant(np.cos(-factor * x - factor * 337))
    ols_model = sm.OLS(temp, cos_x)
    ols_results = ols_model.fit()
    hb = dl.report.HTMLBuilder()
    hb.h1('Taking variance into account with weighted least squares')
    hb.h2('Ordinary least squares')
    hb.add(ols_results.summary().tables[1].as_html())
    ols_preds = ols_results.predict()
    
  4. 使用四分位数范围计算权重,并拟合加权最小二乘模型:

    box = dl.stats.Box(temp)
    iqrs = box.iqr_from_box()
    # Adding 1 to avoid div by 0
    weights = 1./(iqrs + 1)
    wls_model = sm.WLS(temp, cos_x, weights=weights)
    wls_results = wls_model.fit()
    
    hb.h2('Weighted least squares')
    hb.add(wls_results.summary().tables[1].as_html())
    
  5. 绘制模型结果和权重:

    sp = dl.plotting.Subplotter(2, 2, context)
    
    sp.ax.plot(x[1:], temp[1:], 'o', label='Data')
    sp.ax.plot(x[1:], ols_preds[1:], label='Fit')
    sp.label(ylabel_params=dl.data.Weather.get_header('TEMP'))
    
    sp.label(advance=True)
    sp.ax.plot(x, iqrs, 'o')
    
    sp.next_ax().plot(x[1:], temp[1:], 'o', label='Data')
    sp.ax.plot(x[1:], wls_results.predict()[1:], label='Fit')
    sp.label(ylabel_params=dl.data.Weather.get_header('TEMP'))
    
    sp.label(advance=True)
    sp.ax.plot(x, weights, 'o')
    plt.tight_layout()
    HTML(hb.html)
    

最终结果参见下面的截图(参见本书代码包中的weighted_ls.ipynb文件):

How to do it...

另见

使用任意精度进行优化

这本书的目标读者应该知道浮点数的问题。我会提醒你,我们不能准确地表示浮点数。偶数整数表示是有限的。对于某些应用,例如金融计算或涉及已知解析表达式的工作,我们需要比 NumPy 等数值软件更高的精度。Python 标准库提供了Decimal类,我们可以用它来实现任意精度。然而,专门的mpmath库更适合更高级的用途。

温度遵循季节性模式,因此包含余弦的模型似乎是自然的。我们将应用这样的模型。使用任意精度的好处是,您可以轻松地进行分析、微分、求根和逼近多项式。

做好准备

使用以下任一命令安装专用mpmath库:

$ conda install mpmath
$ pip install mpmath

我通过 Anaconda 用 mpmath 0.19 测试了代码。

怎么做...

以下说明描述了如何使用任意精度进行优化:

  1. 进口情况如下:

    import numpy as np
    import matplotlib.pyplot as plt
    import mpmath
    import dautil as dl
    from IPython.display import HTML
    
  2. 为模型和一阶导数定义以下函数:

    def model(t):
        mu, C, w, phi = (9.6848106, -7.59870042, -0.01766333, -5.83349705)
    
        return mu + C * mpmath.cos(w * t + phi)
    
    def diff_model(t):
        return mpmath.diff(model, t)
    
  3. 加载数据,求一阶导数的根:

    vals = dl.data.Weather.load()['TEMP'].dropna()
    vals = dl.ts.groupby_yday(vals).mean()
    diff_root = mpmath.findroot(diff_model, (1, 366), solver='anderson')
    
  4. 获得模型的多项式近似,如下所示:

    days = range(1, 367)
    poly = mpmath.chebyfit(model, (1, 366), 3)
    poly = np.array([float(c) for c in poly])
    
  5. 用以下代码绘制数据、建模结果和近似值:

    sp = dl.plotting.Subplotter(2, 1, context)
    cp = dl.plotting.CyclePlotter(sp.ax)
    cp.plot(days, [model(i) for i in days], label='Model')
    cp.plot(days, vals, label='Data')
    sp.ax.annotate(s='Root of derivative', xy=(diff_root, vals.max() - 1),
                xytext=(diff_root, vals.max() - 8),
                arrowprops=dict(arrowstyle='->'))
    yvar = dl.data.Weather.get_header('TEMP')
    sp.label(ylabel_params=yvar)
    
    cp = dl.plotting.CyclePlotter(sp.next_ax())
    cp.plot(days, vals, label='Data')
    cp.plot(days, np.polyval(poly, days), label='Approximation')
    sp.label(ylabel_params=yvar)
    plt.tight_layout()
    HTML(dl.report.HTMLBuilder().watermark())
    

最终结果参见下面的截图(参见本书代码包中的mpmath_fit.ipynb文件):

How to do it...

另见

对线性代数使用任意精度

很多模型可以归结为线性方程组,其中是线性代数的定义域。中提到的使用任意精度进行优化的 mpmath 库配方也可以做任意精度的线性代数。

理论上,我们可以将任何可微函数近似为多项式级数。为了找到多项式的系数,我们可以定义一个线性方程组,基本上是取一个数据向量(向量作为数学项)的幂,用一个 1 的向量来表示多项式中的常数。我们将用 mpmath lu_solve()函数来求解这样一个系统。作为示例数据,我们将使用按一年中的某一天分组的风速数据。

做好准备

有关说明,请参考使用任意精度进行优化配方。

怎么做...

按照以下步骤对线性代数使用任意精度:

  1. 进口情况如下:

    import mpmath
    import dautil as dl
    import numpy as np
    import matplotlib.pyplot as plt
    from IPython.display import HTML
    
  2. 定义以下函数,用 mpmath 计算算术平均值:

    def mpmean(arr):
        mpfs = [mpmath.mpf(a) for a in arr]
    
        return sum(mpfs)/len(arr)
    
  3. 加载数据,用lu_solve() :

    vals = dl.data.Weather.load()['WIND_SPEED'].dropna()
    vals = dl.ts.groupby_yday(vals).apply(mpmean)
    
    days = np.arange(1, 367, dtype=int)
    A = [[], [], []]
    A[0] = np.ones_like(days, dtype=int).tolist()
    A[1] = days.tolist()
    A[2] = (days ** 2).tolist()
    A = mpmath.matrix(A).transpose()
    
    params = mpmath.lu_solve(A, vals)
    
    result = dl.report.HTMLBuilder()
    result.h1('Arbitrary Precision Linear Algebra')
    result.h2('Polynomial fit')
    dfb = dl.report.DFBuilder(['Coefficient 0', 'Coefficient 1', 'Coefficient 2'])
    dfb.row(params)
    result.add_df(dfb.build())
    

    求解系统

  4. 定义以下函数来计算我们得到的多项式:

    def poly(x):
        return mpmath.polyval(params[::-1], x)
    
  5. 使用fourier()函数获得三角近似:

    cs = mpmath.fourier(poly, days.tolist(), 1)
    result.h2('Cosine and sine terms')
    dfb = dl.report.DFBuilder(['Coefficient 1', 'Coefficient 2'])
    dfb.row(cs[0])
    dfb.row(cs[1])
    result.add_df(dfb.build(index=['Cosine', 'Sine']), index=True)
    
  6. 将数据、模型结果和近似值绘制如下:

    sp = dl.plotting.Subplotter(2, 1, context)
    
    cp = dl.plotting.CyclePlotter(sp.ax)
    cp.plot(days, vals, label='Data')
    cp.plot(days, poly(days), label='Fit')
    yvar = dl.data.Weather.get_header('WIND_SPEED')
    sp.label(ylabel_params=yvar)
    
    cp = dl.plotting.CyclePlotter(sp.next_ax())
    cp.plot(days, vals, label='Data')
    cp.plot(days, [mpmath.fourierval(cs, days, d) for d in days], label='Approximation')
    sp.label(ylabel_params=yvar)
    plt.tight_layout()
    HTML(result.html)
    

最终结果参见下面的截图(参见本书代码包中的mpmath_linalg.ipynb文件):

How to do it...

另见

五、网络挖掘、数据库和大数据

本章菜单上有以下食谱:

  • 模拟网络浏览
  • 刮网
  • 处理非 ASCII 文本和 HTML 实体
  • 实现关联表
  • 设置数据库迁移脚本
  • 向现有表中添加表列
  • 创建表后添加索引
  • 设置测试 web 服务器
  • 用事实表和维度表实现星型模式
  • 利用 HDFS
  • 设置火花
  • 使用 Spark 对数据进行聚类

简介

这一章侧重于数学,但更侧重于技术主题。科技可以为数据分析师提供很多东西。数据库已经存在了一段时间,但是大多数人熟悉的关系数据库可以追溯到 20 世纪 70 年代。埃德加·科德提出了许多想法,这些想法后来导致了关系模型和 SQL 的创建。从那以后,关系数据库一直是一项主导技术。在 20 世纪 80 年代,面向对象的编程语言引起了范式的转变,并不幸与关系数据库不匹配。

面向对象编程语言支持继承等概念,而关系数据库和 SQL 不支持这些概念(当然也有一些例外)。Python 生态系统有几个试图解决这种不匹配问题的对象关系映射 ( ORM )框架。这是不可能的,也没有必要全部覆盖,所以我选择了 SQLAlchemy 作为这里的食谱。我们还将把数据库模式迁移视为一个常见的热门话题,尤其是对于生产系统。

大数据是你可能听说过的流行语之一。Hadoop 和 Spark 可能听起来也很熟悉。我们将在本章中研究这些框架。如果您使用我的 Docker 映像,您将很不幸地在其中找不到 Selenium、Hadoop 和 Spark,因为为了节省空间,我决定不包含它们。

另一个重要的技术发展是万维网,也称为互联网。互联网是最终的数据来源;然而,以易于分析的形式获得这些数据有时是一个相当大的挑战。作为最后的资源,我们可能不得不抓取网页。成功没有保证,因为网站所有者可以在不警告我们的情况下更改内容。这是由你来保持网页抓取食谱的最新代码。

模拟网页浏览

公司网站通常由团队或部门使用专门的工具和模板制作。很多内容是动态生成的,由很大一部分 JavaScript 和 CSS 组成。这意味着即使我们下载了内容,我们仍然至少要评估 JavaScript 代码。我们可以通过 Python 程序做到这一点的一种方法是使用 【硒】应用编程接口。Selenium 的主要目的实际上是测试网站,但没有什么能阻止我们用它来刮网站。

我们将刮除本书代码包中的test_widget.ipynb文件,而不是刮除网站。为了模拟浏览这个网页,我们在test_simulating_browsing.py中提供了一个单元测试类。如果你想知道,这不是测试 IPython 笔记本的推荐方法。

出于历史原因,我更喜欢使用 XPath 来查找 HTML 元素。XPath 是一种查询语言,也适用于 HTML。这不是唯一的方法,您还可以使用 CSS 选择器、标签名或 id。为了找到正确的 XPath 表达式,你可以为你最喜欢的浏览器安装一个相关的插件,或者在谷歌浏览器中,你可以检查一个元素的 XPath。

做好准备

使用以下命令安装硒:

$ pip install selenium

我用 Selenium 2.47.1 测试了代码。

怎么做…

以下步骤向您展示了如何使用我制作的 IPython 小部件模拟网页浏览。该配方的代码在本书代码包的test_simulating_browsing.py文件中:

  1. 第一步是运行以下内容:

    $ ipython notebook
    
    
  2. 进口情况如下:

    from selenium import webdriver
    import time
    import unittest
    import dautil as dl
    
    NAP_SECS = 10
    
  3. 定义以下函数,创建一个火狐浏览器实例:

    class SeleniumTest(unittest.TestCase):
        def setUp(self):
            self.logger = dl.log_api.conf_logger(__name__)
            self.browser = webdriver.Firefox()
    
  4. 定义测试完成后要清理的以下功能:

        def tearDown(self):
            self.browser.quit()
    
  5. 以下功能点击小部件标签(我们必须等待用户界面响应):

    def wait_and_click(self, toggle, text):
            xpath = "//a[@data-toggle='{0}' and contains(text(), '{1}')]"
            xpath = xpath.format(toggle, text)
            elem = dl.web.wait_browser(self.browser, xpath)
            elem.click()
    
  6. 定义以下函数,该函数执行测试,包括评估笔记本单元格并单击 IPython 小部件中的几个选项卡(我们使用端口 8888):

        def test_widget(self):
            self.browser.implicitly_wait(NAP_SECS)
            self.browser.get('http://localhost:8888/notebooks/test_widget.ipynb')
    
            try:
                # Cell menu
                xpath = '//*[@id="menus"]/div/div/ul/li[5]/a'
                link = dl.web.wait_browser(self.browser, xpath)
                link.click()
                time.sleep(1)
    
                # Run all
                xpath = '//*[@id="run_all_cells"]/a'
                link = dl.web.wait_browser(self.browser, xpath)
                link.click()
                time.sleep(1)
    
                self.wait_and_click('tab', 'Figure')
                self.wait_and_click('collapse', 'figure.figsize')
            except Exception:
                self.logger.warning('Error while waiting to click', exc_info=True)
                self.browser.quit()
    
            time.sleep(NAP_SECS)
            self.browser.save_screenshot('widgets_screenshot.png')
    
    if __name__ == "__main__":
        unittest.main()
    

以下截图由代码创建:

How to do it…

另见

刮网

我们知道搜索引擎发出被称为机器人的自主程序,在互联网上寻找信息。通常,这导致创建类似于电话簿或字典的巨大索引。Python 3 用户目前的情况(2015 年 9 月)在刮网方面并不理想。大多数框架只支持 Python 2。然而,圭多·范罗森斯,一生的仁慈独裁者 ( BDFL )刚刚在 GitHub 上贡献了一个使用 AsyncIO API 的爬虫。向 BDFL 致敬!

为了保存抓取的网址,我分叉了存储库并做了一些小的修改。我也让爬虫提前退出了。这些变化不是很优雅,但这是我在有限的时间内所能做的。无论如何,我不能指望比 BDFL 本人做得更好。

一旦我们有了网页链接列表,我们将从 Selenium 加载这些网页(参考模拟网页浏览食谱)。我选择了 PhantomJS,一款无头浏览器,应该比 Firefox 占用空间更小。虽然这不是绝对必要的,但我认为有时下载你正在抓取的网页是有意义的,因为你可以在本地测试抓取。您还可以更改下载的 HTML 中的链接,以指向本地文件。这与设置测试网络服务器配方有关。抓取的一个常见用例是创建一个用于语言分析的文本语料库。这是我们在这个食谱中的目标。

做好准备

按照模拟网页浏览食谱所述安装硒。我在这个食谱中使用了 PhantomJS,但这并不是硬性要求。您可以使用 Selenium 支持的任何其他浏览器。我的修改在https://github.com/ivanidris/500lines/releases的 0.0.1 标签下(2015 年 9 月检索)。下载其中一个源档案并将其解压缩。导航至crawler目录及其code子目录。

使用以下命令启动(可选步骤)爬虫(我以 CNN 为例):

$ python crawl.py edition.cnn.com

怎么做…

您可以在本书的代码包中使用带有链接的 CSV 文件,也可以按照我在上一节中解释的那样自己制作。以下过程描述了如何创建新闻文章的文本语料库(参见本书代码包中的download_html.py文件):

  1. 进口情况如下:

    import dautil as dl
    import csv
    import os
    from selenium import webdriver
    from selenium.webdriver.support.ui import WebDriverWait
    from selenium.webdriver.support import expected_conditions as EC
    from selenium.webdriver.common.by import By
    import urllib.parse as urlparse
    import urllib.request as urlrequest
    
  2. 定义以下全局常数:

    LOGGER = dl.log_api.conf_logger('download_html')
    DRIVER = webdriver.PhantomJS()
    NAP_SECONDS = 10
    
  3. 定义以下函数从一个 HTML 页面中提取文本并保存:

    def write_text(fname):
        elems = []
    
        try:
            DRIVER.get(dl.web.path2url(fname))
    
            elems = WebDriverWait(DRIVER, NAP_SECONDS).until(
                EC.presence_of_all_elements_located((By.XPATH, '//p'))
            )
    
            LOGGER.info('Elems', elems)
    
            with open(fname.replace('.html', '_phantomjs.html'), 'w') as pjs_file:
                LOGGER.warning('Writing to %s', pjs_file.name)
                pjs_file.write(DRIVER.page_source)
    
        except Exception:
            LOGGER.error("Error processing HTML", exc_info=True)
    
        new_name = fname.replace('html', 'txt')
    
        if not os.path.exists(new_name):
            with open(new_name, 'w') as txt_file:
                LOGGER.warning('Writing to %s', txt_file.name)
    
                lines = [e.text for e in elems]
                LOGGER.info('lines', lines)
                txt_file.write(' \n'.join(lines))
    
  4. 定义如下main()函数,读取带有链接的 CSV 文件,调用前面步骤中的函数:

    def main():
        filedir = os.path.join(dl.data.get_data_dir(), 'edition.cnn.com')
    
        with open('saved_urls.csv') as csvfile:
            reader = csv.reader(csvfile)
    
            for line in reader:
                timestamp, count, basename, url = line
                fname = '_'.join([count, basename])
                fname = os.path.join(filedir, fname)
    
                if not os.path.exists(fname):
                    dl.data.download(url, fname)
    
                write_text(fname)
    
    if __name__ == '__main__':
        DRIVER.implicitly_wait(NAP_SECONDS)
        main()
        DRIVER.quit()
    

处理非 ASCII 文本和 HTML 实体

HTML 不像来自数据库查询或 Pandas 的数据那样结构化。你可能会被用正则表达式或字符串函数操纵 HTML。然而,这种方法只在有限的情况下有效。您最好使用专门的 Python 库来处理 HTML。在这个食谱中,我们将使用 lxml 库的clean_html()功能。这个函数从一个网页中剥离所有的 JavaScript 和 CSS。

美国信息交换标准码 ( ASCII )在 2007 年底之前一直是互联网上的主导编码标准,UTF-8 (8 位 Unicode)占据首位。ASCII 仅限于英文字母,不支持不同语言的字母。Unicode 对字母有更广泛的支持。但是,我们有时需要将自己限制在 ASCII 中,所以这个食谱为您提供了一个如何忽略非 ASCII 字符的示例。

做好准备

使用 pip 或 conda 安装 lxml,如下所示:

$ pip install lxml
$ conda install lxml

我用 Anaconda 的 lxml 3.4.2 测试了代码。

怎么做…

代码在本书的代码包中的processing_html.py文件中,分为以下步骤:

  1. 进口情况如下:

    from lxml.html.clean import clean_html
    from difflib import Differ
    import unicodedata
    import dautil as dl
    
    PRINT = dl.log_api.Printer()
    
  2. 定义以下函数来区分两个文件:

    def diff_files(text, cleaned):
        d = Differ()
        diff = list(d.compare(text.splitlines(keepends=True),
                              cleaned.splitlines(keepends=True)))
        PRINT.print(diff)
    
  3. 以下代码块打开一个 HTML 文件,对其进行清理,并将清理后的文件与原始文件进行比较:

    with open('460_cc_phantomjs.html') as html_file:
        text = html_file.read()
        cleaned = clean_html(text)
        diff_files(text, cleaned)
        PRINT.print(dl.web.find_hrefs(cleaned))
    
  4. 下面的代码片段演示了对非 ASCII 文本的处理:

    bulgarian = 'Питон is Bulgarian for Python'
    PRINT.print('Bulgarian', bulgarian)
    PRINT.print('Bulgarian ignored', unicodedata.normalize('NFKD', bulgarian).encode('ascii', 'ignore'))
    

最终结果请参考下面的截图(为了简洁起见,我省略了一些输出):

How to do it…

另见

实现关联表

关联表充当数据库表之间的桥梁,数据库表具有多对多的关系。该表包含与它所连接的表的主键相链接的外键。

在这个食谱中,我们将把网页和网页内的链接联系起来。一个页面有很多链接,链接可以在很多页面中。我们将只关注其他网站的链接,但这不是必需的。如果您试图在本地机器上复制一个网站进行测试或分析,那么您还需要存储图像和 JavaScript 链接。看看下面的关系模式图:

Implementing association tables

做好准备

我用 Anaconda 安装了 SQLAlchemy 0.9.9,如下所示:

$ conda install sqlalchemy

如果愿意,也可以使用以下命令安装 SQLAlchemy:

$ pip install sqlalchemy

怎么做…

本书代码包中impl_association.py文件的以下代码实现了关联表模式:

  1. 进口情况如下:

    from sqlalchemy import create_engine
    from sqlalchemy import Column
    from sqlalchemy import ForeignKey
    from sqlalchemy import Integer
    from sqlalchemy import String
    from sqlalchemy import Table
    from sqlalchemy.orm import backref
    from sqlalchemy.orm import relationship
    from sqlalchemy.ext.declarative import declarative_base
    from sqlalchemy.orm import sessionmaker
    from sqlalchemy.exc import IntegrityError
    import dautil as dl
    import os
    
    Base = declarative_base()
    
  2. 定义以下类来表示网页:

    class Page(Base):
        __tablename__ = 'pages'
        id = Column(Integer, primary_key=True)
        filename = Column(String, nullable=False, unique=True)
        links = relationship('Link', secondary='page_links')
    
        def __repr__(self):
            return "Id=%d filename=%s" %(self.id, self.filename)
    
  3. 定义以下类来表示网络链接:

    class Link(Base):
        __tablename__ = 'links'
        id = Column(Integer, primary_key=True)
        url = Column(String, nullable=False, unique=True)
    
        def __repr__(self):
            return "Id=%d url=%s" %(self.id, self.url)
    
  4. 定义以下类来表示页面和链接之间的关联:

    class PageLink(Base):
        __tablename__ = 'page_links'
        page_id = Column(Integer, ForeignKey('pages.id'), primary_key=True)
        link_id = Column(Integer, ForeignKey('links.id'), primary_key=True)
        page = relationship('Page', backref=backref('link_assoc'))
        link = relationship('Link', backref=backref('page_assoc'))
    
        def __repr__(self):
            return "page_id=%s link_id=%s" %(self.page_id, self.link_id)
    
  5. 定义以下功能来浏览 HTML 文件并更新相关表格:

    def process_file(fname, session):
        with open(fname) as html_file:
            text = html_file.read()
    
            if dl.db.count_where(session, Page.filename, fname):
                # Cowardly refusing to continue
                return
    
            page = Page(filename=fname)
            hrefs = dl.web.find_hrefs(text)
    
            for href in set(hrefs):
                # Only saving http links
                if href.startswith('http'):
                    if dl.db.count_where(session, Link.url, href):
                        continue
    
                    link = Link(url=href)
                    session.add(PageLink(page=page, link=link))
    
            session.commit()
    
  6. 定义以下函数来填充数据库:

    def populate():
        dir = dl.data.get_data_dir()
        path = os.path.join(dir, 'crawled_pages.db')
        engine = create_engine('sqlite:///' + path)
        DBSession = sessionmaker(bind=engine)
        Base.metadata.create_all(engine)
        session = DBSession()
    
        files  = ['460_cc_phantomjs.html', '468_live_phantomjs.html']
    
        for file in files:
            process_file(file, session)
    
        return session
    
  7. 下面的代码片段使用了我们定义的函数和类:

    if __name__ == "__main__":
        session = populate()
        printer = dl.log_api.Printer(nelems=3)
        pages = session.query(Page).all()
        printer.print('Pages', pages)
    
        links = session.query(Link).all()
        printer.print('Links', links)
    
        page_links = session.query(PageLink).all()
        printer.print('PageLinks', page_links)
    

最终结果参见下面的截图:

How to do it…

设置数据库迁移脚本

你在编程课上学到的第一件事是,没有人能第一时间把复杂的程序做对。软件随着时间的推移而发展,我们希望最好的。自动化测试中的自动化有助于确保我们的程序随着时间的推移而改进。然而,当谈到进化数据库模式时,自动化似乎并不那么明显。尤其是在大型企业中,数据库模式是数据库管理员和专家的领域。当然,还有与更改模式相关的安全和操作问题,在生产数据库中更是如此。在任何情况下,您都可以在本地测试环境中实现数据库迁移,并为生产团队记录建议的更改。

我们将使用 Alembic 来演示如何着手设置迁移脚本。在我看来,Alembic 是这项工作的合适工具,尽管它截至 2015 年 9 月仍处于测试阶段。

做好准备

使用以下命令安装 Alembic:

$ pip install alembic

Alembic 依赖于 SQLAlchemy,如果需要,它会自动安装 SQLAlchemy。你现在应该有alembic命令在你的路径上。这一章我用了 Alembic 0.8.2。

怎么做…

以下步骤描述了如何设置一个迁移步骤。当我们在目录中运行 Alembic 初始化脚本时,它会创建一个alembic目录和一个名为alembic.ini的配置文件:

  1. 导航到适当的目录并初始化迁移项目,如下所示:

    $ alembic init alembic
    
    
  2. 根据需要编辑alembic.ini文件。例如,更改sqlalchemy.url属性以指向正确的数据库。

另见

向现有表格添加表格列

如果我们使用对象关系映射器(ORM),比如 SQLAlchemy,我们将类映射到表,将类属性映射到表列。通常,由于新的业务需求,我们需要添加一个表列和相应的类属性。我们可能需要在添加后立即填充该列。

如果我们处理生产数据库,那么您可能没有直接访问权限。幸运的是,我们可以用 Alembic 生成 SQL,数据库管理员可以查看它。

做好准备

参考设置数据库迁移脚本配方。

怎么做…

Alembic 有自己的版本控制系统,需要额外的表。它还用生成的 Python 代码文件在alembic目录下创建一个versions目录。我们需要在这些文件中指定迁移所需的更改类型:

  1. 创建新版本,如下所示:

    $ alembic revision -m "Add a column"
    
    
  2. 打开生成的 Python 文件(例如27218d73000_add_a_column.py)。用下面的代码替换其中的两个函数,增加link_type字符串列:

    def upgrade():
        # MODIFIED Ivan Idris
        op.add_column('links', sa.Column('link_type', sa.String(20)))
    
    def downgrade():
        # MODIFIED Ivan Idris
        op.drop_column('links', 'link_type')
    
  3. 生成 SQL,如下所示:

    $ alembic upgrade head --sql
    
    

有关最终结果,请参考以下屏幕截图:

How to do it…

创建表后添加索引

指数是计算中的一个通用概念。这本书还有一个索引,可以更快地查找,将概念与页码匹配起来。索引占用空间;就这本书而言,几页。数据库索引还有一个额外的缺点,由于更新索引的额外开销,它们会使插入和更新变得更慢。通常,主键和外键会自动获得索引,但这取决于数据库实现。

添加索引不能掉以轻心,最好在咨询数据库管理员后进行。Alembic 的索引添加功能类似于我们在中看到的功能,将一个表列添加到现有的表配方中。

做好准备

参考设置数据库迁移脚本配方。

怎么做…

这个配方和在现有的表格配方上增加一个表格列有一些重叠,所以我就不重复所有的细节了:

  1. 创建新版本,如下所示:

    $ alembic revision -m "Add indices"
    
    
  2. 打开生成的 Python 文件(例如21579ecccd8_add_indices.py)并修改代码,使其具有以下功能,这些功能负责添加索引:

    def upgrade():
        # MODIFIED Ivan Idris
        op.create_index('idx_links_url', 'links', ['url'])
        op.create_index('idx_pages_filename', 'pages', ['filename'])
    
    def downgrade():
        # MODIFIED Ivan Idris
        op.drop_index('idx_links_url')
        op.drop_index('idx_pages_filename')
    
  3. 生成 SQL,如下所示:

    $ alembic upgrade head --sql
    
    

最终结果参见下面的截图:

How to do it…

它是如何工作的…

create_index()函数添加给定索引名、表和表列列表的索引。drop_index()函数则相反,移除给定索引名的索引。

另见

设置测试网络服务器

第 1 章为可再现数据分析奠定基础中,我们讨论了为什么单元测试是一个好主意。纯粹主义者会告诉你,你只需要单元测试。然而,普遍的共识是,更高级别的测试也是有用的。

显然,这本书是关于数据分析的,而不是关于 web 开发的。不过,通过网站或网络服务分享你的结果或数据是一个常见的要求。当你挖掘网络或者做其他与网络相关的事情时,经常需要重现某些用例,比如登录表单。正如您对成熟语言的期望,Python 有许多优秀的网络框架。我选择了 Flask,这是一个简单的 Pythonic 网络框架,因为它看起来很容易设置,但是你应该使用你自己的判断,因为我不知道你的要求是什么。

做好准备

我用 Anaconda 的 Flask 0.10.1 测试了代码。用condapip安装烧瓶,如下所示:

$ conda install flask
$ pip install flask

怎么做…

在这个食谱中,我们将设置一个带有登录表单的安全页面,您可以使用它进行测试。代码由app.py Python 文件和templates目录下的 HTML 文件组成(我就不详细讨论 HTML 了):

  1. 进口情况如下:

    from flask import Flask
    from flask import render_template
    from flask import request
    from flask import redirect
    from flask import url_for
    
    app = Flask(__name__)
    
  2. 定义以下功能来处理主页请求:

    @app.route('/')
    def home():
        return "Test Site"
    
  3. 定义以下功能来处理登录尝试:

    @app.route('/secure', methods=['GET', 'POST'])
    def login():
        error = None
        if request.method == 'POST':
            if request.form['username'] != 'admin' or\
                    request.form['password'] != 'admin':
                error = 'Invalid password or user name.'
            else:
                return redirect(url_for('home'))
        return render_template('admin.html', error=error)
    
  4. 以下块运行服务器(不要将debug=True用于面向公众的网站):

    if __name__ == '__main__':
        app.run(debug=True)
    
  5. 运行$ python app.py,在http://127.0.0.1:5000/http://127.0.0.1:5000/secure打开网页浏览器。

有关最终结果,请参考以下屏幕截图:

How to do it…

用事实表和维度表实现星型模式

星型模式是一种便于报告的数据库模式。星型模式适用于事件的处理,如网站访问、广告点击或金融交易。事件信息(指标,如温度或购买金额)存储在事实表中,事实表链接到小得多的维度表。星型模式是非规范化的,这将完整性检查的责任放在了应用程序代码上。因此,我们应该只以受控的方式写入数据库。如果使用 SQLAlchemy 进行大容量插入,应该选择核心应用编程接口而不是 ORM 应用编程接口,或者使用直接的 SQL。你可以在http://docs.sqlalchemy.org/en/rel_1_0/faq/performance.html阅读更多原因(2015 年 9 月检索)。

时间是报告中的一个常见维度。例如,我们可以在维度表中存储每日天气测量的日期。对于数据中的每个日期,我们可以保存日期、年份、月份和一年中的某一天。我们可以在处理事件之前预先填充这个表,然后根据需要添加新的日期。如果我们假设只需要维护数据库一个世纪,我们甚至不需要向时间维度表中添加新记录。在这种情况下,我们将在时间维度表中预先填充我们想要支持的范围内的所有可能日期。如果我们正在处理二进制或分类变量,预先填充维度表也是可能的。

在本食谱中,我们将为中描述的直接营销数据实施星型模式。数据在一个直接营销活动的 CSV 文件中。为了简单起见,我们将忽略一些列。作为一个指标,我们将带采购金额的spend列。对于维度,我选择了渠道(电话、网络或多渠道)、邮政编码(农村、郊区或城市)和细分市场(男性、女性或没有电子邮件)。请参考以下实体关系图:

Implementing a star schema with fact and dimension tables

怎么做…

以下代码下载数据,加载到数据库中,然后查询数据库(参考本书代码包中的star_schema.py文件):

  1. 进口情况如下:

    from sqlalchemy import Column
    from sqlalchemy import create_engine
    from sqlalchemy.ext.declarative import declarative_base
    from sqlalchemy import ForeignKey
    from sqlalchemy import Integer
    from sqlalchemy import String
    from sqlalchemy.orm import sessionmaker
    from sqlalchemy import func
    import dautil as dl
    from tabulate import tabulate
    import sqlite3
    import os
    from joblib import Memory
    
    Base = declarative_base()
    memory = Memory(cachedir='.')
    
  2. 定义以下类来表示邮政编码维度:

    class DimZipCode(Base):
        __tablename__ = 'dim_zip_code'
        id = Column(Integer, primary_key=True)
        # Urban, Suburban, or Rural.
        zip_code = Column(String(8), nullable=False, unique=True)
    
  3. 定义以下类来表示线段尺寸:

    class DimSegment(Base):
        __tablename__ = 'dim_segment'
        id = Column(Integer, primary_key=True)
        # Mens E-Mail, Womens E-Mail or No E-Mail
        segment = Column(String(14), nullable=False, unique=True)
    
  4. 定义以下类来表示通道维度:

    class DimChannel(Base):
        __tablename__ = 'dim_channel'
        id = Column(Integer, primary_key=True)
        channel = Column(String)
    
  5. 定义以下类来表示事实表:

    class FactSales(Base):
        __tablename__ = 'fact_sales'
        id = Column(Integer, primary_key=True)
        zip_code_id = Column(Integer, ForeignKey('dim_zip_code.id'),
                             primary_key=True)
        segment_id = Column(Integer, ForeignKey('dim_segment.id'),
                            primary_key=True)
        channel_id = Column(Integer, ForeignKey('dim_channel.id'),
                            primary_key=True)
    
        # Storing amount as cents
        spend = Column(Integer)
    
        def __repr__(self):
            return "zip_code_id={0} channel_id={1} segment_id={2}".format(
                self.zip_code_id, self.channel_id, self.segment_id)
    
  6. 定义以下功能创建 SQLAlchemy 会话:

    def create_session(dbname):
        engine = create_engine('sqlite:///{}'.format(dbname))
        DBSession = sessionmaker(bind=engine)
        Base.metadata.create_all(engine)
    
        return DBSession()
    
  7. 定义以下功能来填充段维度表:

    def populate_dim_segment(session):
        options = ['Mens E-Mail', 'Womens E-Mail', 'No E-Mail']
    
        for option in options:
            if not dl.db.count_where(session, DimSegment.segment, option):
                session.add(DimSegment(segment=option))
    
        session.commit()
    
  8. 定义以下函数来填充邮政编码维度表:

    def populate_dim_zip_code(session):
        # Note the interesting spelling
        options = ['Urban', 'Surburban', 'Rural']
    
        for option in options:
            if not dl.db.count_where(session, DimZipCode.zip_code, option):
                session.add(DimZipCode(zip_code=option))
    
        session.commit()
    
  9. 定义以下函数来填充通道维度表:

    def populate_dim_channels(session):
        options = ['Phone', 'Web', 'Multichannel']
    
        for option in options:
            if not dl.db.count_where(session, DimChannel.channel, option):
                session.add(DimChannel(channel=option))
    
        session.commit()
    
  10. 定义以下函数来填充事实表(出于性能原因,它使用直接的 SQL:

```py
def load(csv_rows, session, dbname):
    channels = dl.db.map_to_id(session, DimChannel.channel)
    segments = dl.db.map_to_id(session, DimSegment.segment)
    zip_codes = dl.db.map_to_id(session, DimZipCode.zip_code)
    conn = sqlite3.connect(dbname)
    c = conn.cursor()
    logger = dl.log_api.conf_logger(__name__)

    for i, row in enumerate(csv_rows):
        channel_id = channels[row['channel']]
        segment_id = segments[row['segment']]
        zip_code_id = zip_codes[row['zip_code']]
        spend = dl.data.centify(row['spend'])

        insert = "INSERT INTO fact_sales (id, segment_id,\
            zip_code_id, channel_id, spend) VALUES({id}, \
            {sid}, {zid}, {cid}, {spend})"
        c.execute(insert.format(id=i, sid=segment_id,
                                zid=zip_code_id, cid=channel_id,     spend=spend))

        if i % 1000 == 0:
            logger.info("Progress %s/64000", i)
            conn.commit()

    conn.commit()
    c.close()
    conn.close()
```
  1. 定义以下函数下载并解析数据:
```py
@memory.cache
def get_and_parse():
    out = dl.data.get_direct_marketing_csv()
    return dl.data.read_csv(out)
```
  1. 以下模块使用我们定义的函数和类【T3:
```py
if __name__ == "__main__":
    dbname = os.path.join(dl.data.get_data_dir(), 'marketing.db')
    session = create_session(dbname)
    populate_dim_segment(session)
    populate_dim_zip_code(session)
    populate_dim_channels(session)

    if session.query(FactSales).count() < 64000:
        load(get_and_parse(), session, dbname)

    fsum = func.sum(FactSales.spend)
    query = session.query(DimSegment.segment, DimChannel.channel,
                          DimZipCode.zip_code, fsum)
    dim_cols = (DimSegment.segment, DimChannel.channel, DimZipCode.zip_code)
    dim_entities = [dl.db.entity_from_column(col) for col in dim_cols]
    spend_totals = query.join(FactSales,
                              *dim_entities)\
                        .group_by(*dim_cols).order_by(fsum.desc()).all()
    print(tabulate(spend_totals, tablefmt='psql',
                   headers=['Segment', 'Channel', 'Zip Code', 'Spend']))
```

有关最终结果(以美分为单位的支出金额),请参考以下屏幕截图:

How to do it…

另见

使用 HDFS

Hadoop 分布式文件系统 ( HDFS )是大数据 Hadoop 框架的存储组件。HDFS 是一个分布式文件系统,它在多个系统上传播数据,其灵感来自于谷歌用于其搜索引擎的谷歌文件系统。HDFS 需要一个 Java 运行时环境 ( JRE ),它使用一个NameNode服务器来跟踪文件。系统还会复制数据,这样丢失几个节点不会导致数据丢失。HDFS 的典型用例是处理大型只读文件。Apache Spark 也包含在本章中,它也可以使用 HDFS。

做好准备

安装 Hadoop 和一个 JRE。因为这些不是 Python 框架,所以您必须检查什么程序适合您的操作系统。这个食谱我用的是 Hadoop 2.7.1 和 Java 1.7.0_60。这可能是一个复杂的过程,但网上有许多资源可以帮助您解决特定系统的故障。

怎么做…

我们可以用在你的 Hadoop 安装中找到的几个 XML 文件来配置 HDFS。本节中的一些步骤仅作为示例,您应该根据您的操作系统、环境和个人偏好来实施它们:

  1. 编辑core-site.xml文件,使其具有以下内容(注释省略):

    <?xml version="1.0" encoding="UTF-8"?>
    <?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
    <configuration>
        <property>
            <name>fs.default.name</name>
            <value>hdfs://localhost:8020</value>
        </property>
    </configuration>
    
  2. 编辑hdfs-site.xml文件,使其具有以下内容(注释省略),将每个文件的复制设置为仅 1,以在本地运行 HDFS:

    <?xml version="1.0" encoding="UTF-8"?>
    <?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
    <configuration>
        <property>
            <name>dfs.replication</name>
            <value>1</value>
        </property>
    </configuration>
    
  3. 如有必要,在您的系统上启用远程登录来 SSH 到本地主机并生成密钥(Windows 用户可以使用 putty):

    $ ssh-keygen -t dsa -f ~/.ssh/id_dsa
    $ cat ~/.ssh/id_dsa.pub >> ~/.ssh/authorized_keys
    
    
  4. 从 Hadoop 目录的根目录格式化文件系统:

    $ bin/hdfs namenode –format
    
    
  5. 启动 NameNode 服务器,如下(相反命令为$ sbin/stop-dfs.sh ):

    $ sbin/start-dfs.sh
    
    
  6. 使用以下命令在 HDFS 创建目录:

    $ hadoop fs -mkdir direct_marketing
    
    
  7. 或者,如果您想使用火花配方中的direct_marketing.csv文件,您需要将其复制到 HDFS,如下所示:

    $ hadoop fs -copyFromLocal <path to file>/direct_marketing.csv direct_marketing
    
    

另见

  • 位于 HDFS 用户指南

设置火花

Apache Spark 是 Hadoop 生态系统中的一个项目(参考使用 HDFS 配方),据称其性能优于 Hadoop 的 MapReduce。Spark 尽可能将数据加载到内存中,对机器学习有很好的支持。在用 Spark 配方聚类数据中,我们将通过 Spark 应用机器学习算法。

Spark 可以独立工作,但它被设计为使用 HDFS 与 Hadoop 一起工作。弹性分布式数据集 ( RDDs )是 Spark 的中心结构,代表分布式数据。Spark 对 Scala(一种 JVM 语言)有很好的支持,对 Python 的支持有些滞后。例如,pyspark API 中对流的支持有点滞后。Spark 也有 DataFrames 的概念,但是它不是通过 pandas 实现的,而是通过 Spark 实现的。

做好准备

https://spark.apache.org/downloads.html的下载页面下载星火(2015 年 9 月检索)。我下载了星火 1.5.0 的spark-1.5.0-bin-hadoop2.6.tgz档案。

将归档文件解压缩到适当的目录中。

怎么做…

以下步骤通过几个可选步骤说明了 Spark 的基本设置:

  1. 如果要使用不同于系统 Python 的 Python 版本,请通过操作系统的 GUI 或 CLI 设置PYSPARK_PYTHON环境变量,如下所示:

    $ export PYSPARK_PYTHON=/path/to/anaconda/bin/python
    
    
  2. 设置SPARK_HOME环境变量,如下所示:

    $ export SPARK_HOME=<path/to/spark/>spark-1.5.0-bin-hadoop2.6
    
    
  3. python目录添加到您的PYTHONPATH环境变量中,如下所示:

    $ export PYTHONPATH=$SPARK_HOME/python:$PYTHONPATH
    
    
  4. py4j的 ZIP 添加到您的PYTHONPATH环境变量中,如下所示:

    $ export PYTHONPATH=$SPARK_HOME/python/lib/py4j-0.8.2.1-src.zip:$PYTHONPATH
    
    
  5. 如果 Spark 的日志记录过于冗长,将$SPARK_HOME/conf目录中的log4j.properties.template文件复制到log4j.properties并将 INFO 级别更改为 WARN。

另见

官方星火网站在http://spark.apache.org/(2015 年 9 月检索)

用 Spark 聚类数据

在上一个食谱设置火花中,我们介绍了火花的基本设置。如果你按照使用 HDFS 食谱,你可以选择提供来自 Hadoop 的数据。在这种情况下,您需要以这种方式指定文件的网址,hdfs://hdfs-host:port/path/direct_marketing.csv

我们将使用与我们在中使用的相同的数据,用事实和维度表实现星型模式。但是,这次我们将使用spendhistoryrecency列。第一列对应直销活动后的最近购买金额,第二列对应历史购买金额,第三列对应最近几个月的购买金额。数据描述见http://blog . minethatdata . com/2008/03/minethatdata-e-mail-analytics-and-data . html(2015 年 9 月检索)。我们将应用流行的 K-means 机器学习算法对数据进行聚类。第九章集成学习和降维,更关注机器学习算法。K-means 算法试图为给定数量的聚类的数据集找到最佳聚类。我们应该要么知道这个数字,要么通过反复试验找到它。在本食谱中,我通过集内平方和误差 ( WSSSE )也称为集内平方和 ( WCSS )来评估聚类。该度量计算每个点和其指定簇之间距离的平方误差之和。您可以在第 10 章评估分类器、回归器和聚类中了解更多关于评估指标的信息。

做好准备

按照设置火花配方中的说明进行操作。

怎么做…

该配方的代码在本书代码包的clustering_spark.py文件中:

  1. 进口情况如下:

    from pyspark.mllib.clustering import KMeans
    from pyspark import SparkContext
    import dautil as dl
    import csv
    import matplotlib.pyplot as plt
    import matplotlib as mpl
    from matplotlib.colors import Normalize
    
  2. 定义以下函数计算误差:

    def error(point, clusters):
        center = clusters.centers[clusters.predict(point)]
    
        return dl.stats.wssse(point, center)
    
  3. 读取并解析数据,如下所示:

    sc = SparkContext()
    csv_file = dl.data.get_direct_marketing_csv()
    lines = sc.textFile(csv_file)
    header = lines.first().split(',')
    cols_set = set(['recency', 'history', 'spend'])
    select_cols = [i for i, col in enumerate(header) if col in cols_set]
    
  4. 设置以下 rdd:

    header_rdd = lines.filter(lambda l: 'recency' in l)
    noheader_rdd = lines.subtract(header_rdd)
    temp = noheader_rdd.map(lambda v: list(csv.reader([v]))[0])\
                       .map(lambda p: (int(p[select_cols[0]]),
                            dl.data.centify(p[select_cols[1]]),
                            dl.data.centify(p[select_cols[2]])))
    
    # spend > 0
    temp = temp.filter(lambda x: x[2] > 0)
    
  5. 用 k-means 算法对数据进行聚类:

    points = []
    clusters = None
    
    for i in range(2, 28):
        clusters = KMeans.train(temp, i, maxIterations=10,
                                runs=10, initializationMode="random")
    
        val = temp.map(lambda point: error(point, clusters))\
                  .reduce(lambda x, y: x + y)
        points.append((i, val))
    
  6. 绘制集群,如下所示:

    dl.options.mimic_seaborn()
    fig, [ax, ax2] = plt.subplots(2, 1)
    ax.set_title('k-means Clusters')
    ax.set_xlabel('Number of clusters')
    ax.set_ylabel('WSSSE')
    dl.plotting.plot_points(ax, points)
    
    collected = temp.collect()
    recency, history, spend = zip(*collected)
    indices = [clusters.predict(c) for c in collected]
    ax2.set_title('Clusters for spend, history and recency')
    ax2.set_xlabel('history (cents)')
    ax2.set_ylabel('spend (cents)')
    markers = dl.plotting.map_markers(indices)
    colors = dl.plotting.sample_hex_cmap(name='hot', ncolors=len(set(recency)))
    
    for h, s, r, m in zip(history, spend, recency, markers):
        ax2.scatter(h, s, s=20 + r, marker=m, c=colors[r-1])
    
    cma = mpl.colors.ListedColormap(colors, name='from_list', N=None)
    norm = Normalize(min(recency), max(recency))
    msm = mpl.cm.ScalarMappable(cmap=cma, norm=norm)
    msm.set_array([])
    fig.colorbar(msm, label='Recency')
    
    for i, center in enumerate(clusters.clusterCenters):
        recency, history, spend = center
        ax2.text(history, spend, str(i))
    
    plt.tight_layout()
    plt.show()
    

最终结果见以下截图(图中数字对应聚类中心):

How to do it…

它是如何工作的…

k 均值聚类将数据点分配给 k 个聚类。聚类问题不是直接可以解决的,但是我们可以应用启发式算法,获得一个可接受的结果。k 均值算法在两个步骤之间迭代,不包括 k 均值的初始化(通常是随机的):

  • 给每个数据点分配一个 WCSS 均值最低的聚类
  • 重新计算聚类中心作为聚类点坐标的平均值

当集群分配变得稳定时,算法停止。

还有更多…

Spark 1.5.0 增加了对流 K 均值的实验支持。由于这些新特性的实验性质,我决定不详细讨论它们。我在本书的代码包streaming_clustering.py文件中添加了以下示例代码:

import dautil as dl
from pyspark.mllib.clustering import StreamingKMeansModel
from pyspark import SparkContext

csv_file = dl.data.get_direct_marketing_csv()
csv_rows = dl.data.read_csv(csv_file)

stkm = StreamingKMeansModel(28 * [[0., 0., 0.]], 28 * [1.])
sc = SparkContext()

for row in csv_rows:
    spend = dl.data.centify(row['spend'])

    if spend > 0:
        history = dl.data.centify(row['history'])
        data = sc.parallelize([[int(row['recency']),
                               history, spend]])
        stkm = stkm.update(data, 0., 'points')

print(stkm.centers)

另见

六、信号处理和时间序列

在本章中,我们将介绍以下食谱:

  • 用周期图进行光谱分析
  • 用韦尔奇方法估计功率谱密度
  • 分析峰值
  • 测量相位同步
  • 指数平滑法
  • 评估平滑度
  • 使用隆布-斯卡格尔周期图
  • 分析音频的频谱
  • 用离散余弦变换分析信号
  • 块自举时间序列数据
  • 移动块自举时间序列数据
  • 应用离散小波变换

简介

时间是科学和日常生活中的一个重要维度。时间序列数据非常丰富,需要特殊的技术。通常,我们对趋势和季节性或周期性感兴趣。在数学术语中,这意味着我们试图用(通常是线性的)多项式或三角函数,或两者的组合来表示数据。

当我们研究季节性时,我们通常区分时域和频域分析。在时域,我们可以使用十几个 Pandas 函数来滚动窗口。我们还可以平滑数据以去除噪声,同时希望保持足够的信号。平滑在许多方面类似于拟合,这很方便,因为我们可以重用一些我们知道的回归工具。

为了进入频域,我们应用变换,例如快速傅立叶变换离散余弦变换。然后我们可以用周期图进一步分析信号。

用周期图进行光谱分析

我们可以认为周期性的信号是由多个频率组成的。比如声音是由多种音调组成,光是由多种颜色组成的。频率范围称为频谱。当我们分析一个信号的频谱时,我们很自然地会看到这个信号的傅里叶变换的结果。周期图对此进行了扩展,并等于傅里叶变换的平方幅度,如下所示:

Spectral analysis with periodograms

我们将查看以下变量的周期图:

  • KNMI De Bilt 气象数据中的雨量值
  • 雨量值的二阶差(相当于微积分中的二阶导数)
  • 使用 365 天窗口的雨数值的滚动总和
  • 使用 365 天窗口的雨值的滚动平均值

怎么做...

  1. 进口情况如下:

    from scipy import signal
    import matplotlib.pyplot as plt
    import dautil as dl
    import numpy as np
    import pandas as pd
    from IPython.display import HTML
    
  2. 加载数据如下:

    fs = 365
    rain = dl.data.Weather.load()['RAIN'].dropna()
    
  3. 定义以下函数绘制周期图:

    def plot_periodogram(arr, ax):
        f, Pxx_den = signal.periodogram(arr, fs)
        ax.set_xlabel('Frequency (Hz)')
        ax.set_ylabel('PSD')
        ax.semilogy(f, Pxx_den)
    
  4. 用以下代码绘制周期图:

    sp = dl.plotting.Subplotter(2, 2, context)
    sp.label()
    plot_periodogram(rain, sp.ax)
    sp.label(advance=True)
    plot_periodogram(np.diff(rain, 2), sp.ax)
    sp.label(advance=True)
    plot_periodogram(pd.rolling_sum(rain, fs).dropna(), sp.ax)
    sp.label(advance=True)
    plot_periodogram(pd.rolling_mean(rain, fs).dropna(), sp.ax)
    HTML(sp.exit())
    

有关最终结果,请参考以下屏幕截图:

How to do it...

代码来自本书代码包演示周期图中的periodograms.ipynb文件。

另见

用韦尔奇方法估计功率谱密度

韦尔奇方法是对周期图技术的改进(它降低了噪声),以 P.D .韦尔奇命名。通过以下步骤降低功率谱的噪声:

  1. 我们用固定数量的重叠点分割信号。如果重叠为 0,那么我们有巴特利特方法
  2. 在时域中,我们将窗口函数应用于步骤 1 的每个片段。
  3. 我们计算每个片段的周期图,如光谱分析周期图配方中所述。
  4. 我们对周期图进行平均,从而减少噪音。平均有效地平滑了信号。然而,我们现在处理的是频率仓(就像直方图)。

我们还将探讨 法诺因子,给出如下:

Estimating power spectral density with the Welch method

这是一个加窗的方差均值比。除以平均值基本上将这些值归一化,我们得到一个归一化的离差度量。作为输入数据,我们将使用温度数据。

怎么做...

  1. 进口情况如下:

    from scipy import signal
    import matplotlib.pyplot as plt
    import dautil as dl
    from IPython.display import HTML
    
  2. 加载数据并计算 Fano 因子:

    fs = 365
    temp = dl.data.Weather.load()['TEMP'].dropna()
    fano_factor = dl.ts.fano_factor(temp, fs)
    
  3. 定义以下函数绘制周期图:

    def plot_welch(arr, ax):
        f, Pxx_den = signal.welch(arr, fs)
        ax.semilogy(f, Pxx_den)
    
  4. 绘制输入数据和对应的周期图:

    sp = dl.plotting.Subplotter(2, 2, context)
    temp.plot(ax=sp.ax)
    sp.label(ylabel_params=dl.data.Weather.get_header('TEMP'))
    sp.label(advance=True)
    sp.ax.plot(temp.index, fano_factor)
    sp.label(advance=True)
    plot_welch(temp, sp.ax)
    sp.label(advance=True)
    plot_welch(fano_factor.dropna(), sp.ax)
    HTML(sp.exit())
    

最终结果参见以下截图:

How to do it...

代码在本书代码包的estimating_welch.ipynb文件中。

另见

分析峰值

峰的分析与谷的分析相似,因为两者都是极值。SciPy 具有找到相对最大值的argrelmax()函数。当我们将此函数应用于每日温度值时,它不仅会发现夏季的热天,还会发现冬季的热天,除非我们使该函数考虑更大的时间范围。当然,我们也可以检查数值是否高于阈值,或者仅使用先验知识选择夏季数据。

当我们分析时间序列数据中的峰值时,我们可以应用两种方法。第一种方法是考虑一年、一个月或另一个固定时间间隔内的最高峰值,并用这些值构建一个序列。第二种方法是将任何高于阈值的值定义为峰值。在这个食谱中,我们将使用第 95 个百分位数作为阈值。在这种方法中,我们可以在一个序列中有多个峰值。例如,在热浪的情况下,长条纹会产生负面影响。

怎么做...

  1. 进口情况如下:

    import dautil as dl
    from scipy import signal
    import matplotlib.pyplot as plt
    import seaborn as sns
    from IPython.display import HTML
    
  2. 加载并重新采样数据:

    temp = dl.data.Weather.load()['TEMP'].dropna()
    monthly = temp.resample('M')
    
  3. 绘制峰值,注意还考虑了冬季的热天:

    sp = dl.plotting.Subplotter(2, 2, context)
    max_locs = signal.argrelmax(monthly.values)
    sp.ax.plot(monthly.index, monthly, label='Monthly means')
    sp.ax.plot(monthly.index[max_locs], monthly.values[max_locs], 
               'o', label='Tops')
    sp.label(ylabel_params=dl.data.Weather.get_header('TEMP'))
    
  4. 绘制年度最大系列:

    annual_max = dl.ts.groupby_year(temp).max()
    sp.next_ax().plot(annual_max.index, annual_max, label='Annual Maximum Series')
    dl.plotting.plot_polyfit(sp.ax, annual_max.index, annual_max.values)
    sp.label(ylabel_params=dl.data.Weather.get_header('TEMP'))
    
  5. 绘制超过第 95 百分位阈值的全年最长高温天数:

    _, threshhold = dl.stats.outliers(temp, method='percentiles')
    over_threshhold = temp > threshhold
    streaks = dl.ts.groupby_year(over_threshhold).apply(
        lambda x: dl.collect.longest_streak(x, 1))
    sp.next_ax().plot(streaks.index, streaks)
    dl.plotting.plot_polyfit(sp.ax, streaks.index, streaks.values)
    over_threshhold = dl.ts.groupby_year(over_threshhold).mean()
    sp.label()
    
  6. 绘制年度最大系列分布:

    sp.label(advance=True)
    sns.distplot(annual_max, ax=sp.ax)
    sp.label(xlabel_params=dl.data.Weather.get_header('TEMP'))
    HTML(sp.exit())
    

有关最终结果,请参考以下屏幕截图:

How to do it...

代码在本书代码包的 analyzing_peaks.ipynb文件中。

另见

测量相位同步

两个信号可以完全同步,也可以不同步,或者介于两者之间。我们通常以弧度测量相位同步瞬时相位的相关量可以用 NumPy angle()功能测量。对于实值数据,我们需要获得信号的解析表示,它由希尔伯特变换给出。希尔伯特变换也有 SciPy 和 NumPy 版本。

互相关使用滑动内积测量两个信号之间的相关性。我们可以使用互相关来测量两个信号之间的时间延迟。NumPy 提供correlate()函数,计算两个数组之间的互相关。

怎么做...

  1. 进口情况如下:

    import dautil as dl
    import matplotlib.pyplot as plt
    import numpy as np
    from IPython.display import HTML
    
  2. 加载数据,计算瞬时相位:

    df = dl.data.Weather.load().dropna()
    df = dl.ts.groupby_yday(df).mean().dropna()
    ws_phase = dl.ts.instant_phase(df['WIND_SPEED'])
    wd_phase = dl.ts.instant_phase(df['WIND_DIR'])
    
  3. 绘制风向和风速 z 分数:

    sp = dl.plotting.Subplotter(2, 2, context)
    cp = dl.plotting.CyclePlotter(sp.ax)
    cp.plot(df.index, dl.stats.zscores(df['WIND_DIR'].values),
           label='Wind direction')
    cp.plot(df.index, dl.stats.zscores(df['WIND_SPEED'].values),
           label='Wind speed')
    sp.label()
    
  4. 将瞬时相位绘制如下:

    cp = dl.plotting.CyclePlotter(sp.next_ax())
    cp.plot(df.index, ws_phase, label='Wind speed')
    cp.plot(df.index, wd_phase, label='Wind direction')
    sp.label()
    
  5. 绘制风速和风向的相关性:

    sp.label(advance=True)
    sp.ax.plot(np.correlate(df['WIND_SPEED'], df['WIND_DIR'], 'same'))
    
  6. 用快速傅里叶变换绘制相移图:

    sp.label(advance=True)
    sp.ax.plot(np.angle(np.fft.fft(df['WIND_SPEED'])/np.fft.fft(df['WIND_DIR'])))
    HTML(sp.exit())
    

有关最终结果,请参考以下屏幕截图:

How to do it...

本食谱的代码在本书代码包的 phase_synchrony.ipynb文件中。

另见

指数平滑

指数平滑是一个低通滤波器,旨在去除噪声。在这个配方中,我们将应用单指数平滑和双指数平滑,如下式所示:

Exponential smoothing

单指数平滑(6.3)需要 平滑因子 α ,其中 0 < α < 1 。双指数平滑(6.4 和 6.5)试图通过 趋势平滑因子 β 处理数据中的趋势,其中 0 < β < 1

我们还将研究风速的滚动偏差,这类似于 z 分数,但它们适用于滚动窗口。平滑与回归相关,虽然平滑的目标是去噪。然而,与回归相关的指标,如均方误差 ( 均方误差)也适用于平滑。

怎么做...

  1. 进口情况如下:

    import dautil as dl
    import pandas as pd
    import matplotlib.pyplot as plt
    import numpy as np
    import seaborn as sns
    from IPython.display import HTML
    
  2. 定义以下函数,帮助可视化双指数平滑的结果:

    def grid_mse(i, j, devs):
        alpha = 0.1 * i
        beta = 0.1 * j
        cell = dl.ts.double_exp_smoothing(devs.values, alpha, beta)
    
        return dl.stats.mse(devs, cell)
    
  3. 加载风速数据,计算年平均值和滚动偏差:

    wind = dl.data.Weather.load()['WIND_SPEED'].dropna()
    wind = dl.ts.groupby_year(wind).mean()
    devs = dl.ts.rolling_deviations(wind, 12).dropna()
    
  4. 绘制风速数据的年平均值:

    sp = dl.plotting.Subplotter(2, 2, context)
    sp.label(ylabel_params=dl.data.Weather.get_header('WIND_SPEED'))
    sp.ax.plot(wind.index, wind)
    
  5. 用 0.7 的 α 绘制滚动偏差:

    cp = dl.plotting.CyclePlotter(sp.next_ax())
    cp.plot(devs.index, devs, label='Rolling Deviations')
    cp.plot(devs.index, dl.ts.exp_smoothing(devs.values, 0.7), label='Smoothing')
    sp.label()
    
  6. 绘制不同平滑因子的均方误差:

    alphas = 0.01 * np.arange(1, 100)
    errors = [dl.stats.mse(devs, dl.ts.exp_smoothing(devs.values, alpha)
              for alpha in alphas]
    sp.label(advance=True)
    sp.ax.plot(alphas, errors)
    
  7. 绘制 αβ 值网格的均方误差:

    sp.label(advance=True)
    rng = range(1, 10)
    df = dl.report.map_grid(rng, rng, ["alpha", "beta", "mse"], grid_mse, devs) 
    sns.heatmap(df, cmap='Blues', square=True, annot=True, fmt='.1f',
                ax=sp.ax)
    
    HTML(sp.exit())
    

有关最终结果,请参考以下屏幕截图:

How to do it...

代码在本书代码包的 exp_smoothing.ipynb文件中。

另见

评估平滑度

平滑的很多方面都堪比回归;因此,您也可以将第 10 章评估分类器、回归器和聚类中的一些技术应用于平滑。在本食谱中,我们将使用Savitzky-Golay 滤波器进行平滑,该滤波器符合以下等式:

Evaluating smoothing

该滤波器将大小为 n 的滚动窗口内的点拟合为一个数量级为 m 的多项式。亚伯拉罕·萨维奇基和马塞尔·戈雷在 1964 年左右创造了这种算法,并首次将其应用于化学问题。过滤器有两个自然形成网格的参数。就像在回归问题中一样,我们将看一看差异,在这种情况下,原始信号和平滑信号之间的差异。我们假设,就像我们拟合数据一样,残差是随机的,并且遵循高斯分布。

怎么做...

以下步骤来自本书代码包中的eval_smooth.ipynb文件:

  1. 进口情况如下:

    import dautil as dl
    import matplotlib.pyplot as plt
    from scipy.signal import savgol_filter
    import pandas as pd
    import numpy as np
    import seaborn as sns
    from IPython.display import HTML
    
  2. 定义以下助手函数:

    def error(data, fit):
        return data - fit
    
    def win_rng():
        return range(3, 25, 2)
    
    def calc_mape(i, j, pres):
        return dl.stats.mape(pres, savgol_filter(pres, i, j))
    
  3. 加载大气压力数据如下:

    pres = dl.data.Weather.load()['PRESSURE'].dropna()
    pres = pres.resample('A')
    
  4. 绘制原始数据和具有窗口大小 11 和各种多项式阶次的过滤器:

    sp = dl.plotting.Subplotter(2, 2, context)
    cp = dl.plotting.CyclePlotter(sp.ax)
    cp.plot(pres.index, pres, label='Pressure')
    cp.plot(pres.index, savgol_filter(pres, 11, 2), label='Poly order 2')
    cp.plot(pres.index, savgol_filter(pres, 11, 3), label='Poly order 3')
    cp.plot(pres.index, savgol_filter(pres, 11, 4), label='Poly order 4')
    sp.label(ylabel_params=dl.data.Weather.get_header('PRESSURE'))
    
  5. 绘制不同窗口大小的滤波器残差的标准偏差:

    cp = dl.plotting.CyclePlotter(sp.next_ax())
    stds = [error(pres, savgol_filter(pres, i, 2)).std()
            for i in win_rng()]
    cp.plot(win_rng(), stds, label='Filtered')
    stds = [error(pres, pd.rolling_mean(pres, i)).std()
            for i in win_rng()]
    cp.plot(win_rng(), stds, label='Rolling mean')
    sp.label()
    
  6. 绘制滤波残差的箱线图:

    sp.label(advance=True)
    sp.ax.boxplot([error(pres, savgol_filter(pres, i, 2))
                for i in win_rng()])
    sp.ax.set_xticklabels(win_rng())
    
  7. 绘制窗口大小和多项式阶数网格的 MAPE 图:

    sp.label(advance=True)
    df = dl.report.map_grid(win_rng()[1:], range(1, 5),
                     ['win_size', 'poly', 'mape'], calc_mape, pres)
    sns.heatmap(df, cmap='Blues', ax=sp.ax)
    HTML(sp.exit())
    

最终结果参见以下截图:

How to do it...

另见

使用隆布-斯卡格尔周期图

隆布-斯卡格尔周期图是一种对数据进行正弦拟合的频谱估计方法,经常用于采样不均匀的数据。方法以尼古拉斯·r·隆布和杰弗里·d·斯卡格尔的名字命名。该算法发表于 1976 年左右,此后一直在改进。Scargle 引入了一个时间延迟参数,用于分离正弦和余弦波形。以下等式定义了时间延迟(6.7)和周期图(6.8)。

Using the Lomb-Scargle periodogram

怎么做...

  1. 进口情况如下:

    from scipy import signal
    import numpy as np
    import matplotlib.pyplot as plt
    import dautil as dl
    import statsmodels.api as sm
    from IPython.display import HTML
    
  2. 加载太阳黑子数据如下:

    df = sm.datasets.sunspots.load_pandas().data
    sunspots = df['SUNACTIVITY'].values
    size = len(sunspots)
    t = np.linspace(-2 * np.pi, 2 * np.pi, size)
    sine = dl.ts.sine_like(sunspots)
    f = np.linspace(0.01, 2, 10 * size)
    
  3. 绘制如下正弦波形:

    sp = dl.plotting.Subplotter(2, 2, context)
    sp.ax.plot(t, sine)
    sp.label()
    
    sp.next_ax().plot(df['YEAR'], df['SUNACTIVITY'])
    sp.label()
    
  4. 将周期图应用于正弦:

    pgram = signal.lombscargle(t, sine, f)
    sp.next_ax().plot(f, 2 * np.sqrt(pgram/size))
    sp.label()
    
  5. 将周期图应用于太阳黑子数据:

    pgram = signal.lombscargle(np.arange(size, dtype=float), sunspots, f)
    sp.next_ax().plot(f, 2 * np.sqrt(pgram/size))
    sp.label()
    HTML(sp.exit())
    

有关最终结果,请参考以下屏幕截图:

How to do it...

前面的代码是本书代码包中lomb_scargle.ipynb文件的分解。

另见

分析音频频谱

我们可以应用许多技术来分析音频,因此,我们可以详细讨论哪些技术最合适。最明显的方法据称是快速傅立叶变换。作为变体,我们可以使用 短时傅立叶变换 ( STFT )。STFT 将时域中的信号分成相等的部分,然后对每个部分应用快速傅立叶变换。我们将使用的另一种算法是倒谱,最初用于分析地震,但后来成功应用于语音分析。功率倒谱由下式给出:

Analyzing the frequency spectrum of audio

算法如下:

  1. 计算傅里叶变换。
  2. 计算变换的平方幅度。
  3. 取上一个结果的对数。
  4. 应用傅里叶逆变换。
  5. 再次计算大小的平方。

通常,当我们在频域中有大的变化时,倒谱是有用的。倒谱的一个重要用途是形成音频分类的特征向量。这需要从频率到 mel 标度的映射(参考中提到的维基百科页面,另请参见部分)。

怎么做...

  1. 进口情况如下:

    import dautil as dl
    import matplotlib.pyplot as plt
    import numpy as np
    from ch6util import read_wav
    from IPython.display import HTML
    
  2. 定义以下功能,通过快速傅立叶变换计算信号幅度:

    def amplitude(arr):
        return np.abs(np.fft.fft(arr))
    
  3. 加载数据如下:

    rate, audio = read_wav()
    
  4. 绘制音频波形:

    sp = dl.plotting.Subplotter(2, 2, context)
    t = np.arange(0, len(audio)/float(rate), 1./rate)
    sp.ax.plot(t, audio)
    freqs = np.fft.fftfreq(audio.size, 1./rate)
    indices = np.where(freqs > 0)[0]
    sp.label()
    
  5. 绘制振幅谱:

    magnitude = amplitude(audio)
    sp.next_ax().semilogy(freqs[indices], magnitude[indices])
    sp.label()
    
  6. 将倒谱绘制如下:

    cepstrum = dl.ts.power(np.fft.ifft(np.log(magnitude ** 2)))
    sp.next_ax().semilogy(cepstrum)
    sp.label()
    
  7. 将 STFT 绘制成等高线图:

    npieces = 200
    stft_amps = []
    
    for i, c in enumerate(dl.collect.chunk(audio[: npieces ** 2], len(audio)/npieces)):
        amps = amplitude(c)
        stft_amps.extend(amps)
    
    stft_freqs = np.linspace(0, rate, npieces)
    stft_times = np.linspace(0, len(stft_amps)/float(rate), npieces)
    sp.next_ax().contour(stft_freqs/rate, stft_freqs,
               np.log(stft_amps).reshape(npieces, npieces))
    sp.label()
    
    HTML(sp.exit())
    

最终结果参见以下截图:

How to do it...

示例代码在本书代码包的analyzing_audio.ipynb文件中。

另见

用离散余弦变换分析信号

离散余弦变换 ( 离散余弦变换)是一种类似于傅里叶变换的变换,但它试图仅通过余弦项的和来表示信号(参见等式 6.11)。离散余弦变换用于信号压缩和计算 mel 频率频谱,这是我在分析音频频谱配方中提到的。我们可以通过以下等式将正常频率转换为 mel 频率(更适合分析语音和音乐的频率):

Analyzing signals with the discrete cosine transform

创建 mel 频谱的步骤并不复杂,但有很多步骤。相关的维基百科页面可在https://en.wikipedia.org/wiki/Mel-frequency_cepstrum获得(2015 年 9 月检索)。如果你做一个快速的网络搜索,你可以找到几个实现该算法的 Python 库。我在这个配方中实现了一个非常简单的计算版本。

怎么做...

  1. 进口情况如下:

    import dautil as dl
    from scipy.fftpack import dct
    import matplotlib.pyplot as plt
    import ch6util
    import seaborn as sns
    import numpy as np
    from IPython.display import HTML
    
  2. 加载数据并转换如下:

    rate, audio = ch6util.read_wav()
    transformed = dct(audio)
    
  3. 使用离散余弦变换绘制振幅频谱:

    sp = dl.plotting.Subplotter(2, 2, context)
    freqs = np.fft.fftfreq(audio.size, 1./rate)
    indices = np.where(freqs > 0)[0]
    sp.ax.semilogy(np.abs(transformed)[indices])
    sp.label()
    
  4. 绘制振幅的分布:

    sns.distplot(np.log(np.abs(transformed)), ax=sp.next_ax())
    sp.label()
    
  5. 绘制相位分布图:

    sns.distplot(np.angle(transformed), ax=sp.next_ax())
    sp.label()
    
  6. 将 mel 振幅谱绘制如下:

    magnitude = ch6util.amplitude(audio)
    cepstrum = dl.ts.power(np.fft.ifft(np.log(magnitude ** 2)))
    mel = 1127 * np.log(1 + freqs[indices]/700)
    sp.next_ax().plot(mel, ch6util.amplitude(dct(np.log(magnitude[indices] ** 2))))
    sp.label()
    HTML(sp.exit())
    

有关最终结果,请参考以下屏幕截图:

How to do it...

这个食谱的代码在这个书的代码包中的analyzing_dct.ipynb文件中。

另见

块自举时间序列数据

通常的自举方法不能保持时间序列数据的顺序,因此不适合趋势估计。在块自举方法中,我们将数据分割成大小相等的非重叠块,并使用这些块生成新样本。在这个食谱中,我们将应用一个非常简单且易于实现的线性模型,其中包含年温度数据。该配方的程序如下:

  1. 将数据分割成块并生成新的数据样本。
  2. 将数据拟合到一行,或者计算新数据的第一个差异。
  3. 重复上一步,建立第一个差异的斜率或中间值列表。

怎么做...

  1. 进口情况如下:

    import dautil as dl
    import random
    import matplotlib.pyplot as plt
    import pandas as pd
    import numpy as np
    import seaborn as sns
    import ch6util
    from IPython.display import HTML
    
  2. 定义以下函数引导数据:

    def shuffle(temp, blocks):
        random.shuffle(blocks)
        df = pd.DataFrame({'TEMP': dl.collect.flatten(blocks)},
                          index=temp.index)
        df = df.resample('A')
    
        return df
    
  3. 加载数据并从中创建块:

    temp = dl.data.Weather.load()['TEMP'].resample('M').dropna()
    blocks = list(dl.collect.chunk(temp.values, 100))
    random.seed(12033)
    
  4. 绘制一些随机实现作为健全性检查:

    sp = dl.plotting.Subplotter(2, 2, context)
    cp = dl.plotting.CyclePlotter(sp.ax)
    medians = []
    slopes = []
    
    for i in range(240):
        df = shuffle(temp, blocks)
        slopes.append(ch6util.fit(df))
        medians.append(ch6util.diff_median(df))
    
        if i < 5:
            cp.plot(df.index, df.values)
    
    sp.label(ylabel_params=dl.data.Weather.get_header('TEMP'))
    
  5. 使用自举数据绘制第一个差值中位数的分布:

    sns.distplot(medians, ax=sp.next_ax(), norm_hist=True)
    sp.label()
    
  6. 使用自举数据

    sns.distplot(slopes, ax=sp.next_ax(), norm_hist=True)
    sp.label()
    

    绘制线性回归斜率的分布

  7. 绘制不同数量引导的置信区间:

    mins = []
    tops = []
    xrng = range(30, len(medians))
    
    for i in xrng:
        min, max = dl.stats.outliers(medians[:i])
        mins.append(min)
        tops.append(max)
    
    cp = dl.plotting.CyclePlotter(sp.next_ax())
    cp.plot(xrng, mins, label='5 %')
    cp.plot(xrng, tops, label='95 %')
    sp.label()
    HTML(sp.exit())
    

有关最终结果,请参考以下屏幕截图:

How to do it...

以下代码来自本书代码包中的block_boot.ipynb文件。

另见

移动块自举时间序列数据

如果你按照块自举时间序列数据的方法,你现在知道了一个简单的时间序列数据自举方案。移动块自举算法有点复杂。在该方案中,我们通过移动固定大小的窗口来生成重叠块,类似于移动平均。然后,我们组装这些块来创建新的数据样本。

在本食谱中,我们将把移动块自举应用于年温度数据,以生成第二差值中位数和 AR(1)模型斜率的列表。这是一个滞后为 1 的自回归模型。此外,我们将尝试使用中值滤波器来中和异常值和噪声。

怎么做...

以下代码片段来自本书代码包中的moving_boot.ipynb文件:

  1. 进口情况如下:

    import dautil as dl
    import numpy as np
    import pandas as pd
    import matplotlib.pyplot as plt
    import seaborn as sns
    import ch6util
    from scipy.signal import medfilt
    from IPython.display import HTML
    
  2. 定义以下函数引导数据:

    def shuffle(temp):
        indices = np.random.choice(start, n/12)
        sample = dl.collect.flatten([temp.values[i: i + 12] for i in indices])
        sample = medfilt(sample)
        df = pd.DataFrame({'TEMP': sample}, index=temp.index[:len(sample)])
        df = df.resample('A', how=np.median)
    
        return df
    
  3. 加载数据如下:

    temp = dl.data.Weather.load()['TEMP'].resample('M', how=np.median).dropna()
    n = len(temp)
    start = np.arange(n - 11)
    np.random.seed(2609787)
    
  4. 绘制一些随机实现作为健全性检查:

    sp = dl.plotting.Subplotter(2, 2, context)
    cp = dl.plotting.CyclePlotter(sp.ax)
    medians = []
    slopes = []
    
    for i in range(240):
        df = shuffle(temp)
        slopes.append(dl.ts.ar1(df.values.flatten())['slope'])
        medians.append(ch6util.diff_median(df, 2))
    
        if i < 5:
            cp.plot(df.index, df.values)
    
    sp.label(ylabel_params=dl.data.Weather.get_header('TEMP'))
    
  5. 使用自举数据绘制第二差值中位数的分布:

    sns.distplot(medians, ax=sp.next_ax())
    sp.label()
    
  6. 使用自举数据绘制 AR(1)模型斜率的分布:

    sns.distplot(slopes, ax=sp.next_ax())
    sp.label()
    
  7. 绘制不同引导次数的置信区间:

    mins = []
    tops = []
    xrng = range(30, len(medians))
    
    for i in xrng:
        min, max = dl.stats.outliers(medians[:i])
        mins.append(min)
        tops.append(max)
    
    cp = dl.plotting.CyclePlotter(sp.next_ax())
    cp.plot(xrng, mins, label='5 %')
    cp.plot(xrng, tops, label='95 %')
    sp.label()
    HTML(sp.exit())
    

有关最终结果,请参考以下屏幕截图:

How to do it...

另见

应用离散小波变换

离散小波变换 ( 离散小波变换)捕获时域和频域的信息。数学家阿尔弗雷德·哈尔创造了第一个小波。我们在这个食谱中也会用到这个 哈尔小波。变换返回近似细节系数,我们需要一起使用才能得到原始信号。逼近系数是低通滤波器的结果。高通滤波器产生细节系数。哈尔小波算法的阶数为 0(n),类似于 STFT 算法(参考分析音频频谱配方),结合了频率和时间信息。

与傅里叶变换的不同之处在于,我们将信号表示为正弦和余弦项的和,而小波由单个波(小波函数)表示。就像在 STFT 一样,我们在时域中分割信号,然后将小波函数应用于每个片段。DWT 在这个配方中可以有多个级别,我们不会超过第一个级别。为了获得下一级,我们将小波应用于前一级的近似系数。这意味着我们可以有多个层次的细节系数。

作为数据集,我们将看一看著名的尼罗河流量,这甚至是希腊历史学家希罗多德写的。最近,在上个世纪,水文学家赫斯特发现了一个幂律,适用于当年尼罗河 T2 流量的重新标度范围。更多信息请参考参见部分。重新缩放的范围并不难计算,但有许多步骤,如下式所述:

Applying the discrete wavelet transform

来自权力定律的赫斯特指数是趋势的指示器。我们还可以用更有效的方法从小波系数中得到赫斯特指数。

开始

安装pywavelets,如下:

$ pip install pywavelets 

这个食谱我用的是pywavelets 0.3.0。

怎么做...

  1. 进口情况如下:

    from statsmodels import datasets
    import matplotlib.pyplot as plt
    import pywt
    import pandas as pd
    import dautil as dl
    import numpy as np
    import seaborn as sns
    import warnings
    from IPython.display import HTML
    
  2. 过滤警告如下(可选步骤):

    warnings.filterwarnings(action='ignore',
                            message='.*Mean of empty slice.*')
    warnings.filterwarnings(action='ignore',
                            message='.*Degrees of freedom <= 0 for slice.*')
    
  3. 定义以下函数来计算重新缩放的范围:

    def calc_rescaled_range(X):
        N = len(X)
    
        # 1\. Mean
        mean = X.mean()
    
        # 2\. Y mean adjusted
        Y = X - mean
    
        # 3\. Z cumulative deviates
        Z = np.array([Y[:i].sum() for i in range(N)])
    
        # 4\. Range R
        R = np.array([0] + [np.ptp(Z[:i]) for i in range(1, N)])
    
        # 5\. Standard deviation S
        S = np.array([X[:i].std() for i in range(N)])
    
        # 6\. Average partial R/S
        return [np.nanmean(R[:i]/S[:i]) for i in range(N)]
    
  4. 加载数据并用哈尔小波变换:

    data = datasets.get_rdataset('Nile', cache=True).data
    cA, cD = pywt.dwt(data['Nile'].values, 'haar')
    coeff = pd.DataFrame({'cA': cA, 'cD': cD})
    
  5. 绘制尼罗河流量如下图:

    sp = dl.plotting.Subplotter(2, 2, context)
    sp.ax.plot(data['time'], data['Nile'])
    sp.label()
    
  6. 绘制转换数据的近似值和细节系数:

    cp = dl.plotting.CyclePlotter(sp.next_ax())
    cp.plot(range(len(cA)), cA, label='Approximation coefficients')
    cp.plot(range(len(cD)), cD, label='Detail coefficients')
    sp.label()
    
  7. 绘制系数的重新缩放的范围如下:

    sp.next_ax().loglog(range(len(cA)), calc_rescaled_range(cA), 
                        label='Approximation coefficients')
    sp.ax.loglog(range(len(cD)), calc_rescaled_range(cD), 
                 label='Detail coefficients')
    sp.label()
    
  8. 拟合绘制尼罗河流量数据的重新标度范围:

    range_df = pd.DataFrame(data={'Year': data.index,
                                  'Rescaled range':
                                  calc_rescaled_range(data['Nile'])})
    sp.next_ax().set(xscale="log", yscale="log")
    sns.regplot('Year', 'Rescaled range', range_df, ax=sp.ax, order=1,
                scatter_kws={"s": 100})
    sp.label()
    HTML(sp.exit())
    

有关最终结果,请参考以下屏幕截图:

How to do it...

相关代码在本书的代码包discrete_wavelet.ipynb文件中。

另见

七、使用金融数据分析选股

在本章中,我们将介绍以下食谱:

  • 计算简单和日志返回
  • 用夏普比率和流动性对股票进行排名
  • 用卡尔马尔比率和索蒂诺比率对股票进行排名
  • 分析退货统计数据
  • 将个股与大盘联系起来
  • 探索风险和回报
  • 用非参数游程检验检验市场
  • 随机漫步测试
  • 用自回归模型确定市场效率
  • 为股票价格数据库创建表格
  • 填充股票价格数据库
  • 优化等权双资产投资组合

简介

金融学涉及许多学科,如货币、储蓄、投资和保险。在这一章中,我们将集中讨论股票投资,因为股价数据非常丰富。根据学术理论,一般投资者不应该投资个股,而应该投资整个市场,例如,代表一个国家内大公司的一篮子股票。经济学家为这一理论提出了几个这样的论点。首先,金融市场是随机的;因此,通过选股击败一个平均篮子是非常困难的。其次,个股波动剧烈,价格波动剧烈。这些价格变动在一个篮子里得到平均,这使得投资一组股票的风险降低。

我们将分析股票价格,但没有什么能阻止你重用食谱来分析共同基金和交易所交易基金或其他金融资产。为了简化分析,我将选择范围限制在六只知名美国公司的股票,这些公司也在标准普尔 500 股票指数中有代表。

计算简单和日志返回

收益衡量(股票)价格的变化率。使用收益的好处是收益是无量纲的,所以我们可以很容易地比较不同金融证券的收益。相比之下,仅金融资产的价格并不能告诉我们太多。在本章中,我们计算每日回报,因为我们的数据是每天采样的。通过小的调整,您应该能够在不同的时间范围内应用相同的分析。

事实上,有各种类型的回报。出于基本分析的目的,我们只需要了解 simple (7.1)和 log(arithmic)返回(7.2),如下式所示:

Computing simple and log returns

实际上,这些类型的返回可以很容易地转换,从简单的返回到日志返回。如果给你选择,日志返回是你应该喜欢的,因为它们更容易计算。

怎么做...

  1. 进口情况如下:

    import dautil as dl
    import ch7util
    import matplotlib.pyplot as plt
    
  2. 下载标准普尔 500 指数数据:

    ohlc = dl.data.OHLC()
    sp500 = ohlc.get('^GSPC')['Adj Close']
    rets = sp500[1:]/sp500[:-1] - 1
    
  3. 绘制简单和日志返回:

    _, ax = plt.subplots()
    cp = dl.plotting.CyclePlotter(ax)
    cp.plot(sp500.index, rets, label='Simple')
    cp.plot(sp500.index[1:], ch7util.log_rets(sp500), label='Log')
    ax.set_title('Simple and Log Returns')
    ax.set_xlabel('Date')
    ax.set_ylabel('Return')
    ax.legend(loc='best')
    

最终结果参考以下截图(简单和日志返回的值非常接近):

How to do it...

本食谱的代码在本书代码包的simple_log_rets.ipynb文件中。

另见

用夏普比率和流动性对股票进行排名

由 T2 威廉夏普定义的夏普比率是一个基本的投资指标。比例如下:

Ranking stocks with the Sharpe ratio and liquidity

这个比率取决于资产的回报和基准的回报。我们将使用标准普尔 500 指数作为基准。这个比率应该代表一个报酬与风险的比率。我们希望在最小化风险的同时最大化回报,这与最大化夏普比率相对应。

另一个重要的投资变量是流动性。现金是最终的流动资产,但大多数其他资产的流动性较低,这意味着当我们试图出售或购买它们时,它们会改变价值。我们将使用本食谱中的交易量作为流动性的衡量标准。(交易量对应于金融资产的交易数量。流动性衡量一项资产的流动性——买卖它有多容易。)

怎么做...

你可以在本书的代码包sharpe_liquidity.ipynb文件中找到代码:

  1. 进口情况如下:

    import numpy as np
    import dautil as dl
    import matplotlib.pyplot as plt
    import ch7util
    
  2. 定义以下函数计算平均交易量的比值和对数:

    def calc_metrics(ticker, ohlc):
        stock = ohlc.get(ticker)
        sp500 = ohlc.get('^GSPC')
        merged = ch7util.merge_sp500(stock, sp500)
        rets_stock = ch7util.log_rets(merged['Adj Close_stock'])
        rets_sp500 = ch7util.log_rets(merged['Adj Close_sp500'])
        stock_sp500 = rets_stock - rets_sp500
        sharpe_stock = stock_sp500.mean()/stock_sp500.std()
        avg_vol = np.log(merged['Volume_stock'].mean())
    
        return (sharpe_stock, avg_vol)
    
  3. ch7util模块计算我们一篮子股票的指标:

    dfb = dl.report.DFBuilder(cols=['Ticker', 'Sharpe', 'Log(Average Volume)'])
    
    ohlc = dl.data.OHLC()
    
    for symbol in ch7util.STOCKS:
        sharpe, vol = calc_metrics(symbol, ohlc)
        dfb.row([symbol, sharpe, vol])
    
    df = dfb.build(index=ch7util.STOCKS)
    
  4. 绘制股票的比率和对数平均成交量:

    _, ax = plt.subplots()
    ax.scatter(df['Log(Average Volume)'], df['Sharpe'])
    dl.plotting.plot_polyfit(ax, df['Log(Average Volume)'], df['Sharpe'])
    
    dl.plotting.plot_text(ax, df['Log(Average Volume)'],
                          df['Sharpe'], ch7util.STOCKS)
    ax.set_xlabel('Log(Average Volume)')
    ax.set_ylabel('Sharpe')
    ax.set_title('Sharpe Ratio & Liquidity')
    

有关最终结果,请参考以下屏幕截图:

How to do it...

另见

用卡尔马尔比率和索蒂诺比率对股票进行排名

SortinoCalmar 比率是与夏普比率相当的业绩比率(参考用夏普比率和流动性配方对股票进行排名)。比率甚至更多;然而,夏普比率一直在附近最长,因此应用非常广泛。

Sortino 比率以 Frank Sortino 命名,但它是由 Brian Rom 定义的。比率将风险定义为低于基准的下行方差。基准可以是指数,也可以是固定收益,如零。该比率定义如下:

Ranking stocks with the Calmar and Sortino ratios

R 为资产回报, T 为目标基准, DR 为下行风险。卡尔马尔比率是由特里·杨发明的,并以他的公司和时事通讯命名。该比率将风险定义为资产的最大提取(价格从峰值跌至底部)。

怎么做...

以下是本书代码包中calmar_sortino.ipynb文件的分解:

  1. 进口情况如下:

    import numpy as np
    import dautil as dl
    import ch7util
    from scipy.signal import argrelmin
    from scipy.signal import argrelmax
    import matplotlib.pyplot as plt
    
  2. 定义以下函数计算排序比:

    def calc_sortino(rets):
        # Returns below target
        semi_var = rets[rets < 0] ** 2
        semi_var = semi_var.sum()/len(rets)
        sortino = np.sqrt(semi_var)
    
        return rets.mean()/sortino
    
  3. 定义以下函数计算卡尔马尔比:

    def calc_calmar(rets):
        # Peaks and bottoms indexes in sequence
        mins = np.ravel(argrelmin(rets))
        maxs = np.ravel(argrelmax(rets))
        extrema = np.concatenate((mins, maxs))
        extrema.sort()
    
        return -rets.mean()/np.diff(rets[extrema]).min()
    
  4. 为我们的股票列表计算卡尔马尔和索蒂诺比率:

    ohlc = dl.data.OHLC()
    dfb = dl.report.DFBuilder(cols=['Ticker', 'Sortino', 'Calmar'])
    
    for symbol in ch7util.STOCKS:
        stock = ohlc.get(symbol)
        rets = ch7util.log_rets(stock['Adj Close'])
        sortino = calc_sortino(rets)
        calmar = calc_calmar(rets)
        dfb.row([symbol, sortino, calmar])
    
    df = dfb.build(index=ch7util.STOCKS).dropna()
    
  5. 绘制股票的 Sortino 和 Calmar 比率:

    _, ax = plt.subplots()
    ax.scatter(df['Sortino'], df['Calmar'])
    dl.plotting.plot_polyfit(ax, df['Sortino'], df['Calmar'])
    dl.plotting.plot_text(ax, df['Sortino'], df['Calmar'], ch7util.STOCKS)
    ax.set_xlabel('Sortino')
    ax.set_ylabel('Calmar')
    ax.set_title('Sortino & Calmar Ratios')
    

有关最终结果,请参考以下屏幕截图:

How to do it...

另见

分析退货统计

回报,尤其是股票指数的回报,已经被广泛研究。过去,人们认为回报是正态分布的。然而,现在很明显,回报分布有肥尾(比正态分布更肥)。更多信息可在https://en.wikipedia.org/wiki/Fat-tailed_distribution获得(2015 年 10 月检索)。检查数据是否符合正态分布非常容易。我们只需要样本的平均值和标准差。

在本食谱中,我们将探讨许多主题:

  • 股票收益的偏斜度和峰度是一个值得研究的问题。偏斜度在股票期权模型中尤其重要。分析师通常将自己限制在均值和标准差范围内,假设它们分别对应于报酬和风险。
  • 如果我们对趋势的存在感兴趣,那么我们应该看一看自相关图。这是一个自相关图——即信号和某个滞后的信号之间的相关性(也在 Python 数据分析Packt Publishing 中解释)。
  • 我们还将绘制负回报(其绝对值)和对数-对数标度上的相应计数,因为这些似乎近似遵循幂律(尤其是尾值)。

怎么做...

分析可以在本书代码包的rets_stats.ipynb文件中找到:

  1. 进口情况如下:

    import dautil as dl
    import ch7util
    import matplotlib.pyplot as plt
    from scipy.stats import skew
    from scipy.stats import kurtosis
    from pandas.tools.plotting import autocorrelation_plot
    import numpy as np
    from scipy.stats import norm
    from IPython.display import HTML
    
  2. 计算我们股票的回报:

    ohlc = dl.data.OHLC()
    rets_dict = {}
    
    for i, symbol in enumerate(ch7util.STOCKS):
        rets = ch7util.log_rets(ohlc.get(symbol)['Adj Close'])
        rets_dict[symbol] = rets
    
    sp500 = ch7util.log_rets(ohlc.get('^GSPC')['Adj Close'])
    
  3. 绘制标准普尔 500 回报和相应理论正态分布的直方图:

    sp = dl.plotting.Subplotter(2, 2, context)
    sp.ax.set_xlim(-0.05, 0.05)
    _, bins, _ = sp.ax.hist(sp500, bins=dl.stats.sqrt_bins(sp500),
                         alpha=0.6, normed=True)
    sp.ax.plot(bins, norm.pdf(bins, sp500.mean(), sp500.std()), lw=2)
    
  4. 绘制收益的偏斜度和峰度:

    skews = [skew(rets_dict[s]) for s in ch7util.STOCKS]
    kurts = [kurtosis(rets_dict[s]) for s in ch7util.STOCKS]
    sp.label()
    
    sp.next_ax().scatter(skews, kurts)
    dl.plotting.plot_text(sp.ax, skews, kurts, ch7util.STOCKS)
    sp.label()
    
  5. 绘制标准普尔 500 回报的自相关图:

    autocorrelation_plot(sp500, ax=sp.next_ax())
    sp.label()
    
  6. 绘制负回报(绝对值)和计数的对数-对数图:

    # Negative returns
    counts, neg_rets = np.histogram(sp500[sp500 < 0])
    neg_rets = neg_rets[:-1] + (neg_rets[1] - neg_rets[0])/2
    # Adding 1 to avoid log(0)
    dl.plotting.plot_polyfit(sp.next_ax(), np.log(np.abs(neg_rets)),
                             np.log(counts + 1), plot_points=True)
    sp.label()
    
    HTML(sp.exit())
    

有关最终结果,请参考以下屏幕截图:

How to do it...

将个股与大盘关联起来

当我们定义一个股市或指数时,我们通常会选择在某些方面相似的股票。例如,股票可能在同一个国家或大陆。鸟类的位置可以从它们所属的鸟群的位置来粗略估计。同样,我们预计股票回报将与其市场相关,尽管不一定完全相关。

我们将探讨以下指标:

  • 最明显的指标据称是个股回报率和标准普尔 500 指数的相关系数。
  • 另一个指标是从线性回归而不是相关性中获得的斜率。
  • 我们也可以分析收益的平方差,有点类似于回归诊断中的平方误差。
  • 我们也可以将交易量和波动性联系起来,而不是将回报联系起来。为了衡量波动性,我们将使用有点不寻常的高低价格差的平方值。实际上,我们应该把这个值除以一个常数;然而,这对于相关系数计算来说不是必需的。

怎么做...

分析在本书代码包的correlating_market.ipynb文件中:

  1. 进口情况如下:

    import ch7util
    import dautil as dl
    import numpy as np
    import matplotlib.pyplot as plt
    from IPython.display import HTML
    
  2. 定义以下函数来计算波动率:

    def hl2(df, suffix):
        high = df['High_' + suffix]
        low = df['Low_' + suffix]
    
        return (high - low) ** 2
    
  3. 定义以下函数来关联标准普尔 500 和我们的股票:

    def correlate(stock, sp500):
        merged = ch7util.merge_sp500(stock, sp500)
        rets = ch7util.log_rets(merged['Adj Close_stock'])
        sp500_rets = ch7util.log_rets(merged['Adj Close_sp500'])
        result = {}
    
        result['corrcoef'] = np.corrcoef(rets, sp500_rets)[0][1]
        slope, _ = np.polyfit(sp500_rets, rets, 1)
        result['slope'] = slope
    
        srd = (sp500_rets - rets) ** 2
        result['msrd'] = srd.mean()
        result['std_srd'] = srd.std()
    
        result['vols'] = np.corrcoef(merged['Volume_stock'],
                                     merged['Volume_sp500'])[0][1]
    
        result['hl2'] = np.corrcoef(hl2(merged, 'stock'),
                                    hl2(merged, 'sp500'))[0][1]
    
        return result
    
  4. 将我们的一组股票与标准普尔 500 指数联系起来

  5. 绘制股票的相关系数:

    sp = dl.plotting.Subplotter(2, 2, context)
    dl.plotting.bar(sp.ax, ch7util.STOCKS,
                    [corr['corrcoef'] for corr in corrs])
    sp.label()
    
    dl.plotting.bar(sp.next_ax(), ch7util.STOCKS,
                    [corr['slope'] for corr in corrs])
    sp.label()
    
  6. 绘制差异统计的平方:

    sp.next_ax().set_xlim([0, 0.001])
    dl.plotting.plot_text(sp.ax, [corr['msrd'] for corr in corrs],
                          [corr['std_srd'] for corr in corrs],
                          ch7util.STOCKS, add_scatter=True,
                          fontsize=9, alpha=0.6)
    sp.label()
    
  7. 绘制成交量和波动率相关系数:

    dl.plotting.plot_text(sp.next_ax(), [corr['vols'] for corr in corrs],
                          [corr['hl2'] for corr in corrs],
                          ch7util.STOCKS, add_scatter=True,
                          fontsize=9, alpha=0.6)
    sp.label()
    
    HTML(sp.exit())
    

最终结果参见以下截图:

How to do it...

探索风险与回报

金融中的贝塔系数是一个线性回归模型的斜率(T2 ),该模型包含了 T3 资产的收益和一个基准的收益,例如 T4 500 指数。该模型定义如下:

Exploring risk and return

根据 资本资产定价模型 ( 资本资产定价模型)的说法,贝塔是衡量风险的指标。预期收益由收益的平均值给出。如果我们绘制各种证券的贝塔值和预期收益,我们就获得了相应市场的证券市场线 ( SML )。SML 的截距给出了无风险率,理论上我们应该在不承担任何风险的情况下获得回报。一般来说,如果一项资产不在 SML,那么根据资本资产定价模型,它被错误定价。

怎么做...

程序在本书代码包的capm.ipynb文件中:

  1. 进口情况如下:

    import dautil as dl
    import numpy as np
    import pandas as pd
    import ch7util
    import matplotlib.pyplot as plt
    
  2. 定义以下函数来计算β值:

    def calc_beta(symbol):
        ohlc = dl.data.OHLC()
        sp500 = ohlc.get('^GSPC')['Adj Close']
        stock = ohlc.get(symbol)['Adj Close']
        df = pd.DataFrame({'SP500': sp500, symbol: stock}).dropna()
        sp500_rets = ch7util.log_rets(df['SP500'])
        rets = ch7util.log_rets(df[symbol])
        beta, _ = np.polyfit(sp500_rets, rets, 1)
    
        # annualize & percentify
        return beta, 252 * rets.mean() * 100
    
  3. 计算我们股票的贝塔值和平均回报:

    betas = []
    means = []
    
    for symbol in ch7util.STOCKS:
        beta, ret_mean = calc_beta(symbol)
        betas.append(beta)
        means.append(ret_mean)
    
  4. 绘制结果和市场安全线:

    _, ax = plt.subplots()
    dl.plotting.plot_text(ax, betas, means, ch7util.STOCKS, add_scatter=True)
    dl.plotting.plot_polyfit(ax, betas, means)
    ax.set_title('Capital Asset Pricing Model')
    ax.set_xlabel('Beta')
    ax.set_ylabel('Mean annual return (%)')
    

有关最终结果,请参考以下屏幕截图:

How to do it...

另见

用非参数游程检验检验市场

有效市场假说(【EMH】)规定你平均不能通过挑选更好的股票或把握市场时机来“跑赢市场”。根据《EMH》的说法,所有关于市场的信息都可以以这样或那样的形式立即提供给每个市场参与者,并立即反映在资产价格中,因此投资就像是在玩一场纸牌游戏,所有的牌都露出来了。你能赢的唯一方法是在风险很大的股票上下注,然后走运。

法国数学家学士在 1900 年左右为 EMH 开发了一个测试。该测试考察了连续出现的正负价格变化。我们不计算价格没有变化的事件,只使用它们来结束运行。无论如何,对于流动性市场来说,这类事件相对较少。

统计测试本身在金融界之外为人所知,被称为“T2”沃尔德-沃尔福威茨运行测试“T3”。如果我们用“+”表示正变化,用“-”表示负变化,我们可以用 5 次运行得到序列“++++++++++++”。以下计算运行次数的平均值μ (7.6)、标准偏差σ (7.7)和 Z 评分 Z (7.8)的公式 R 还要求负变化次数 N- ,正变化次数 N+ ,以及总变化次数 N :

Examining the market with the non-parametric runs test

我们假设运行次数遵循正态分布,这给了我们一种方法,在我们选择的置信水平下,潜在地拒绝运行的随机性。

怎么做...

看看这本书代码包里的non_parametric.ipynb文件。

  1. 进口情况如下:

    import dautil as dl
    import numpy as np
    import pandas as pd
    import ch7util
    import matplotlib.pyplot as plt
    from scipy.stats import norm
    from IPython.display import HTML
    
  2. 定义以下函数来计算运行次数:

    def count_runs(signs):
        nruns = 0
        prev = None
    
        for s in signs:
            if s != 0 and s != prev:
                nruns += 1
    
            prev = s
    
        return nruns
    
  3. 定义以下函数来计算平均值、标准偏差和 z 分数:

    def proc_runs(symbol):
        ohlc = dl.data.OHLC()
        close = ohlc.get(symbol)['Adj Close'].values
        diffs = np.diff(close)
        nplus = (diffs > 0).sum()
        nmin = (diffs < 0).sum()
        n = nplus + nmin
        mean = (2 * (nplus * nmin) / n) + 1
        var = (mean - 1) * (mean - 2) / (n - 1)
        std = np.sqrt(var)
        signs = np.sign(diffs)
        nruns = count_runs(np.diff(signs))
    
        return mean, std, (nruns - mean) / std
    
  4. 计算我们股票的指标:

    means = []
    stds = []
    zscores = []
    
    for symbol in ch7util.STOCKS:
        mean, std, zscore = proc_runs(symbol)
        means.append(mean)
        stds.append(std)
        zscores.append(zscore)
    
  5. 用表示 95%置信水平的线绘制 z 分数:

    sp = dl.plotting.Subplotter(2, 1, context)
    dl.plotting.plot_text(sp.ax, means, stds, ch7util.STOCKS, add_scatter=True)
    sp.label()
    
    dl.plotting.bar(sp.next_ax(), ch7util.STOCKS, zscores)
    sp.ax.axhline(norm.ppf(0.95), label='95 % confidence level')
    sp.label()
    HTML(sp.exit())
    

最终结果参见以下截图:

How to do it...

另见

随机行走测试

随机游走假说 ( RWH )就像有效市场假说(参考用非参数游程检验食谱检验市场)一样,声称市场是打不过的。 RWH 规定,资产价格执行随机游走。事实上,只要反复抛硬币,你就能生成相当令人信服的股价图。

1988 年,金融学教授罗和麦克莱恩斯用资产价格的自然对数作为数据为构建了一个测试。该测试指定原木价格在平均值(7.9)附近漂移。我们预计不同频率(例如,一天和两天)的价格变化是随机的。此外,两个不同频率下的方差(7.10 和 7.11)是相关的,根据以下等式,相应的比值(7.12)通常分布在零附近:

Testing for random walks

怎么做...

代码在本书代码包的random_walk.ipynb文件中:

  1. 进口情况如下:

    import dautil as dl
    import numpy as np
    import matplotlib.pyplot as plt
    import ch7util
    
  2. 计算我们股票的比率:

    ratios = []
    
    for symbol in ch7util.STOCKS:
        ohlc = dl.data.OHLC()
        P = ohlc.get(symbol)['Adj Close'].values
        N = len(P)
        mu = (np.log(P[-1]) - np.log(P[0]))/N
        var_a = 0
        var_b = 0
    
        for k in range(1, N):
            var_a = (np.log(P[k]) - np.log(P[k - 1]) - mu) ** 2
            var_a = var_a / N
    
        for k in range(1, N//2):
            var_b = (np.log(P[2 * k]) - np.log(P[2 * k - 2]) - 2 * mu) ** 2
            var_b = var_b / N
    
        ratios.append(np.sqrt(N) * (var_b/var_a - 1))
    
  3. 绘制比率图,我们预计比率接近于零(7.12):

    _, ax = plt.subplots()
    dl.plotting.bar(ax, ch7util.STOCKS, ratios)
    ax.set_title('Random Walk Test')
    ax.set_ylabel('Ratio')
    

有关最终结果,请参考以下屏幕截图:

How to do it...

另见

用自回归模型确定市场效率

根据有效市场假说(参考用非参数游程检验方法检验市场),关于资产的所有信息立即反映在资产的价格中。这意味着以前的价格不会影响现在的价格。以下方程指定了自回归模型(7。13)和受限模型(7。14)将所有系数设置为零:

Determining market efficiency with autoregressive models

如果我们相信市场是有效的,我们会期望非限制模型比限制模型没有什么增加,因此,比率(7。15)的相应 R 平方系数应该接近 1。

怎么做...

脚本在本书代码包的autoregressive_test.ipynb文件中:

  1. 进口情况如下:

    import dautil as dl
    import ch7util
    import numpy as np
    import matplotlib.pyplot as plt
    import statsmodels.api as sm
    from IPython.display import HTML
    
  2. 使用(7.13)和(7.14)拟合模型,然后使用(7.15)为我们的股票列表计算市场效率:

    ohlc = dl.data.OHLC()
    efficiencies = []
    restricted_r2 = []
    unrestricted_r2 = []
    
    for stock in ch7util.STOCKS:
        rets = ch7util.log_rets(ohlc.get(stock)['Adj Close'])
        restricted = sm.OLS(rets, rets.mean() * np.ones_like(rets)).fit()
        rets_1 = rets[3:-1]
        rets_2 = rets[2:-2]
        rets_3 = rets[1:-3]
        rets_4 = rets[:-4]
        x = np.vstack((rets_1, rets_2, rets_3, rets_4)).T
        x = sm.add_constant(x)
        y = rets[4:]
        unrestricted = sm.OLS(y, x).fit()
        restricted_r2.append(restricted.rsquared)
        unrestricted_r2.append(unrestricted.rsquared)
        efficiencies.append(1 - restricted.rsquared/unrestricted.rsquared)
    
  3. 将市场效率和 R 平方值绘制如下:

    sp = dl.plotting.Subplotter(2, 1, context)
    dl.plotting.bar(sp.ax, ch7util.STOCKS, efficiencies)
    sp.label()
    dl.plotting.plot_text(sp.next_ax(), unrestricted_r2, np.array(restricted_r2)/10 ** -16,
                          ch7util.STOCKS, add_scatter=True)
    sp.label()
    HTML(sp.exit())
    

有关最终结果,请参考以下屏幕截图:

How to do it...

另见

为股票价格数据库创建表格

只存储股票价格一般来说不是很有用。我们通常希望存储关于公司和相关衍生品的额外静态信息,如股票期权和期货。经济学理论告诉我们,在历史价格数据中寻找周期和趋势或多或少是浪费时间;因此,创建数据库似乎更没有意义。当然你不必相信这个理论,无论如何创建一个股票价格数据库是一个有趣的技术挑战。数据库对于投资组合优化也很有用(参见食谱优化同等权重的 2 资产投资组合)。

我们将基于中的星型模式进行设计,用事实和维度表实现星型模式。事实表将保存价格,包括日期维度表、资产维度表和来源维度表,如下图所示:

Creating tables for a stock prices database

显然,模式会随着时间的推移而发展,根据需要添加或删除表、索引和列。我们将使用中的模式填充股票价格数据库配方。

怎么做...

该模式在本书的代码包中的database_tables.py文件中定义:

  1. 进口情况如下:

    from sqlalchemy import Column
    from sqlalchemy.ext.declarative import declarative_base
    from sqlalchemy import Date
    from sqlalchemy import ForeignKey
    from sqlalchemy import Integer
    from sqlalchemy import String
    
    Base = declarative_base()
    
  2. 为股价事实表定义以下类别:

    class StockPrice(Base):
        __tablename__ = 'stock_price'
        id = Column(Integer, primary_key=True)
        date_id = Column(Integer, ForeignKey('date_dim.id'),
                         primary_key=True)
        asset_id = Column(Integer, ForeignKey('asset_dim.id'),
                          primary_key=True)
        source_id = Column(Integer, ForeignKey('source_dim.id'),
                           primary_key=True)
        open_price = Column(Integer)
        high_price = Column(Integer)
        low_price = Column(Integer)
        close_price = Column(Integer)
        adjusted_close = Column(Integer)
        volume = Column(Integer)
    
  3. 为日期维度表定义以下类:

    class DateDim(Base):
        __tablename__ = 'date_dim'
        id = Column(Integer, primary_key=True)
        date = Column(Date, nullable=False, unique=True)
        day_of_month = Column(Integer, nullable=False)
        day_of_week = Column(Integer, nullable=False)
        month = Column(Integer, nullable=False)
        quarter = Column(Integer, nullable=False)
        year = Column(Integer, nullable=False)
    
  4. 定义以下类来保存股票信息:

    class AssetDim(Base):
        __tablename__ = 'asset_dim'
        id = Column(Integer, primary_key=True)
        symbol = Column(String, nullable=False, unique=True)
        name = Column(String, nullable=False)
        # Could make this a reference to separate table
        category = Column(String, nullable=False)
        country = Column(String, nullable=False)
        # Could make this a reference to separate table
        sector = Column(String, nullable=False)
    
  5. 为源维度表定义以下类(雅虎财经只需要一个条目):

    class SourceDim(Base):
        __tablename__ = 'source_dim'
        id = Column(Integer, primary_key=True)
        name = Column(String, nullable=False)
        url = Column(String)
    

填充股票价格数据库

为股票价格数据库配方创建表格中,我们为历史股票价格数据库定义了一个模式。在这个食谱中,我们将使用雅虎财经的数据填充表格,并绘制不同时间段和业务部门的平均交易量。

股市研究人员发现了几个与季节效应有关的奇怪现象。此外,还有某些反复出现的事件,如盈利公告、股息支付和期权到期。同样,经济理论告诉我们,我们观察到的任何模式要么是幻觉,要么是所有市场参与者都已经知道的。这是真是假很难证实;然而,这种方法作为数据分析的练习是很棒的。此外,您可以使用数据库来优化您的投资组合,如优化等权重 2 资产投资组合食谱中所述。

怎么做...

代码在本书代码包的populate_database.ipynb文件中:

  1. 进口情况如下:

    import database_tables as tables
    import pandas as pd
    import os
    import dautil as dl
    import ch7util
    import sqlite3
    import matplotlib.pyplot as plt
    import seaborn as sns
    from IPython.display import HTML
    
  2. 定义以下函数来填充日期维度表:

    def populate_date_dim(session):
        for d in pd.date_range(start='19000101', end='20250101'):
            adate = tables.DateDim(date=d.date(), day_of_month=d.day,
                                   day_of_week=d.dayofweek, month=d.month,
                                   quarter=d.quarter, year=d.year)
            session.add(adate)
    
        session.commit()
    
  3. 定义以下功能填充资产维度表:

    def populate_asset_dim(session):
        asset = tables.AssetDim(symbol='AAPL', name='Apple Inc.',
                                category='Common Stock', country='USA',
                                sector='Consumer Goods')
        session.add(asset)
    
        asset = tables.AssetDim(symbol='INTC', name='Intel Corporation',
                                category='Common Stock', country='USA',
                                sector='Technology')
        session.add(asset)
    
        asset = tables.AssetDim(symbol='MSFT', name='Microsoft Corporation',
                                category='Common Stock', country='USA',
                                sector='Technology')
        session.add(asset)
    
        asset = tables.AssetDim(symbol='KO', name='The Coca-Cola Company',
                                category='Common Stock', country='USA',
                                sector='Consumer Goods')
        session.add(asset)
    
        asset = tables.AssetDim(symbol='DIS', name='The Walt Disney Company',
                                category='Common Stock', country='USA',
                                sector='Services')
        session.add(asset)
    
        asset = tables.AssetDim(symbol='MCD', name='McDonald\'s Corp.',
                                category='Common Stock', country='USA',
                                sector='Services')
        session.add(asset)
    
        asset = tables.AssetDim(symbol='NKE', name='NIKE, Inc.',
                                category='Common Stock', country='USA',
                                sector='Consumer Goods')
        session.add(asset)
    
        asset = tables.AssetDim(symbol='IBM',
                                name='International Business Machines Corporation',
                                category='Common Stock', country='USA',
                                sector='Technology')
        session.add(asset)
    
        session.commit()
    
  4. 定义以下函数来填充源维度表:

    def populate_source_dim(session):
        session.add(tables.SourceDim(name='Yahoo Finance',
                                     url='https://finance.yahoo.com'))
        session.commit()
    
  5. 定义以下函数来填充持有股票价格的事实表:

    def populate_prices(session):
        symbols = dl.db.map_to_id(session, tables.AssetDim.symbol)
        dates = dl.db.map_to_id(session, tables.DateDim.date)
        source_id = session.query(tables.SourceDim).first().id
        ohlc = dl.data.OHLC()
        conn = sqlite3.connect(dbname)
        c = conn.cursor()
        insert = '''INSERT INTO stock_price (id, date_id,
            asset_id, source_id, open_price, high_price, low_price,
            close_price, adjusted_close, volume)  VALUES({id}, {date_id},
            {asset_id}, {source_id}, {open_price}, {high_price},
            {low_price}, {close_price}, {adj_close}, {volume})'''
        logger = dl.log_api.conf_logger(__name__)
    
        for symbol in ch7util.STOCKS:
            df = ohlc.get(symbol)
            i = 0
    
            for index, row in df.iterrows():
                date_id = dates[index.date()]
                asset_id = symbols[symbol]
                i += 1
                stmt = insert.format(id=i, date_id=date_id,
                                     asset_id=asset_id,
                                     source_id=source_id,
                                     open_price=dl.data.centify(row['Open']),
                                     high_price=dl.data.centify(row['High']),
                                     low_price=dl.data.centify(row['Low']),
                                     close_price=dl.data.centify(row['Close']),
                                     adj_close=dl.data.centify(row['Adj Close']),
                                     volume=int(row['Volume']))
                c.execute(stmt)
    
                if i % 1000 == 0:
                    logger.info("Progress %s %s", symbol, i)
    
                conn.commit()
    
            conn.commit()
    
        c.close()
        conn.close()
    
  6. 定义以下函数来填充所有表:

    def populate(session):
        if session.query(tables.SourceDim).count() == 0:
            populate_source_dim(session)
            populate_asset_dim(session)
            populate_date_dim(session)
            populate_prices(session)
    
  7. 定义以下函数来绘制平均体积:

    def plot_volume(col, ax):
        df = pd.read_sql(sql.format(col=col), conn)
        sns.barplot(x=col, y='AVG(P.Volume/1000)', data=df,
                    hue='sector', ax=ax)
    
        ax.legend(loc='best')
    
    dbname = os.path.join(dl.data.get_data_dir(), 'stock_prices.db')
    session = dl.db.create_session(dbname, tables.Base)
    populate(session)
    sql = '''
        SELECT
            A.sector,
            D.{col},
            AVG(P.Volume/1000)
        FROM stock_price P
        INNER JOIN date_dim D  ON (P.Date_Id = D.Id)
        INNER JOIN asset_dim A ON (P.asset_id = a.Id)
        GROUP BY
            A.sector,
            D.{col}
          '''
    
  8. 用以下代码绘制平均体积:

    conn = sqlite3.connect(dbname)
    
    sp = dl.plotting.Subplotter(2, 2, context)
    plot_volume('day_of_week', sp.ax)
    sp.ax.set_xticklabels(['Mon', 'Tue', 'Wed', 'Thu', 'Fri'])
    
    plot_volume('month', sp.next_ax())
    sp.ax.set_xticklabels(dl.ts.short_months())
    
    plot_volume('day_of_month', sp.next_ax())
    plot_volume('quarter', sp.next_ax())
    HTML(sp.exit())
    

有关最终结果,请参考以下屏幕截图:

How to do it...

优化等权重双资产组合

买卖股票有点像购物。购物是超市和网上书店都很熟悉的事情。这些类型的业务通常应用技术,如篮子分析和推荐引擎。例如,如果你是一个写历史不准确小说的作家的粉丝,推荐引擎可能会推荐同一作家的另一部小说或其他历史不准确的小说。

股票推荐引擎不能这样工作。例如,如果你的投资组合中只有石油生产商的股票,而油价对你不利,那么整个投资组合将失去价值。所以,我们应该尝试拥有来自不同行业、行业或地理区域的股票。我们可以用回报的相关性来衡量相似性。

类似于夏普比率(参考用夏普比率和流动性排名股票食谱),我们希望最大化我们的投资组合的平均回报,最小化投资组合回报的方差。这些想法也存在于现代投资组合理论 ( MPT )中,其发明者被授予诺贝尔奖。对于双资产投资组合,我们有以下等式:

Optimizing an equal weights two-asset portfolio

权重 wAwB 为组合权重,合计为 1。权重可以是负的——因为投资者可以卖空(不持有就卖出,这会产生借贷成本)一种证券。我们可以用线性代数方法或一般优化算法解决投资组合优化问题。然而,对于权重相等、只有少数股票的双资产投资组合来说,蛮力方法已经足够好了。

怎么做...

以下是本书代码包中portfolio_optimization.ipynb文件的分解:

  1. 进口情况如下:

    import dautil as dl
    import ch7util
    import pandas as pd
    import numpy as np
    import seaborn as sns
    import matplotlib.pyplot as plt
    
  2. 定义以下函数来计算预期收益(7.16):

    def expected_return(stocka, stockb, means):
        return 0.5 * (means[stocka] + means[stockb])
    
  3. 定义以下函数计算投资组合回报的方差(7.17):

    def variance_return(stocka, stockb, stds):
        ohlc = dl.data.OHLC()
        dfa = ohlc.get(stocka)
        dfb = ohlc.get(stockb)
        merged = pd.merge(left=dfa, right=dfb,
                          right_index=True, left_index=True,
                          suffixes=('_A', '_B')).dropna()
        retsa = ch7util.log_rets(merged['Adj Close_A'])
        retsb = ch7util.log_rets(merged['Adj Close_B'])
        corr = np.corrcoef(retsa, retsb)[0][1]
    
        return 0.25 * (stds[stocka] ** 2 + stds[stockb] ** 2 +
                       2 * stds[stocka] * stds[stockb] * corr)
    
  4. 定义以下函数计算预期收益与方差的比值:

    def calc_ratio(stocka, stockb, means, stds, ratios):
        if stocka == stockb:
            return np.nan
    
        key = stocka + '_' + stockb
        ratio = ratios.get(key, None)
    
        if ratio:
            return ratio
    
        expected = expected_return(stocka, stockb, means)
        var = variance_return(stocka, stockb, stds)
        ratio = expected/var
        ratios[key] = ratio
    
        return ratio
    
  5. 计算每只股票的平均回报和标准差:

    means = {}
    stds = {}
    
    ohlc = dl.data.OHLC()
    
    for stock in ch7util.STOCKS:
        close = ohlc.get(stock)['Adj Close']
        rets = ch7util.log_rets(close)
        means[stock] = rets.mean()
        stds[stock] = rets.std()
    
  6. 计算我们所有股票组合的网格比率:

    pairs = dl.collect.grid_list(ch7util.STOCKS)
    sorted_pairs = [[sorted(row[i]) for row in pairs]
                    for i in range(len(ch7util.STOCKS))]
    ratios = {}
    
    grid = [[calc_ratio(row[i][0], row[i][1], means, stds, ratios)
            for row in sorted_pairs] for i in range(len(ch7util.STOCKS))]
    
  7. 在热图中绘制网格如下:

    %matplotlib inline
    plt.title('Expected Return/Return Variance for 2 Asset Portfolio')
    sns.heatmap(grid, xticklabels=ch7util.STOCKS, yticklabels=ch7util.STOCKS)
    

最终结果参见以下截图:

How to do it...

另见

八、文本挖掘与社交网络分析

在本章中,我们将介绍以下食谱:

  • 创建分类语料库
  • 用句子和单词标记新闻文章
  • 词干,引理,过滤和 TF-IDF 分数
  • 识别命名实体
  • 利用非负矩阵分解提取主题
  • 实现基本术语数据库
  • 计算社交网络密度
  • 计算社交网络接近度中心度
  • 确定中间中心性
  • 估计平均聚类系数
  • 计算图的分类系数
  • 得到图的团数
  • 创建具有余弦相似性的文档图

简介

人类通过语言交流已经有几千年了。手写文本已经存在了很长时间,古腾堡出版社当然是一个巨大的发展,但是现在我们有了计算机、互联网和社交媒体,事情肯定已经失控了。

本章将帮助你应对文本和社交媒体信息的泛滥。我们将使用的主要 Python 库是 NLTK 和 NetworkX。你必须真正理解在这些库中可以找到多少特性。用pipconda安装 NLTK,如下所示:

$ conda/pip install nltk 

代码用 NLTK 3.0.2 进行了测试。如需下载语料库,请按照http://www.nltk.org/data.html给出的说明进行(2015 年 11 月检索)。

使用 pipconda安装网络,如下所示:

$ conda/pip install networkx 

该代码用网络 1.9.1 进行了测试。

创建分类语料库

作为 Python 爱好者,我们对关于 Python 编程或相关技术的新闻感兴趣;但是,如果搜索 Python 文章,也可能会得到关于蛇的文章。这个问题的一个解决方案是训练一个识别相关文章的分类器。这需要一个训练集——一个分类的语料库,例如,“Python 编程”和“其他”类别。

NLTK 有CategorizedPlaintextCorpusReader类,用于构建分类语料库。为了让事情变得更加令人兴奋,我们将从 RSS 源中获取新闻文章的链接。我选择了英国广播公司的节目,但是你当然可以使用任何其他的节目。英国广播公司的节目已经分类了。我选择了世界新闻和科技新闻,所以这给了我们两个类别。提要不包含文章的全文,因此我们需要使用 Selenium 进行一些抓取,如第 5 章网络挖掘、数据库和大数据所述。你可能需要对文本文件进行后处理,因为英国广播公司的网页不仅包含新闻故事的文本,还包含副刊。

做好准备

按照本章简介部分的说明安装 NLTK。安装用于处理 RSS 源的 feedparser:

$ pip/conda install feedparser

我用 feedparser 5.2.1 测试了这个配方。

怎么做...

  1. 进口情况如下:

    import feedparser as fp
    import urllib
    from selenium import webdriver
    from selenium.webdriver.support.ui import WebDriverWait
    from selenium.webdriver.support import expected_conditions as EC
    from selenium.webdriver.common.by import By
    import dautil as dl
    from nltk.corpus.reader import CategorizedPlaintextCorpusReader
    import os
    
  2. 创建以下变量来帮助刮擦:

    DRIVER = webdriver.PhantomJS()
    NAP_SECONDS = 10
    LOGGER = dl.log_api.conf_logger('corpus')
    
  3. 定义以下功能存储文本内容:

    def store_txt(url, fname, title):
        try:
            DRIVER.get(url)
    
            elems = WebDriverWait(DRIVER, NAP_SECONDS).until(
                EC.presence_of_all_elements_located((By.XPATH, '//p'))
            )
    
            with open(fname, 'w') as txt_file:
                txt_file.write(title + '\n\n')
                lines = [e.text for e in elems]
                txt_file.write(' \n'.join(lines))
        except Exception:
            LOGGER.error("Error processing HTML", exc_info=True)
    
  4. 定义以下函数来检索故事:

    def fetch_news(dir):
        base = 'http://newsrss.bbc.co.uk/rss/newsonline_uk_edition/{}/rss.xml'
    
        for category in ['world', 'technology']:
            rss = fp.parse(base.format(category))
    
            for i, entry in enumerate(rss.entries):
                fname = '{0}_bbc_{1}.txt'.format(i, category)
                fname = os.path.join(dir, fname)
    
                if not dl.conf.file_exists(fname):
                    store_txt(entry.link, fname, entry.title)
    
  5. 用以下代码调用函数:

    if __name__ == "__main__":
        dir = os.path.join(dl.data.get_data_dir(), 'bbc_news_corpus')
    
        if not os.path.exists(dir):
            os.mkdir(dir)
    
        fetch_news(dir)
        reader = CategorizedPlaintextCorpusReader(dir, r'.*bbc.*\.txt',
                                                  cat_pattern=r'.*bbc_(\w+)\.txt')
        printer = dl.log_api.Printer(nelems=3)
        printer.print('Categories', reader.categories())
        printer.print('World fileids', reader.fileids(categories=['world']))
        printer.print('Technology fileids',
                      reader.fileids(categories=['technology']))
    

最终结果参见以下截图:

How to do it...

代码在本书代码包的corpus.py文件中。

另见

用句子和单词标记新闻文章

作为 NLTK 分布的一部分的语料库已经被标记化,所以我们可以很容易地获得单词和句子的列表。对于我们自己的语料库,我们也应该应用标记化。这个食谱演示了如何用 NLTK 实现标记化。我们将使用的文本文件在本书的代码包中。这个特殊的文本是英文的,但是 NLTK 也支持其他语言。

做好准备

按照本章简介部分的说明安装 NLTK。

怎么做...

程序在本书代码包的tokenizing.py文件中:

  1. 进口情况如下:

    from nltk.tokenize import sent_tokenize
    from nltk.tokenize import word_tokenize
    import dautil as dl
    
  2. 以下代码演示了标记化:

    fname = '46_bbc_world.txt'
    printer = dl.log_api.Printer(nelems=3)
    
    with open(fname, "r", encoding="utf-8") as txt_file:
        txt = txt_file.read()
        printer.print('Sentences', sent_tokenize(txt))
        printer.print('Words', word_tokenize(txt))
    

有关最终结果,请参考以下屏幕截图:

How to do it...

另见

词干、引理、过滤和 TF-IDF 分数

单词包模型将语料库字面上表示为单词包,不考虑单词的位置,只考虑它们的数量。停止词是常见的词,如“a”、“is”和“the”,它们不增加信息价值。

TF-IDF 分数可以针对单个单词(单字)或多个连续单词(n-克)的组合进行计算。TF-IDF 大致为词频逆文档频率的比值。我说“大致”是因为我们通常取比率的对数或应用加权方案。术语频率是文档中一个单词或 n-gram 的频率。逆文档频率是单词或 n-gram 出现的文档数量的倒数。我们可以使用 TF-IDF 分数进行聚类或者作为分类的一个特征。在用非负矩阵分解提取主题食谱中,我们将使用分数来发现主题。

NLTK 通过一个稀疏矩阵来表示分数,该矩阵为语料库中的每个文档提供一行,为每个单词或 n-gram 提供一列。即使矩阵是稀疏的,我们也应该根据我们试图解决的问题类型,尽可能多地过滤单词。过滤代码在ch8util.py中,执行以下操作:

  • 将所有单词转换为小写。在英语中,句子以大写字母开头,在单词包模型中,我们不关心单词的位置。显然,如果我们想检测命名实体(如识别命名实体配方),案例很重要。
  • 忽略停止词,因为它们没有语义价值。
  • 忽略仅由一个字符组成的单词,因为这些单词要么是停止单词,要么是假装单词的标点符号。
  • 忽略只出现一次的单词,因为这些单词不太可能很重要。
  • 只允许包含字母的单词,因此会忽略像“7”这样包含数字的单词。

我们也会用引理进行过滤。引理化类似于词干化,我也将演示这一点。这两个过程背后的想法是单词有共同的词根,例如,“分析”、“分析师”和“分析师”有共同的词根。一般来说,词干会剪切字符,所以结果不一定是一个有效的单词。相反,引理化总是产生有效的单词并执行字典查找。

本书代码包中ch8util.py文件的代码如下:

from collections import Counter
from nltk.corpus import brown
from joblib import Memory

memory = Memory(cachedir='.')

def only_letters(word):
    for c in word:
        if not c.isalpha():
            return False

    return True

@memory.cache
def filter(fid, lemmatizer, sw):
    words = [lemmatizer.lemmatize(w.lower()) for w in brown.words(fid)
             if len(w) > 1 and w.lower() not in sw]

    # Ignore words which only occur once
    counts = Counter(words)
    rare = set([w for w, c in counts.items() if c == 1])

    filtered_words = [w for w in words if w not in rare]

    return [w for w in filtered_words if only_letters(w)]

我决定将分析限制在单图,但是将分析扩展到二元模型或三元模型是非常容易的。我们将使用的 scikit-learn TfidfVectorizer类允许我们指定一个ngram_range字段,这样我们就可以同时考虑 unigrams 和 n-grams。我们将腌制这个食谱的结果,以供其他食谱重复使用。

做好准备

按照简介部分的说明安装 NLTK。

怎么做...

脚本在本书代码包的stemming_lemma.py文件中:

  1. 进口情况如下:

    from nltk.corpus import brown
    from nltk.corpus import stopwords
    from nltk.stem import PorterStemmer
    from nltk.stem import WordNetLemmatizer
    import ch8util
    from sklearn.feature_extraction.text import TfidfVectorizer
    import numpy as np
    import pandas as pd
    import pickle
    import dautil as dl
    
  2. 演示词干和引理如下:

    stemmer = PorterStemmer()
    lemmatizer = WordNetLemmatizer()
    
    print('stem(analyses)', stemmer.stem('analyses'))
    print('lemmatize(analyses)', lemmatizer.lemmatize('analyses'))
    
  3. 过滤 NLTK 布朗语料库中的单词:

    sw = set(stopwords.words())
    texts = []
    
    fids = brown.fileids(categories='news')
    
    for fid in fids:
        texts.append(" ".join(ch8util.filter(fid, lemmatizer, sw)))
    
  4. 计算 TF-IDF 分数如下:

    vectorizer = TfidfVectorizer()
    matrix = vectorizer.fit_transform(texts)
    
    with open('tfidf.pkl', 'wb') as pkl:
        pickle.dump(matrix, pkl)
    
    sums = np.array(matrix.sum(axis=0)).ravel()
    
    ranks = [(word, val) for word, val in
             zip(vectorizer.get_feature_names(), sums)]
    
    df = pd.DataFrame(ranks, columns=["term", "tfidf"])
    df.to_pickle('tfidf_df.pkl')
    df = df.sort(['tfidf'])
    dl.options.set_pd_options()
    print(df)
    

最终结果参见以下截图:

How to do it...

它是如何工作的

如您所见,词干不返回有效的单词。它比引理化更快;但是,如果想要重用结果,首选引理化是有意义的。TF-IDF 分数在最终 PandasDataFrame对象中按升序排序。更高的 TF-IDF 分数表示更重要的单词。

另见

识别命名实体

命名实体识别 ( NER )试图检测文本中的人名、组织名、地点名和其他名称。有些 NER 系统几乎和人类一样好,但这不是一件容易的事情。命名实体通常以大写字母开头,例如 Ivan。因此,在应用 NER 时,我们不应该改变词语的大小写。

NLTK 支持斯坦福 NER 应用编程接口。这是一个 Java API,所以你的系统上需要有 Java。我用 Java 1.8.0_65 测试了代码。本食谱中的代码下载了截至 2015 年 10 月的最新斯坦福 NER 档案(stanford-ner-2015-04-20.zip/3.5.2)。如果你想要另一个版本,看看http://nlp.stanford.edu/software/CRF-NER.shtml(检索 2015 年 10 月)。

做好准备

按照简介部分的说明安装 NLTK。您可能还需要安装 Java。

怎么做...

脚本在本书代码包的named_entity.py文件中:

  1. 进口如下:

    from nltk.tag.stanford import NERTagger
    import dautil as dl
    import os
    from zipfile import ZipFile
    from nltk.corpus import brown
    
  2. 定义以下功能下载 NER 档案:

    def download_ner():
        url = 'http://nlp.stanford.edu/software/stanford-ner-2015-04-20.zip'
        dir = os.path.join(dl.data.get_data_dir(), 'ner')
    
        if not os.path.exists(dir):
            os.mkdir(dir)
    
        fname = 'stanford-ner-2015-04-20.zip'
        out = os.path.join(dir, fname)
    
        if not dl.conf.file_exists(out):
            dl.data.download(url, out)
    
            with ZipFile(out) as nerzip:
                nerzip.extractall(path=dir)
    
        return os.path.join(dir, fname.replace('.zip', ''))
    
  3. 将 NER 应用于布朗语料库中的一个文件:

    dir = download_ner()
    st = NERTagger(os.path.join(dir, 'classifiers',
                                'english.all.3class.distsim.crf.ser.gz'),
                   os.path.join(dir, 'stanford-ner.jar'))
    fid = brown.fileids(categories='news')[0]
    printer = dl.log_api.Printer(nelems=9)
    
    tagged = [pair for pair in dl.collect.flatten(st.tag(brown.words(fid)))
              if pair[1] != 'O']
    printer.print(tagged)
    

有关最终结果,请参考以下屏幕截图:

How to do it...

它是如何工作的

我们通过指定一个预先训练好的分类器和 NER JAR (Java 档案)创建了一个NerTagger对象。分类器在我们的语料库中将单词标记为组织、位置、人或其他。分类是区分大小写的,这意味着如果你把所有的单词都小写,你会得到不同的结果。

另见

利用非负矩阵分解提取主题

自然语言处理中的主题并不完全符合字典的定义,而是更符合一个模糊的统计概念。我们谈到话题模型和与话题相关的单词的概率分布,正如我们所知。当我们阅读文本时,我们期望出现在标题或正文中的某些单词能够捕捉文档的语义上下文。一篇关于 Python 编程的文章会有“类”和“函数”这样的词,而一个关于蛇的故事会有“蛋”和“害怕”这样的词文本通常有多个主题;例如,这个食谱是关于主题模型和非负矩阵分解,我们将很快讨论。因此,我们可以通过给主题分配不同的权重来为主题定义一个加法模型。

主题建模算法之一是非负矩阵分解 ( NMF )。该算法将一个矩阵分解为两个矩阵的乘积,使得两个矩阵没有负值。通常,我们只能在数值上逼近因式分解的解,时间复杂度是多项式的。scikit-learn NMF类实现了这个算法。NMF 还可以应用于文档聚类和信号处理。

怎么做...

我们将重用来自词干、引理、过滤和 TF-IDF 评分方法的结果:

  1. 进口情况如下:

    from sklearn.decomposition import NMF
    import ch8util
    
  2. 从泡菜加载 TF-IDF 矩阵和单词:

    terms = ch8util.load_terms()
    tfidf = ch8util.load_tfidf()
    
  3. 将主题可视化为高级单词列表:

    nmf = NMF(n_components=44, random_state=51).fit(tfidf)
    
    for topic_idx, topic in enumerate(nmf.components_):
        label = '{}: '.format(topic_idx)
        print(label, " ".join([terms[i] for i in topic.argsort()[:-9:-1]]))
    

有关最终结果,请参考以下屏幕截图:

How to do it...

代码在本书代码包的topic_extraction.py文件中。

它是如何工作的

NMF类有一个components_属性,保存数据的非负成分。我们选择了components_属性中最高值对应的单词。如你所见,话题多种多样,虽然有点过时。

另见

实现基本术语数据库

众所周知,自然语言处理有许多应用:

  • 商业和开源搜索引擎实现的全文搜索
  • 文档聚类
  • 分类,例如在产品评论的上下文中确定文本类型或情感

为了执行这些任务,我们需要计算特征,如 TF-IDF 分数(参考词干、引理、过滤和 TF-IDF 分数)。特别是在大数据集的情况下,存储特征以便于处理是有意义的。搜索引擎使用倒排索引,将单词映射到网页。这类似于关联表模式(参见实现关联表)。

我们将用三个表实现关联表模式。一个表包含单词,另一个表将实现三个表的关联表模式。一个表包含单词,另一个表保存关于文档的信息,第三个表链接其他两个表,如下图所示:

Implementing a basic terms database

怎么做...

程序在本书代码包的terms_database.py文件中:

  1. 进口为如下:

    from sqlalchemy.ext.declarative import declarative_base
    from sqlalchemy import Column
    from sqlalchemy import ForeignKey
    from sqlalchemy import Float
    from sqlalchemy import Integer
    from sqlalchemy import String
    from sqlalchemy.orm import backref
    from sqlalchemy.orm import relationship
    import os
    import dautil as dl
    from nltk.corpus import brown
    from sqlalchemy import func
    import ch8util
    
    Base = declarative_base()
    
  2. 为文本文档定义以下类别:

    class Text(Base):
        __tablename__ = 'texts'
        id = Column(Integer, primary_key=True)
        file = Column(String, nullable=False, unique=True)
        terms = relationship('Term', secondary='text_terms')
    
        def __repr__(self):
            return "Id=%d file=%s" % (self.id, self.file)
    
  3. 为文章中的单词定义以下类别:

    class Term(Base):
        __tablename__ = 'terms'
        id = Column(Integer, primary_key=True)
        word = Column(String, nullable=False, unique=True)
    
        def __repr__(self):
            return "Id=%d word=%s" % (self.id, self.word)
    
  4. 为文档和单词的关联定义以下类:

    class TextTerm(Base):
        __tablename__ = 'text_terms'
        text_id = Column(Integer, ForeignKey('texts.id'), primary_key=True)
        term_id = Column(Integer, ForeignKey('terms.id'), primary_key=True)
        tf_idf = Column(Float)
        text = relationship('Text', backref=backref('term_assoc'))
        term = relationship('Term', backref=backref('text_assoc'))
    
        def __repr__(self):
            return "text_id=%s term_id=%s" % (self.text_id, self.term_id)
    
  5. 定义以下功能在文本表中插入条目:

    def populate_texts(session):
        if dl.db.not_empty(session, Text):
            # Cowardly refusing to continue
            return
    
        fids = brown.fileids(categories='news')
    
        for fid in fids:
            session.add(Text(file=fid))
    
        session.commit()
    
  6. 定义以下函数在术语表中插入条目:

    def populate_terms(session):
        if dl.db.not_empty(session, Term):
            # Cowardly refusing to continue
            return
    
        terms = ch8util.load_terms()
    
        for term in terms:
            session.add(Term(word=term))
    
        session.commit()
    
  7. 定义以下函数在关联表中插入条目:

    def populate_text_terms(session):
        if dl.db.not_empty(session, TextTerm):
            # Cowardly refusing to continue
            return
    
        text_ids = dl.collect.flatten(session.query(Text.id).all())
        term_ids = dl.collect.flatten(session.query(Term.id).all())
    
        tfidf = ch8util.load_tfidf()
        logger = dl.log_api.conf_logger(__name__)
    
        for text_id, row, in zip(text_ids, tfidf):
            logger.info('Processing {}'.format(text_id))
            arr = row.toarray()[0]
            session.get_bind().execute(
                TextTerm.__table__.insert(),
                [{'text_id': text_id, 'term_id': term_id,
                  'tf_idf': arr[i]}
                 for i, term_id in enumerate(term_ids)
                 if arr[i] > 0]
            )
    
        session.commit()
    
  8. 定义以下功能,使用关键词进行搜索:

    def search(session, keywords):
        terms = keywords.split()
        fsum = func.sum(TextTerm.tf_idf)
    
        return session.query(TextTerm.text_id, fsum).\
            join(Term, TextTerm).\
            filter(Term.word.in_(terms)).\
            group_by(TextTerm.text_id).\
            order_by(fsum.desc()).all()
    
  9. 用下面的代码调用我们定义的函数:

    if __name__ == "__main__":
        dbname = os.path.join(dl.data.get_data_dir(), 'news_terms.db')
        session = dl.db.create_session(dbname, Base)
        populate_texts(session)
        populate_terms(session)
        populate_text_terms(session)
        printer = dl.log_api.Printer()
        printer.print('id, tf_idf', search(session, 'baseball game'))
    

我们搜索了“棒球比赛”有关最终结果(文件标识和 TF-IDF 总和),请参考以下屏幕截图:

How to do it...

它是如何工作的

我们使用关联表数据库模式存储 TF-IDF 分数。作为使用数据库的一个例子,我们查询了“棒球比赛”查询在术语表中查找两个单词的标识,然后在关联表中汇总相关的 TF-IDF 分数。总和作为相关性得分。然后,我们给出了相应的文件标识与相关性得分的降序排列。如果您向最终用户显示结果,您必须至少再做一次查询,才能用文件名替换文件标识。碰巧,我们正在分析的文件被命名为ca01ca44,所以查询不是严格必要的。

因为我已经有了 TF-IDF 分数,所以我发现直接存储它们很方便。但是,您也可以决定存储术语频率和反向文档频率,并从中得出 TF-IDF 分数。您只需要确定每个新文档的术语频率和文档中的单词。添加或删除文档时,需要更新所有反向文档频率。然而,通过关联表建立链接已经足以计算术语频率、反向文档频率和 TF-IDF 分数。

另见

计算社交网络密度

人类是群居动物,因此,社会关系非常重要。我们可以将这些联系和相关人员视为一个网络。我们将网络或子集表示为图形。图由通过边或线连接的节点或点组成。图形可以是有向的,也可以是无向的,线条可以是箭头。

我们将使用脸书 SPAN 数据,我们也在可视化网络图与蜂巢图配方中使用了该数据。脸书在 2004 年起步时规模很小,但截至 2015 年,其用户已超过 10 亿。数据没有包括所有的用户,但是对于一个像样的分析来说还是足够的。以下方程描述了无向(8.1)和有向(8.2)图的密度:

Computing social network density

在这些等式中, n 是节点的数量, m 是边的数量。

做好准备

按照简介部分的说明安装网络。

怎么做...

代码在本书代码包的net_density.ipynb文件中:

  1. 进口情况如下:

    import networkx as nx
    import dautil as dl
    
  2. 创建一个网络图,如下所示:

    fb_file = dl.data.SPANFB().load()
    G = nx.read_edgelist(fb_file,
                         create_using=nx.Graph(),
                         nodetype=int)
    
  3. 调用density()功能如下:

    print('Density', nx.density(G))
    

我们得到以下密度:

Density 0.010819963503439287

另见

计算社交网络亲密度中心度

在像脸书 SPAN 数据这样的社交网络中,我们会有有影响力的人。在图形术语中,这些是有影响力的节点。中心性找到重要节点的特征。贴近度中心度使用节点间最短路径作为特征,如下式所示:

Calculating social network closeness centrality

在(8.3)中, d(u,v)uv 之间的最短路径, n 是节点数。一个有影响力的节点离其他节点很近,因此最短路径的总和很低。我们可以分别计算每个节点的贴近度中心度,对于一个大图来说,这可能是一个冗长的计算。NetworkX 允许我们指定我们感兴趣的节点,所以我们将只计算几个节点的接近度中心度。

做好准备

按照简介部分的说明安装网络。

怎么做...

请看一下本书代码包中的close_centrality.ipynb文件:

  1. 进口情况如下:

    import networkx as nx
    import dautil as dl
    
  2. 根据脸书 SPAN 数据创建一个网络图,如下所示:

    fb_file = dl.data.SPANFB().load()
    G = nx.read_edgelist(fb_file,
                         create_using=nx.Graph(),
                         nodetype=int)
    
  3. 计算节点1和节点4037的贴近度中心度:

    print('Closeness Centrality Node 1',
          nx.closeness_centrality(G, 1))
    print('Closeness Centrality Node 4037',
          nx.closeness_centrality(G, 4037))
    

对于脸书跨度数据,我们得到以下结果:

Closeness Centrality Node 1 0.2613761408505405
Closeness Centrality Node 4037 0.18400546821599453

另见

确定中间中心性

介数中心性是一种类似于接近中心性的中心性(参考计算社交网络接近中心性食谱)。该指标由以下等式给出:

Determining the betweenness centrality

它是通过一个节点的所有可能的最短路径对的分数的总和。

做好准备

按照简介部分的说明安装网络。

怎么做...

脚本在本书代码包的between_centrality.ipynb文件中:

  1. 进口情况如下:

    import networkx as nx
    import dautil as dl
    import pandas as pd
    
  2. 将脸书 SPAN 数据载入网络图:

    fb_file = dl.data.SPANFB().load()
    G = nx.read_edgelist(fb_file,
                         create_using=nx.Graph(),
                         nodetype=int)
    
  3. k = 256(要使用的节点数)计算中间中心度,并将结果存储在 PandasDataFrame对象中:

    key_values = nx.betweenness_centrality(G, k=256)
    df = pd.DataFrame.from_dict(key_values, orient='index')
    
    dl.options.set_pd_options()
    print('Betweenness Centrality', df)
    

有关最终结果,请参考以下屏幕截图:

How to do it...

另见

估计平均聚类系数

从幼儿园开始,我们就有了朋友,亲密的朋友,永远最好的朋友,社交媒体的朋友,以及其他的朋友。因此,社交网络图应该有团块,不像你在高中派对上看到的那样。自然产生的问题是,如果我们只是邀请一群随机的陌生人参加聚会,或者在网上重新创建这个设置,会发生什么?我们希望陌生人联系的概率比朋友低。在图论中,这个概率是由 聚类系数来衡量的。

平均聚类系数是聚类系数的局部(单节点)版本。这个度量的定义考虑了由节点组成的三角形。有了三个节点,我们可以形成一个三角形,例如,三个火枪手。如果我们在混合中加入达塔格南,更多的三角形是可能的,但不是所有的三角形都必须实现。达塔尼昂可能会和三个火枪手打起来。在(8.5)中,我们将聚类系数定义为已实现三角形和可能三角形的比率以及平均聚类系数(8.6):

Estimating the average clustering coefficient

做好准备

按照简介部分的说明安装网络。

怎么做...

脚本在本书代码包的avg_clustering.ipynb文件中:

  1. 进口情况如下:

    import networkx as nx
    import dautil as dl
    
  2. 将脸书跨度数据加载到网络图中:

    fb_file = dl.data.SPANFB().load()
    G = nx.read_edgelist(fb_file,
                         create_using=nx.Graph(),
                         nodetype=int)
    
  3. 计算平均聚类系数如下:

    print('Average Clustering',
          nx.average_clustering(G))
    

对于脸书跨度数据,我们得到以下结果:

Average Clustering 0.6055467186200871

另见

计算图的分类系数

在图论中,相似度是通过度分布来衡量的。是一个节点到其他节点的连接数。在有向图中,我们有传入和传出的连接以及相应的度数和度数。朋友往往有共同点。在图论中,这个趋势由配合度系数来衡量。该系数是一对节点之间的皮尔逊相关系数,如下式所示:

Calculating the assortativity coefficient of a graph

qk (剩余度分布)是离开节点 k 的连接数。 ejk 是节点对剩余度数的联合概率分布。

做好准备

按照简介部分的说明安装网络。

怎么做...

代码在本书代码包的assortativity.ipynb文件中:

  1. 进口情况如下:

    import networkx as nx
    import dautil as dl
    
  2. 将脸书跨度数据加载到网络图中:

    fb_file = dl.data.SPANFB().load()
    G = nx.read_edgelist(fb_file,
                         create_using=nx.Graph(),
                         nodetype=int)
    
  3. 计算分类系数如下:

    print('Degree Assortativity Coefficient',
          nx.degree_assortativity_coefficient(G))
    

对于脸书跨度数据,我们得到以下结果:

Degree Assortativity Coefficient 0.0635772291856

另见

求图的团数

一个完全图是一个在中的图,其中每对节点通过唯一的连接连接。一个小团体 是一个完整的子图。这相当于拉帮结派的一般概念,每个人都认识所有其他人。最大团是节点最多的团。团数是最大团中的节点数。不幸的是,找到团数需要很长时间,所以我们不会使用完整的脸书 SPAN 数据。

做好准备

按照简介部分的说明安装网络。

怎么做...

代码在本书代码包的clique_number.py文件中:

  1. 进口情况如下:

    import networkx as nx
    import dautil as dl
    
  2. 将脸书跨度数据加载到网络图中:

    fb_file = dl.data.SPANFB().load()
    G = nx.read_edgelist(fb_file,
                         create_using=nx.Graph(),
                         nodetype=int)
    
  3. 确定子图的团数:

    print('Graph Clique Number',
          nx.graph_clique_number(G.subgraph(list(range(2048)))))
    

对于部分脸书跨度数据,我们得到以下结果:

Graph Clique Number 38

另见

创建具有余弦相似性的文档图

互联网是一个相互链接的大型文档网络。我们可以将其视为文档图,其中每个节点对应一个文档。您将期望文档链接到类似的文档;但是,网页有时会链接到其他不相关的网页。这可能是错误的或故意的,例如在广告或试图提高搜索引擎排名的背景下。像维基百科这样更值得信赖的来源可能会产生更好的图表。然而,一些维基百科页面是非常基本的存根,所以我们可能会错过高质量的链接。

余弦相似度是一种常用的距离度量,用来衡量两个文档的相似度。对于这个度量,我们需要计算两个特征向量的内积。向量的余弦相似性对应于向量之间角度的余弦,因此得名。余弦相似度由以下等式给出:

Creating a document graph with cosine similarity

该配方中的特征向量是对应于文档的 TF-IDF 分数。文档与其自身的余弦相似度等于 1(零角度);因此,对于相似的文档,余弦相似度应该尽可能接近 1。

我们将执行以下步骤来创建布朗语料库中新闻文章的文档图:

  1. 使用我们从词干、引理、过滤和配方的 TF-IDF 分数代码中存储的 TF-IDF 分数计算余弦相似度。结果类似于相关矩阵。
  2. 对于每个文档,在图中为每个文档添加一个连接,这是非常相似的。我用相似度的第 90 个百分位数作为阈值;但是,如果您愿意,可以使用另一个值。
  3. 对于每个文档,使用 TF-IDF 分数选择前三个单词。我用这些词来注释文档节点。
  4. 如本章所述,使用网络计算图形指标。

怎么做...

创建余弦相似性文档图的代码在本书代码包的cos_similarity.ipynb文件中:

  1. 进口情况如下:

    from sklearn.metrics.pairwise import cosine_similarity
    import networkx as nx
    import matplotlib.pyplot as plt
    import numpy as np
    import dautil as dl
    import ch8util
    
  2. 定义以下功能,将节点添加到每个文档前三个最重要的单词注释的网络图中:

    def add_nodes(G, nodes, start, terms):
        for n in nodes:
            words = top_3_words(tfidf, n, terms)
            G.add_node(n, words='{0}: {1}'.
                       format(n, " ".join(words.tolist())))
            G.add_edge(start, n)
    
  3. 定义以下函数来查找文档的前三个单词:

    def top_3_words(tfidf, row, terms):
        indices = np.argsort(tfidf[row].toarray().ravel())[-3:]
    
        return terms[indices]
    
  4. 加载必要的数据,计算余弦相似度,并创建一个网络图:

    tfidf = ch8util.load_tfidf()
    terms = ch8util.load_terms()
    
    sims = cosine_similarity(tfidf, tfidf)
    G = nx.Graph()
    
  5. 迭代余弦相似度,并向图中添加节点:

    for i, row in enumerate(sims):
        over_limit = np.where(row > np.percentile(row, 90))[0]
        nodes = set(over_limit.tolist())
        nodes.remove(i)
        add_nodes(G, nodes, i, terms)
    
  6. 绘制图表并使用网络打印一些指标:

    labels = nx.get_node_attributes(G, 'words')
    nx.draw_networkx(G, pos=nx.spring_layout(G), labels=labels)
    plt.axis('off')
    plt.title('Graph of News Articles in the Brown Corpus')
    print('Density', nx.density(G))
    print('Average Clustering',
          nx.average_clustering(G))
    print('Degree Assortativity Coefficient',
          nx.degree_assortativity_coefficient(G))
    print('Graph Clique Number', nx.graph_clique_number(G))
    

最终结果参见以下截图:

How to do it...

另见

九、集成学习和降维

在本章中,我们将介绍以下食谱:

  • 递归消除特征
  • 应用主成分分析进行降维
  • 应用线性判别分析进行降维
  • 多个模型的堆叠和多数投票
  • 随机森林学习
  • 用 RANSAC 算法拟合噪声数据
  • 打包以提高结果
  • 促进更好的学习
  • 嵌套交叉验证
  • 使用 joblib 重用模型
  • 分层聚类数据
  • 进行一次大西洋之旅

简介

在 1983 年的战争游戏电影中,一台电脑做出了可能导致第三次世界大战的生死决定。据我所知,当时的技术还不能完成这样的壮举。然而,在 1997 年,深蓝超级计算机确实击败了一位世界象棋冠军。2005 年,一辆斯坦福自动驾驶汽车在沙漠中独自行驶了 130 多公里。2007 年,另一个车队的车在正常交通中行驶了 50 多公里。2011 年,沃森电脑在与人类对手的竞赛中获胜。如果我们假设计算机硬件是限制因素,那么我们可以尝试推断未来。雷·库兹韦尔做到了这一点,根据他的说法,我们可以预计人类水平的智力将在 2029 年左右。

在这一章中,我们将集中讨论更简单的预测第二天天气的问题。我们将假设今天的天气取决于昨天的天气。理论上,如果一只蝴蝶在一个位置扇动翅膀,这可能会引发一连串的事件,在几千公里外的地方引发一场暴风雪(蝴蝶效应)。这并非不可能,但非常不可能。然而,如果我们有很多这样的事件,类似的情况会比你想象的更频繁地发生。

不可能考虑所有可能的因素。事实上,我们会试图通过忽略一些我们现有的数据来让我们的生活变得更容易。我们将应用分类和回归算法,以及层次聚类。让我们将结果评估推迟到第 10 章、评估分类器、回归器和聚类。如果你对分类食谱中提到的混淆矩阵感到好奇,请直接跳到用混淆矩阵食谱进行分类。

事实上,如今大多数人工智能系统并不那么智能。法庭上的法官可能会做出错误的决定,因为他或她有偏见或者过得不好。一组多位评委应该表现更好。这相当于一个机器学习项目,我们担心过度拟合和欠拟合。集成学习就是这个难题的解决方案,它基本上意味着以一种巧妙的方式组合多个学习者。

本章的主要部分是关于 超参数优化——这些是分类器和回归器的参数。为了检查过拟合或欠拟合,我们可以使用学习曲线,该曲线显示训练和不同训练集大小的测试分数。我们也可以通过验证曲线改变单个超参数的值。

递归消除特征

如果我们有许多特性(解释变量),那么将它们都包含在我们的模型中是很有诱惑力的。然而,我们会冒过度拟合的风险——得到一个对训练数据非常有效而对看不见的数据非常无效的模型。不仅如此,模型必然会相对较慢,需要大量内存。我们必须权衡准确性(或其他指标)与速度和内存需求。

我们可以尝试忽略特征或创建新的更好的复合特征。例如,在在线广告中,通常使用比率,例如与广告相关的浏览量和点击量的比率。常识或领域知识可以帮助我们选择特征。在最坏的情况下,我们可能不得不依赖相关性或其他统计方法。scikit-learn 库提供了RFE类(递归特征消除),可以自动选择特征。我们将在这个食谱中使用这个课程。我们还需要一个外部估计器。RFE类是相对较新的,不幸的是,不能保证所有的评估人员都能与RFE类一起工作。

怎么做...

  1. 进口情况如下:

    from sklearn.feature_selection import RFE
    from sklearn.svm import SVC
    from sklearn.svm import SVR
    from sklearn.preprocessing import MinMaxScaler
    import dautil as dl
    import warnings
    import numpy as np
    
  2. 创建一个 SVC 分类器和一个 RFE 对象,如下所示:

    warnings.filterwarnings("ignore", category=DeprecationWarning)
    clf = SVC(random_state=42, kernel='linear')
    selector = RFE(clf)
    
  3. 加载数据,使用MinMaxScaler功能进行缩放,并添加一年中的某一天作为特征:

    df = dl.data.Weather.load().dropna()
    df['RAIN'] = df['RAIN'] == 0
    df['DOY'] = [float(d.dayofyear) for d in df.index]
    scaler = MinMaxScaler()
    
    for c in df.columns:
        if c != 'RAIN':
            df[c] = scaler.fit_transform(df[c])
    
  4. 打印数据的第一行作为健全性检查:

    dl.options.set_pd_options()
    print(df.head(1))
    X = df[:-1].values
    np.set_printoptions(formatter={'all': '{:.3f}'.format})
    print(X[0])
    np.set_printoptions()
    
  5. 使用有雨或无雨作为类别来确定对功能的支持和排名(在分类的上下文中):

    y = df['RAIN'][1:].values
    selector = selector.fit(X, y)
    print('Rain support', df.columns[selector.support_])
    print('Rain rankings', selector.ranking_)
    
  6. 使用温度作为特征来确定特征的支持度和等级:

    reg = SVR(kernel='linear')
    selector = RFE(reg)
    y = df['TEMP'][1:].values
    selector = selector.fit(X, y)
    print('Temperature support', df.columns[selector.support_])
    print('Temperature ranking', selector.ranking_)
    

最终结果参见以下截图:

How to do it...

本食谱的代码在本书代码包的feature_elimination.py文件中。

它是如何工作的

RFE类默认选择一半的特征。算法如下:

  1. 根据数据训练外部估计器,并为特征分配权重。
  2. 权重最小的要素将被移除。
  3. 重复该过程,直到我们拥有必要数量的功能。

另见

应用主成分分析进行降维

主成分分析 ( PCA )由卡尔·皮尔逊于 1901 年发明,是一种将数据转换为不相关的正交特征的算法,称为主成分。主分量是协方差矩阵的特征向量。

有时,我们通过在应用主成分分析之前缩放数据来获得更好的结果,尽管这不是严格必要的。我们可以将主成分分析解释为将数据投影到更低维度的空间。一些主成分贡献的信息相对较少(低方差);因此,我们可以省略它们。我们有以下转变:

Applying principal component analysis for dimension reduction

结果是矩阵 T L ,行数与原始矩阵相同,但列数较低。

当然,降维对于可视化和建模以及减少过度拟合的机会是有用的。事实上,有一种技术叫做主成分回归 ( 聚合酶链反应),它利用了这个原理。简而言之,聚合酶链反应执行以下步骤:

  1. 使用主成分分析将数据转换到低维空间。
  2. 在新空间中执行线性回归。
  3. 将结果转换回原始坐标系。

怎么做...

  1. 进口情况如下:

    import dautil as dl
    from sklearn.decomposition import PCA
    import matplotlib.pyplot as plt
    from sklearn.preprocessing import scale
    
  2. 按如下方式加载数据,并按一年中的某一天分组:

    df = dl.data.Weather.load().dropna()
    df = dl.ts.groupby_yday(df).mean()
    X = df.values
    
  3. 应用主成分分析将数据投影到二维空间:

    pca = PCA(n_components=2)
    X_r = pca.fit_transform(scale(X)).T
    
  4. 绘制转换结果:

    plt.scatter(X_r[0], X_r[1])
    plt.xlabel('x')
    plt.ylabel('y')
    plt.title('Dimension Reducion with PCA')
    

有关最终结果,请参考以下屏幕截图:

How to do it...

代码在本书代码包的applying_pca.ipynb文件中。

另见

应用线性判别分析进行降维

线性判别分析 ( LDA )是一种寻找特征线性组合以区分类别的算法。通过投影到低维子空间,它可以用于分类或降维。LDA 需要一个目标属性来进行分类和降维。

如果我们将类密度表示为多元高斯分布,那么线性判别分析假设类具有相同的协方差矩阵。我们可以使用训练数据来估计类分布的参数。

在 scikit-learn 中,lda.LDA在 0.17 中已被弃用,并重新命名为discriminant_analysis.LinearDiscriminantAnalysis。这个类的默认解算器使用奇异值分解,不需要计算协方差矩阵,因此速度很快。

怎么做...

代码在本书代码包的applying_lda.ipynb文件中:

  1. 进口情况如下:

    import dautil as dl
    from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
    import matplotlib.pyplot as plt
    
  2. 加载数据如下:

    df = dl.data.Weather.load().dropna()
    X = df.values
    y = df['WIND_DIR'].values
    
  3. 应用 LDA 将数据投影到二维空间:

    lda = LinearDiscriminantAnalysis(n_components=2)
    X_r = lda.fit(X, y).transform(X).T
    
  4. 绘制转换结果:

    plt.scatter(X_r[0], X_r[1])
    plt.xlabel('x')
    plt.ylabel('y')
    plt.title('Dimension Reduction with LDA')
    

有关最终结果,请参考以下屏幕截图:

How to do it...

另见

多模型叠加和多数投票

一般认为,两个人单独认识的不止一个人。民主应该比独裁更有效。在机器学习中,我们没有人类做决定,而是算法。当我们有多个分类器或回归器一起工作时,我们说的是 集成学习

有很多集成学习方案。最简单的设置是对分类进行多数投票,对回归进行平均。在 scikit-learn 0.17 中,可以使用VotingClassifier类进行多数投票。该分类器允许您使用权重来强调或抑制分类器。

堆叠获取机器学习估计器的输出,然后将这些输出用作另一个算法的输入。当然,您可以将高级算法的输出馈送给另一个预测器。可以使用任意拓扑,但出于实际原因,您应该首先尝试简单的设置。

怎么做...

  1. 进口情况如下:

    import dautil as dl
    from sklearn.tree import DecisionTreeClassifier
    import numpy as np
    import ch9util
    from sklearn.ensemble import VotingClassifier
    from sklearn.grid_search import GridSearchCV
    from IPython.display import HTML
    
  2. 加载数据并创建三个决策树分类器:

    X_train, X_test, y_train, y_test = ch9util.rain_split()
    default = DecisionTreeClassifier(random_state=53, min_samples_leaf=3,
                                     max_depth=4)
    entropy = DecisionTreeClassifier(criterion='entropy',
                                     min_samples_leaf=3, max_depth=4,
                                     random_state=57)
    random = DecisionTreeClassifier(splitter='random', min_samples_leaf=3,
                                    max_depth=4, random_state=5)
    
  3. 使用分类器进行投票:

    clf = VotingClassifier([('default', default), 
                            ('entropy', entropy), ('random', random)])
    params = {'voting': ['soft', 'hard'],
             'weights': [None, (2, 1, 1), (1, 2, 1), (1, 1, 2)]}
    gscv = GridSearchCV(clf, param_grid=params, n_jobs=-1, cv=5)
    gscv.fit(X_train, y_train)
    votes = gscv.predict(X_test)
    
    preds = []
    
    for clf in [default, entropy, random]:
        clf.fit(X_train, y_train)
        preds.append(clf.predict(X_test))
    
    preds = np.array(preds)
    
  4. 绘制基于投票的预测的混淆矩阵:

    %matplotlib inline
    context = dl.nb.Context('stacking_multiple')
    dl.nb.RcWidget(context)
    
    sp = dl.plotting.Subplotter(2, 2, context)
    html = ch9util.report_rain(votes, y_test, gscv.best_params_, sp.ax)
    sp.ax.set_title(sp.ax.get_title() + ' | Voting')
    
  5. 绘制基于堆叠的预测的混淆矩阵:

    default.fit(preds_train.T, y_train)
    stacked_preds = default.predict(preds.T)
    html += ch9util.report_rain(stacked_preds, 
                                y_test, default.get_params(), sp.next_ax())
    sp.ax.set_title(sp.ax.get_title() + ' | Stacking')
    ch9util.report_rain(default.predict(preds.T), y_test)
    
  6. 绘制投票和堆叠分类器的学习曲线【T1:

    ch9util.plot_learn_curve(sp.next_ax(), gscv.best_estimator_, X_train,
                             y_train, title='Voting')
    
    ch9util.plot_learn_curve(sp.next_ax(), default, X_train,
                             y_train, title='Stacking')
    

有关最终结果,请参考以下屏幕截图:

How to do it...

代码是本书代码包中stacking_multiple.ipynb文件中的。

另见

随机森林学习

if a: else b语句是 Python 编程中最常见的语句之一。通过嵌套和组合这样的语句,我们可以构建一个所谓的决策树。这类似于老式的流程图,尽管流程图也允许循环。决策树在机器学习中的应用叫做 决策树学习。决策树学习中的树的末端节点,也称为叶子,包含一个分类问题的类标签。每个非叶节点都与一个包含特征值的布尔条件相关联。

决策树可以用来推导相对简单的规则。能够产生这样的结果,当然是一个巨大的优势。然而,你不得不怀疑这些规则有多好。如果我们添加新数据,我们会得到相同的规则吗?

如果一棵决策树是好的,那么整个森林应该会更好。多棵树应该可以减少过度拟合的机会。然而,在真实的森林中,我们不希望只有一种类型的树。显然,我们将不得不平均或通过多数投票决定什么是适当的结果。

在这个食谱中,我们将应用利奥·布雷曼和阿黛尔·卡特勒发明的 随机森林算法。名称中的“随机”是指从数据中随机选择特征。我们使用所有数据,但不在同一个决策树中。

随机森林也应用 装袋 ( 引导聚集),我们将在装袋中讨论以提高结果配方。决策树的打包包括以下步骤:

  1. 带有替换的示例训练示例,并将其分配到树中。
  2. 根据指定的数据训练树。

我们可以通过交叉验证或绘制测试和训练误差相对于树的数量来确定正确的树的数量。

怎么做...

代码在本书代码包的random_forest.ipynb文件中:

  1. 进口情况如下:

    import dautil as dl
    from sklearn.grid_search import GridSearchCV
    from sklearn.ensemble import RandomForestClassifier
    import ch9util
    import numpy as np
    from IPython.display import HTML
    
  2. 加载数据,做如下预测:

    X_train, X_test, y_train, y_test = ch9util.rain_split()
    clf = RandomForestClassifier(random_state=44)
    params = {
        'max_depth': [2,  4],
        'min_samples_leaf': [1, 3],
        'criterion': ['gini', 'entropy'],
        'n_estimators': [100, 200]
    }
    
    rfc = GridSearchCV(estimator=RandomForestClassifier(),
                       param_grid=params, cv=5, n_jobs=-1)
    rfc.fit(X_train, y_train)
    preds = rfc.predict(X_test)
    
  3. 将降雨预报混淆矩阵绘制如下:

    sp = dl.plotting.Subplotter(2, 2, context)
    html = ch9util.report_rain(preds, y_test, rfc.best_params_, sp.ax)
    
  4. 绘制一系列森林大小的验证曲线:

    ntrees = 2 ** np.arange(9)
    ch9util.plot_validation(sp.next_ax(), rfc.best_estimator_, 
                            X_train, y_train, 'n_estimators', ntrees)
    
  5. 绘制深度范围的验证曲线:

    depths = np.arange(2, 9)
    ch9util.plot_validation(sp.next_ax(), rfc.best_estimator_, 
                            X_train, y_train, 'max_depth', depths)
    
  6. 绘制最佳估计量的学习曲线:

    ch9util.plot_learn_curve(sp.next_ax(), 
                             rfc.best_estimator_, X_train,y_train)
    HTML(html + sp.exit())
    

有关最终结果,请参考以下屏幕截图:

How to do it...

还有更多…

随机森林分类被认为是一种通用的算法,我们几乎可以将其用于任何分类任务。遗传 算法遗传编程一般可以做一个网格搜索或者优化。

我们可以把程序看作是一个生成结果的操作符和操作数的序列。当然,这是一个非常简化的编程模型。然而,在这样的模型中,有可能使用模仿生物学理论的自然选择来进化程序。遗传程序是自我修改的,具有巨大的适应性,但我们得到的决定论水平较低。

TPOT 项目试图发展机器学习管道(目前使用少量分类器,包括随机森林)。我在 GitHub 上分叉了 TPOT 0.1.3 并做了一些修改。TPOT 使用deap作为遗传编程部件,可以安装如下:

$ pip install deap

我用 deap 1.0.2 测试了代码。在标签r1下安装我的更改,如下所示:

$ git clone git@github.com:ivanidris/tpot.git
$ cd tpot
$ git checkout r1
$ python setup.py install

您也可以从https://github.com/ivanidris/tpot/releases/tag/r1获取代码。本书代码包中rain_pot.py文件的以下代码演示了如何用 TPOT 拟合和评分降雨预测:

import ch9util
from tpot import TPOT

X_train, X_test, y_train, y_test = ch9util.rain_split()
tpot = TPOT(generations=7, population_size=110, verbosity=2)
tpot.fit(X_train, y_train)
print(tpot.score(X_train, y_train, X_test, y_test))

另见

用 RANSAC 算法拟合噪声数据

我们在本书的其他地方讨论了回归背景下的异常值问题(参见本食谱末尾的也参见部分)。问题很明显——异常值使得我们很难恰当地拟合我们的模型。随机样本一致性算法 ( RANSAC )尽最大努力以迭代方式拟合我们的数据。RANSAC 是由 Fishler 和 Bolles 在 1981 年推出的。

我们通常对我们的数据有一些了解,例如数据可能遵循正态分布。或者,数据可以是由具有不同特征的多个过程产生的混合。由于数据转换中的故障或错误,我们也可能有异常数据。在这种情况下,识别异常值并对其进行适当处理应该很容易。RANSAC 算法不知道你的数据,但它也假设有内联和外联。

算法经过固定次数的迭代。目标是找到一组指定大小的内联集(共识集)。

RANSAC 执行以下步骤:

  1. 随机选择尽可能小的数据子集并拟合模型。
  2. 检查每个数据点是否与上一步拟合的模型一致。使用残差阈值将不一致的点标记为异常值。
  3. 如果找到足够多的内联,则接受该模型。
  4. 用完全一致集重新估计参数。

scikit-learn RANSACRegressor类可以使用合适的估计器进行拟合。我们将使用默认的LinearRegression估计器。我们还可以指定拟合的最小样本数、残差阈值、异常值的决策函数、决定模型是否有效的函数、最大迭代次数以及共识集中所需的内联数。

怎么做...

代码在本书代码包的fit_ransac.ipynb文件中:

  1. 进口情况如下:

    import ch9util
    from sklearn import linear_model
    from sklearn.grid_search import GridSearchCV
    import numpy as np
    import dautil as dl
    from IPython.display import HTML
    
  2. 加载数据并进行温度预测,如下所示:

    X_train, X_test, y_train, y_test = ch9util.temp_split()
    ransac = linear_model.RANSACRegressor(random_state=27)
    params = {
        'max_trials': [50, 100, 200],
        'stop_probability': [0.98, 0.99]
    }
    
    gscv = GridSearchCV(estimator=ransac, param_grid=params, cv=5)
    gscv.fit(X_train, y_train)
    preds = gscv.predict(X_test)
    
  3. 将预测值与实际值进行散点图:

    sp = dl.plotting.Subplotter(2, 2, context)
    html = ch9util.scatter_predictions(preds, y_test, gscv.best_params_,
                                       gscv.best_score_, sp.ax)
    
  4. 绘制一系列试验号的验证曲线:

    trials = 10 * np.arange(5, 20)
    ch9util.plot_validation(sp.next_ax(), gscv.best_estimator_, 
                            X_train, y_train, 'max_trials', trials)
    
  5. 绘制一系列停止概率的验证曲线:

    probs = 0.01 * np.arange(90, 99)
    ch9util.plot_validation(sp.next_ax(), gscv.best_estimator_, 
                            X_train, y_train, 'stop_probability', probs)
    
  6. 绘制一系列一致集合大小的验证曲线:

    ninliers = 2 ** np.arange(4, 14)
    ch9util.plot_validation(sp.next_ax(), gscv.best_estimator_, 
                            X_train, y_train, 'stop_n_inliers', ninliers)
    HTML(html + sp.exit())
    

有关最终结果,请参考以下屏幕截图:

How to do it...

另见

套袋提高效果

Bootstrap 聚合bagging 是 Leo Breiman 在 1994 年引入的一种算法,它将 Bootstrap 应用于机器学习问题。随机森林学习食谱中也提到了套袋。

该算法旨在通过以下步骤减少过拟合的机会:

  1. 我们通过替换采样从输入训练数据生成新的训练集。
  2. 使模型适合每个生成的训练集。
  3. 通过平均或多数投票来组合模型的结果。

scikit-learn BaggingClassifier类允许我们引导训练示例,我们还可以引导随机森林算法中的特征。当我们执行网格搜索时,我们引用前缀为 base_estimator__的基本估计量的超参数。我们将使用决策树作为基础估计,这样我们就可以重用来自随机森林学习方法的一些超参数配置。

怎么做...

代码在本书代码包的bagging.ipynb文件中:

  1. 进口情况如下:

    import ch9util
    from sklearn.ensemble import BaggingClassifier
    from sklearn.grid_search import GridSearchCV
    from sklearn.tree import DecisionTreeClassifier
    import numpy as np
    import dautil as dl
    from IPython.display import HTML
    
  2. 加载数据并创建BaggingClassifier :

    X_train, X_test, y_train, y_test = ch9util.rain_split()
    clf = BaggingClassifier(base_estimator=DecisionTreeClassifier(
        min_samples_leaf=3, max_depth=4), random_state=43)
    
  3. 网格搜索、拟合和预测如下:

    params = {
        'n_estimators': [320, 640],
        'bootstrap_features': [True, False],
        'base_estimator__criterion': ['gini', 'entropy']
    }
    
    gscv = GridSearchCV(estimator=clf, param_grid=params,
                        cv=5, n_jobs=-1)
    
    gscv.fit(X_train, y_train)
    preds = gscv.predict(X_test)
    
  4. 将降雨预报混淆矩阵绘制如下:

    sp = dl.plotting.Subplotter(2, 2, context)
    html = ch9util.report_rain(preds, y_test, gscv.best_params_, sp.ax)
    
  5. 绘制一系列集合大小的验证曲线:

    ntrees = 2 ** np.arange(4, 11)
    ch9util.plot_validation(sp.next_ax(), gscv.best_estimator_, 
                            X_train, y_train, 'n_estimators', ntrees)
    
  6. 绘制max_samples参数的验证曲线:

    nsamples = 2 ** np.arange(4, 14)
    ch9util.plot_validation(sp.next_ax(), gscv.best_estimator_, 
                            X_train, y_train, 'max_samples', nsamples)
    
  7. 绘制学习曲线如下:

    ch9util.plot_learn_curve(sp.next_ax(), gscv.best_estimator_, 
                             X_train, y_train)
    HTML(html + sp.exit())
    

有关最终结果,请参考以下屏幕截图:

How to do it...

另见

促进更好的学习

数量上的优势是大国比小国更成功的原因。这并不意味着一个大国的人生活得更好。但是从全局来看,个人并没有那么重要,就像在决策树的集合中,如果我们有足够多的树,单棵树的结果可以被忽略。

在分类的背景下,我们将 【弱学习者】定义为只比基线好一点的学习者,例如随机分配班级。虽然学习能力弱的人个人也很弱,就像蚂蚁一样,但是在一起,他们可以像蚂蚁一样做惊人的事情。

使用权重考虑每个学习者的力量是有意义的。这个总的思路叫做 助推。有很多助推算法,其中我们将在本食谱中使用 AdaBoost 。增强算法的不同之处主要在于它们的加权方案。

AdaBoost 使用加权求和来产生最终结果。这是一种自适应算法,试图提高单个训练示例的结果。如果你已经为考试而学习,你可能已经应用了类似的技术,识别你有问题的问题类型,并专注于困难的问题。在 AdaBoost 的情况下,提升是通过调整弱学习者来完成的。

怎么做...

程序在本书代码包的boosting.ipynb文件中:

  1. 进口情况如下:

    import ch9util
    from sklearn.grid_search import GridSearchCV
    from sklearn.ensemble import AdaBoostRegressor
    from sklearn.tree import DecisionTreeRegressor
    import numpy as np
    import dautil as dl
    from IPython.display import HTML
    
  2. 加载数据并创建AdaBoostRegressor类:

    X_train, X_test, y_train, y_test = ch9util.temp_split()
    params = {
        'loss': ['linear', 'square', 'exponential'],
        'base_estimator__min_samples_leaf': [1, 2]
    }
    reg = AdaBoostRegressor(base_estimator=DecisionTreeRegressor(random_state=28),
                            random_state=17)
    
  3. 网格搜索、拟合和预测如下:

    gscv = GridSearchCV(estimator=reg,
                        param_grid=params, cv=5, n_jobs=-1)
    gscv.fit(X_train, y_train)
    preds = gscv.predict(X_test)
    
  4. 将预测值与实际值进行散点图:

    sp = dl.plotting.Subplotter(2, 2, context)
    html = ch9util.scatter_predictions(preds, y_test, gscv.best_params_,  
                                       gscv.best_score_, sp.ax)
    
  5. 绘制一系列集合大小的验证曲线:

    nestimators = 2 ** np.arange(3, 9)
    ch9util.plot_validation(sp.next_ax(), gscv.best_estimator_, 
                            X_train, y_train, 'n_estimators', nestimators)
    
  6. 绘制一系列学习率的验证曲线:

    learn_rate = np.linspace(0.1, 1, 9)
    ch9util.plot_validation(sp.next_ax(), gscv.best_estimator_, 
                            X_train, y_train, 'learning_rate', learn_rate)
    
  7. 绘制学习曲线如下:

    ch9util.plot_learn_curve(sp.next_ax(), gscv.best_estimator_, 
                             X_train, y_train)
    HTML(html + sp.exit())
    

有关最终结果,请参考以下屏幕截图:

How to do it...

另见

嵌套交叉验证

如果我们将数据拟合到一条直线上,数学模型的参数将是直线的斜率和截距。当我们确定模型的参数时,我们在数据的子集(训练集)上拟合模型,并在其余数据(测试集)上评估模型的性能。这叫做 验证还有更精细的方案。例如,scikit-learn GridSearchCV类使用 k 倍交叉验证。

分类器和回归器通常需要额外的参数(超参数),例如集成的组件数量,这通常与第一句中提到的线性模型无关。谈论模型有点令人困惑,因为我们有带有普通参数的模型和带有超参数的更大的模型。

让我们称较大的模型为 2 级模型,尽管据我所知这不是标准术语。如果我们使用GridSearchCV来获得 2 级模型的超参数,我们还有另一组参数(不是超参数或 1 级参数)需要担心——折叠的数量和用于比较的度量。评估指标还没有通过评审(参考第 10 章评估分类器、回归器和聚类,但是比我们在本章中使用的指标更多。此外,我们可能会担心我们是否在用于评估结果的相同数据上确定了超参数。一种解决方案是应用 嵌套交叉验证

嵌套交叉验证由以下交叉验证组成:

  • 内部交叉验证进行超参数优化,例如使用网格搜索
  • 外部交叉验证用于评估绩效和进行统计分析

在这个配方中,我们将查看以下分布:

  • 所有分数的分布
  • GridSearchCV报告的每个外部交叉验证迭代的最佳分数分布
  • 每个折叠的平均分数分布
  • GridSearchCV迭代中分数标准差的分布

怎么做...

代码在本书代码包的nested_cv.ipynb文件中:

  1. 进口情况如下:

    from sklearn.grid_search import GridSearchCV
    from sklearn.cross_validation import ShuffleSplit
    from sklearn.cross_validation import cross_val_score
    import dautil as dl
    from sklearn.ensemble import ExtraTreesRegressor
    from joblib import Memory
    import numpy as np
    from IPython.display import HTML
    
    memory = Memory(cachedir='.')
    
  2. 按照上一节

    @memory.cache
    def get_scores():
        df = dl.data.Weather.load()[['WIND_SPEED', 'TEMP', 'PRESSURE']].dropna()
        X = df.values[:-1]
        y = df['TEMP'][1:]
    
        params = { 'min_samples_split': [1, 3],
                'min_samples_leaf': [3, 4]}
    
        gscv = GridSearchCV(ExtraTreesRegressor(bootstrap=True,
                                                random_state=37),
                            param_grid=params, n_jobs=-1, cv=5)
        cv_outer = ShuffleSplit(len(X), n_iter=500,
                                test_size=0.3, random_state=55)
        r2 = []
        best = []
        means = []
        stds = []
    
        for train_indices, test_indices in cv_outer:
            train_i = X[train_indices], y[train_indices]
            gscv.fit(*train_i)
            test_i = X[test_indices]
            gscv.predict(test_i)
            grid_scores = dl.collect.flatten([g.cv_validation_scores
                for g in gscv.grid_scores_])
            r2.extend(grid_scores)
            means.extend(dl.collect.flatten([g.mean_validation_score
                for g in gscv.grid_scores_]))
            stds.append(np.std(grid_scores))
            best.append(gscv.best_score_)
    
        return {'r2': r2, 'best': best, 'mean': means, 'std': stds}
    

    的描述,得到 R 平方的分数

  3. 获取分数并将其载入 NumPy 数组:

    scores = get_scores()
    r2 = np.array(scores['r2'])
    avgs = np.array(scores['mean'])
    stds = np.array(scores['std'])
    best = np.array(scores['best'])
    
  4. 将分布绘制如下:

    sp = dl.plotting.Subplotter(2, 2, context)
    dl.plotting.hist_norm_pdf(sp.ax, r2)
    sp.label()
    
    dl.plotting.hist_norm_pdf(sp.next_ax(), best)
    sp.label()
    
    dl.plotting.hist_norm_pdf(sp.next_ax(), avgs)
    sp.label()
    
    dl.plotting.hist_norm_pdf(sp.next_ax(), stds)
    sp.label()
    HTML(sp.exit())
    

有关最终结果(交叉验证结果的分布),请参考以下屏幕截图:

How to do it...

另见

使用 joblib 重用模型

joblib Memory类是一个实用程序类,便于将函数或方法结果缓存到磁盘。我们通过指定一个缓存目录来创建一个Memory对象。然后,我们可以修饰函数以进行缓存,或者在类构造函数中指定要缓存的方法。如果愿意,可以指定要忽略的参数。Memory类的默认行为是在函数修改或输入值改变时移除缓存。显然,您也可以通过移动或删除缓存目录和文件来手动删除缓存。

在这个食谱中,我描述了如何重用 scikit-learn 回归器或分类器。天真的方法是将对象存储在标准 Python 泡菜中或使用 joblib。然而,在大多数情况下,最好存储估计量的超参数。

我们将使用ExtraTreesRegressor类作为估计量。额外树 ( 极随机树)是随机森林算法的变种,在随机森林学习食谱中有所涉及。

怎么做...

  1. 进口情况如下:

    from sklearn.grid_search import GridSearchCV
    from sklearn.ensemble import ExtraTreesRegressor
    import ch9util
    from tempfile import mkdtemp
    import os
    import joblib
    
  2. 加载数据并定义超参数网格搜索字典:

    X_train, X_test, y_train, y_test = ch9util.temp_split()
    params = {'min_samples_split': [1, 3],
              'bootstrap': [True, False],
              'min_samples_leaf': [3, 4]}
    
  3. 按照以下步骤进行网格搜索:

    gscv = GridSearchCV(ExtraTreesRegressor(random_state=41),
                        param_grid=params, cv=5)
    
  4. 拟合和预测如下:

    gscv.fit(X_train, y_train)
    preds = gscv.predict(X_test)
    
  5. 存储网格搜索找到的最佳参数:

    dir = mkdtemp()
    pkl = os.path.join(dir, 'params.pkl')
    joblib.dump(gscv.best_params_, pkl)
    params = joblib.load(pkl)
    print('Best params', gscv.best_params_)
    print('From pkl', params)
    
  6. 创建一个新的估计值,并比较预测值:

    est = ExtraTreesRegressor(random_state=41)
    est.set_params(**params)
    est.fit(X_train, y_train)
    preds2 = est.predict(X_test)
    print('Max diff', (preds - preds2).max())
    

最终结果参见以下截图:

How to do it...

代码在本书代码包的reusing_models.py文件中。

另见

分层聚类数据

Python 数据分析中,您学习了聚类——在不提供任何提示的情况下将数据分成聚类——这是无监督学习的一种形式。有时,我们需要猜测集群的数量,就像我们在中使用 Spark 方法对流式数据进行集群一样。

不限制簇包含其他簇。在这种情况下,我们谈到层次聚类。我们需要一个距离度量来分隔数据点。看看下面的等式:

Hierarchically clustering data

在本食谱中,我们将使用 SciPy pdist()函数提供的欧几里德距离(9.2)。点集合之间的距离由链接标准给出。在本食谱中,我们将使用 SciPy linkage()功能提供的单连锁标准(9.3)。

怎么做...

脚本在本书代码包的clustering_hierarchy.ipynb文件中:

  1. 进口情况如下:

    from scipy.spatial.distance import pdist
    from scipy.cluster.hierarchy import linkage
    from scipy.cluster.hierarchy import dendrogram
    import dautil as dl
    import matplotlib.pyplot as plt
    
  2. 加载数据,重采样至年值,计算距离:

    df = dl.data.Weather.load().resample('A').dropna()
    dist = pdist(df)
    
  3. 将分层聚类绘制如下:

    dendrogram(linkage(dist), labels=[d.year for d in df.index],
               orientation='right')
    plt.tick_params(labelsize=8)
    plt.xlabel('Cluster')
    plt.ylabel('Year')
    

有关最终结果,请参考以下屏幕截图:

How to do it...

另见

进行一次茶道旅行

antao 是蒙特利尔的一个机器学习小组创建的 Python 库,通常与深度学习相关联,尽管这不一定是它的核心目的。antio 与 NumPy 紧密集成,可以在 CPU 或 GPU 上运行代码。如果您对图形处理器选项感兴趣,请参考部分列出的文档,另请参见部分。also 还支持通过符号变量进行符号微分。

根据它的文档,antao 是 NumPy 和 SymPy 的杂交。用 Anano 实现机器学习算法是可能的,但它没有使用 scikit-learn 那么容易和方便。但是,您可能会获得更高的并行性和数值稳定性的潜在优势。

在本食谱中,我们将使用梯度下降对温度数据进行线性回归。梯度下降是一种优化算法,我们可以在回归环境中使用它来最小化拟合残差。梯度衡量一个函数有多陡。为了找到一个局部极小值,该算法需要许多与梯度有多陡成正比的步骤。我们正试图走下坡路,但我们不知道在哪个方向可以找到局部最小值。所以,大幅度下跌平均会让我们下跌得更快,但不能保证。在某些情况下,它可能有助于平滑功能(更平滑的山丘),因此我们不会花很多时间振荡。

做好准备

使用以下命令安装天线:

$ pip install --no-deps git+git://github.com/Theano/Theano.git 

截至 2015 年 11 月,我用出血边缘版本测试了代码。

怎么做...

代码在本书代码包的theano_tour.ipynb文件中:

  1. 进口情况如下:

    import theano
    import numpy as np
    import theano.tensor as T
    import ch9util
    from sklearn.cross_validation import train_test_split
    from sklearn.metrics import r2_score
    import dautil as dl
    from IPython.display import HTML
    
  2. 加载温度数据并定义无符号变量:

    temp = dl.data.Weather.load()['TEMP'].dropna()
    X = temp.values[:-1]
    y = temp.values[1:]
    X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=16)
    w = theano.shared(0., name ='w')
    c = theano.shared(0., name ='c')
    
    x = T.vector('x')
    y = T.vector('y')
    
  3. 定义预测和成本(损失)函数以最小化:

    prediction = T.dot(x, w) + c
    cost = T.sum(T.pow(prediction - y, 2))/(2 * X_train.shape[0])
    Define gradient functions as follows:
    gw = T.grad(cost, w)
    gc = T.grad(cost, c)
    
    learning_rate = 0.01
    training_steps = 10000
    
  4. 将训练函数定义如下:

    train = theano.function([x, y], cost, updates =
                            [(w, w - learning_rate * gw),
                             (c, c - learning_rate * gc)])
    predict = theano.function([x], prediction)
    
  5. 按照以下步骤训练估算器:

    for i in range(training_steps):
        train(X_train.astype(np.float), y_train)
    
  6. 预测和可视化预测如下:

    preds = predict(X_test)
    r2 = r2_score(preds, y_test)
    HTML(ch9util.scatter_predictions(preds, y_test, '', r2))
    

有关最终结果,请参考以下屏幕截图:

How to do it...

另见

十、评估分类器、回归器和聚类

在本章中,我们将介绍以下食谱:

  • 用混淆矩阵直接分类
  • 计算精度、召回率和 F1 分数
  • 检查接收器工作特性和曲线下的区域
  • 想象契合度
  • 计算均方误差和中值绝对误差
  • 用平均轮廓系数评价聚类
  • 将结果与虚拟分类器进行比较
  • 确定 MAPE 和多相萃取
  • 与虚拟回归器进行比较
  • 计算平均绝对误差和残差平方和
  • 检查分类的 kappa
  • 看看马修斯相关系数

简介

评估分类器、回归器和聚类是一个涉及多个方面的关键多维问题。纯粹从工程的角度来看,我们担心速度、内存和正确性。在某些情况下,速度就是一切。如果记忆不足,我们当然要优先考虑。这个世界是一个充满选择的巨大迷宫,你有时被迫选择一个模型而不是其他模型,而不是在一个集合中使用多个模型。当然,我们应该用适当的评估标准来指导我们的理性决策。

有这么多的评估标准,你需要多本书来描述它们。显然,许多指标非常相似。其中一些被接受和流行,在这些度量中,一些在 scikit-learn 中实现。

我们将评估第 9 章、集成学习和降维中的分类器和回归器。我们将这些估计应用于天气预报的样本问题。这不一定是人类擅长的问题。实现人的表现是一些问题的目标,例如人脸识别、字符识别、垃圾邮件分类和情感分析。作为击败的底线,我们经常选择某种形式的随机猜测。

用混淆矩阵直接分类

准确性是一个衡量模型在给定环境中表现如何的指标。准确性是 scikit-learn 分类器的默认评估指标。不幸的是,准确性是一维的,当类不平衡时,它没有帮助。我们在第 9 章集成学习和降维中检查的降雨数据相当均衡。雨天的数量几乎等于不下雨的天数。在电子邮件垃圾邮件分类的情况下,至少对我来说,平衡转向了垃圾邮件。

A 混淆矩阵是一个表格,通常用来总结分类的结果。表的两个维度是预测类和目标类。在二元分类的背景下,我们谈论正类和负类。将一个类命名为负数是任意的——这并不一定意味着它在某种程度上是坏的。我们可以将任何多类问题简化为一类,而不是问题的其余部分;所以,当我们评估二元分类时,我们可以将框架扩展到多类分类。一个类可以被正确预测,也可以不被正确预测;我们相应地用“真”和“假”来标记这些实例。

我们有四种真、假、阳性和阴性组合,如下表所述:

|   |

预测类别

|
| --- | --- |
| 目标类 | 真阳性:下雨了,我们预测的没错。 | 误报:我们错误的预测了会下雨。 |
| 假阴性:确实下雨了,但是我们预测不会下雨。 | 真否定:没下雨,我们预测的没错。 |

怎么做...

  1. 进口情况如下:

    import numpy as np
    from sklearn.metrics import confusion_matrix
    import seaborn as sns
    import dautil as dl
    from IPython.display import HTML
    import ch10util
    
  2. 定义以下函数绘制混淆矩阵:

    def plot_cm(preds, y_test, title, cax):
        cm = confusion_matrix(preds.T, y_test)
        normalized_cm = cm/cm.sum().astype(float)
        sns.heatmap(normalized_cm, annot=True, fmt='.2f', vmin=0, vmax=1,
                    xticklabels=['Rain', 'No Rain'],
                    yticklabels=['Rain', 'No Rain'], ax=cax)
        cax.set_xlabel('Predicted class')
        cax.set_ylabel('Expected class')
        cax.set_title('Confusion Matrix for Rain Forecast | ' + title)
    
  3. 加载目标值并绘制随机森林分类器、打包分类器、投票和堆叠分类器的混淆矩阵:

    y_test = np.load('rain_y_test.npy')
    sp = dl.plotting.Subplotter(2, 2, context)
    
    plot_cm(y_test, np.load('rfc.npy'), 'Random Forest', sp.ax)
    
    plot_cm(y_test, np.load('bagging.npy'), 'Bagging', sp.next_ax())
    
    plot_cm(y_test, np.load('votes.npy'),'Votes', sp.next_ax())
    
    plot_cm(y_test, np.load('stacking.npy'), 'Stacking', sp.next_ax())
    sp.fig.text(0, 1, ch10util.classifiers())
    HTML(sp.exit())
    

有关最终结果,请参考以下屏幕截图:

How to do it...

源代码在本书代码包的 conf_matrix.ipynb文件中。

它是如何工作的

我们为四个分类器显示了四个混淆矩阵,每个矩阵的四个数字似乎在重复。当然,数字并不完全相等;然而,你必须考虑到一些随机变化。

另见

计算精度、召回率和 F1 分数

用混淆矩阵配方直接分类中,您了解到我们可以将分类的样本标记为真阳性、假阳性、真阴性和假阴性。通过这些类别的计数,我们可以计算的许多评估指标,我们将在本食谱中涵盖四个,如下式所示:

Computing precision, recall, and F1-score

这些指标的范围从零到一,零是最差的理论分数,一是最好的。实际上,最差的分数是我们通过随机猜测得到的。实践中的最佳分数可能低于 1,因为在某些情况下,我们只能希望模仿人类的表现,对于正确的分类应该是什么可能存在歧义,例如,在情感分析的情况下(涵盖在 Python 数据分析一书中)。

  • 精确度(10.1)是正确预测的比率。
  • 精度 (10.2)将相关性度量为将阴性类别样本分类为阳性的可能性。选择哪个班是积极的多少有些武断,但我们可以说,未雨绸缪是积极的。高精度意味着我们将相对较少的非雨天(负)标记为雨天。对于搜索(网络、数据库或其他),这意味着相关结果的数量相对较高。
  • 回忆 (10.3)是找到所有阳性样本的可能性。如果再来一次,雨天是我们的正课,雨天分类越正确,召回率越高。对于搜索,我们可以通过返回所有文档来获得完美的召回,因为这将自动返回所有相关文档。人脑有点像数据库,在这种情况下,回忆意味着记住某个 Python 函数如何工作的可能性。
  • F1 分数 (10.4)是精度和召回率的调和平均值(实际上,F1 分数有多个变化)。G 分数使用几何平均值;但是,据我所知,它不太受欢迎。F1 成绩背后的理念,相关的 F 成绩和 G 成绩,就是把精准性和召回率结合起来。这并不一定是最好的衡量标准。还有其他指标你可能更喜欢,比如马修斯相关系数(参考看一下马修斯相关系数配方)和科恩卡帕(参考检验卡帕的分类配方)。当我们开始选择这么多分类指标时,我们显然想要最好的指标。然而,你必须根据你的情况做出选择,因为没有一个标准适合所有人。

怎么做...

  1. 进口情况如下:

    import numpy as np
    from sklearn import metrics
    import ch10util
    import dautil as dl
    from IPython.display import HTML
    
  2. 加载目标值并计算指标:

    y_test = np.load('rain_y_test.npy')
    accuracies = [metrics.accuracy_score(y_test, preds)
                  for preds in ch10util.rain_preds()]
    precisions = [metrics.precision_score(y_test, preds)
                  for preds in ch10util.rain_preds()]
    recalls = [metrics.recall_score(y_test, preds)
               for preds in ch10util.rain_preds()]
    f1s = [metrics.f1_score(y_test, preds)
           for preds in ch10util.rain_preds()]
    
  3. 绘制降雨预报的指标:

    sp = dl.plotting.Subplotter(2, 2, context)
    ch10util.plot_bars(sp.ax, accuracies)
    sp.label()
    
    ch10util.plot_bars(sp.next_ax(), precisions)
    sp.label()
    
    ch10util.plot_bars(sp.next_ax(), recalls)
    sp.label()
    
    ch10util.plot_bars(sp.next_ax(), f1s)
    sp.label()
    sp.fig.text(0, 1, ch10util.classifiers())
    HTML(sp.exit())
    

最终结果见截图后的:

How to do it...

代码在本书代码包的precision_recall.ipynb文件中。

另见

检查接收器操作特性和曲线下的区域

接收器 操作特性 ( ROC )是二进制分类器的召回率(10.3)和假阳性率 ( FPR )的图。FPR 由下式给出:

Examining a receiver operating characteristic and the area under a curve

在本食谱中,我们将绘制我们在第 9 章、集成学习和降维中使用的各种分类器的 ROC。此外,我们将绘制与随机猜测相关的曲线和理想曲线。显然,我们想要超越基线,尽可能地接近理想曲线。

曲线下的 区域 ( AUCROC AUCAUROC )是总结 ROC 的另一个评价指标。AUC 也可以用来比较模型,但它提供的信息比 ROC 少。

怎么做...

  1. 进口情况如下:

    from sklearn import metrics
    import numpy as np
    import ch10util
    import dautil as dl
    from IPython.display import HTML
    
  2. 加载数据并计算指标:

    y_test = np.load('rain_y_test.npy')
    roc_aucs = [metrics.roc_auc_score(y_test, preds)
                for preds in ch10util.rain_preds()]
    
  3. 绘制雨预报器的 AUROC:

    sp = dl.plotting.Subplotter(2, 1, context)
    ch10util.plot_bars(sp.ax, roc_aucs)
    sp.label()
    
  4. 绘制降雨预报器的 ROC 曲线:

    cp = dl.plotting.CyclePlotter(sp.next_ax())
    
    for preds, label in zip(ch10util.rain_preds(),
                            ch10util.rain_labels()):
        fpr, tpr, _ = metrics.roc_curve(y_test, preds,
                                        pos_label=True)
        cp.plot(fpr, tpr, label=label)
    
    fpr, tpr, _ = metrics.roc_curve(y_test, y_test)
    sp.ax.plot(fpr, tpr, 'k', lw=4, label='Ideal')
    sp.ax.plot(np.linspace(0, 1), np.linspace(0, 1), 
               '--', label='Baseline')
    sp.label()
    sp.fig.text(0, 1, ch10util.classifiers())
    HTML(sp.exit())
    

有关最终结果,请参考以下屏幕截图:

How to do it...

代码在本书代码包的roc_auc.ipynb文件中。

另见

可视化拟合优度

我们期望,或者至少希望,回归的残差只是随机噪声。如果不是这样,那么我们的回归者可能忽略了信息。我们期望残差是独立的并且是正态分布的。用直方图或者 QQ 图比较容易检查。一般来说,我们希望残差的均值尽可能接近零,并且希望残差的方差尽可能小。理想拟合会有零值残差。

怎么做...

  1. 进口情况如下:

    import numpy as np
    import matplotlib.pyplot as plt
    import dautil as dl
    import seaborn as sns
    from scipy.stats import probplot
    from IPython.display import HTML
    
  2. 加载助推回归器的目标和预测:

    y_test = np.load('temp_y_test.npy')
    preds = np.load('boosting.npy')
    
  3. 将实际值和预测值绘制如下:

    sp = dl.plotting.Subplotter(2, 2, context)
    cp = dl.plotting.CyclePlotter(sp.ax)
    cp.plot(y_test)
    cp.plot(preds)
    sp.ax.set_ylabel(dl.data.Weather.get_header('TEMP'))
    sp.label()
    
  4. 将残差绘制如下:

    residuals = preds - y_test
    sp.next_ax().plot(residuals)
    sp.label()
    
  5. 绘制残差分布图:

    sns.distplot(residuals, ax=sp.next_ax())
    sp.label()
    
  6. 绘制残差的 QQ 图:

    probplot(residuals, plot=sp.next_ax())
    HTML(sp.exit())
    

有关最终结果,请参考以下屏幕截图:

How to do it...

代码在本书代码包的visualizing_goodness.ipynb文件中。

另见

计算均方误差和中值绝对误差

均方误差 ( 均方误差)和中值 绝对误差 ( 梅达)是流行的回归指标。它们由以下等式给出:

Computing MSE and median absolute error

均方误差(10.6)类似于总体方差。因此,均方误差的平方根( RMSE )类似于标准偏差。均方误差的单位与被分析的变量相同——在我们的例子中,是温度。理想拟合具有零值残差,因此其均方误差等于零。由于我们处理的是平方误差,均方误差的值大于或理想地等于零。

MedAE 类似于 MSE,但是我们从残差的绝对值开始,我们使用中值而不是平均值作为中心性的度量。MedAE 也类似于方差,理想情况下为零或非常小。取绝对值而不是平方有可能避免数值不稳定和速度问题,并且中值对于异常值比平均值更稳健。此外,取平方倾向于强调较大的误差。

在本食谱中,我们将为来自第 9 章集成学习和降维的回归器绘制 MSE 和 MedAE 的自举种群。

怎么做...

  1. 进口情况如下:

    from sklearn import metrics
    import ch10util
    from IPython.display import HTML
    import dautil as dl
    from IPython.display import HTML
    
  2. 绘制温度预测指标的分布:

    sp = dl.plotting.Subplotter(3, 2, context)
    ch10util.plot_bootstrap('boosting',
                            metrics.mean_squared_error, sp.ax)
    sp.label()
    
    ch10util.plot_bootstrap('boosting',
                            metrics.median_absolute_error, sp.next_ax())
    sp.label()
    
    ch10util.plot_bootstrap('etr',
                            metrics.mean_squared_error, sp.next_ax())
    sp.label()
    
    ch10util.plot_bootstrap('etr',
                            metrics.median_absolute_error, sp.next_ax())
    sp.label()
    
    ch10util.plot_bootstrap('ransac',
                            metrics.mean_squared_error, sp.next_ax())
    sp.label()
    
    ch10util.plot_bootstrap('ransac',
                            metrics.median_absolute_error, sp.next_ax())
    sp.label()
    sp.fig.text(0, 1, ch10util.regressors())
    HTML(sp.exit())
    

最终结果参见以下截图:

How to do it...

代码在本书代码包的mse.ipynb文件中。

另见

用平均轮廓系数评价聚类

聚类是一种无监督的机器学习类型的分析。虽然我们一般不知道什么是最好的聚类,但我们仍然可以得到一个聚类结果有多好的概念。一种方法是计算轮廓系数,如下式所示:

Evaluating clusters with the mean silhouette coefficient

在上式中, a(i) 是样本 i 相对于同一聚类中其他样本的平均相异度。一个小的 a(i) 表示样品属于它的簇。 b(i)i 与其他聚类的最低平均相异度。它为 i 指示下一个最佳集群。如果样本的轮廓系数 s(i) 接近 1,则表示样本分配正确。 s(i) 的值在-1 到 1 之间变化。所有样本的轮廓系数的平均值衡量聚类的质量。

我们可以使用平均轮廓系数来通知我们对 K-均值聚类算法的聚类数量的决定。在第 5 章网络挖掘、数据库和大数据中的使用 Spark 配方对流式数据进行聚类中更详细地介绍了 K-means 聚类算法。

怎么做...

  1. 进口情况如下:

    import dautil as dl
    from sklearn.cluster import KMeans
    from sklearn.metrics import silhouette_score
    from sklearn.metrics import silhouette_samples
    from IPython.display import HTML
    
  2. 定义以下函数绘制轮廓样本:

    def plot_samples(ax, years, labels, i, avg):
        silhouette_values = silhouette_samples(X, labels)
        dl.plotting.plot_text(ax, years, silhouette_values,
                              labels, add_scatter=True)
        ax.set_title('KMeans k={0} Silhouette avg={1:.2f}'.format(i, avg))
        ax.set_xlabel('Year')
        ax.set_ylabel('Silhouette score')
    
  3. 加载数据并重新采样,如下所示:

    df = dl.data.Weather.load().resample('A').dropna()
    years = [d.year for d in df.index]
    X = df.values
    
  4. 绘制不同簇数的簇:

    sp = dl.plotting.Subplotter(2, 2, context)
    avgs = []
    rng = range(2, 9)
    
    for i in rng:
        kmeans = KMeans(n_clusters=i, random_state=37)
        labels = kmeans.fit_predict(X)
        avg = silhouette_score(X, labels)
        avgs.append(avg)
    
        if i < 5:
            if i > 2:
                sp.next_ax()
    
            plot_samples(sp.ax, years, labels, i, avg)
    
    sp.next_ax().plot(rng, avgs)
    sp.label()
    HTML(sp.exit())
    

有关最终结果,请参考以下屏幕截图:

How to do it...

代码在本书代码包的 evaluating_clusters.ipynb文件中。

另见

用虚拟分类器比较结果

scikit-learn DummyClassifier类实现了几种随机猜测的策略,可以作为分类器的基线。策略如下:

  • stratified:这使用了训练集类分布
  • most_frequent:这预示着最频繁的上课
  • prior:这在 scikit-learn 0.17 中提供,通过最大化课程优先级进行预测
  • uniform:这使用均匀分布对类进行随机采样
  • constant:这预测了用户指定的类

可以看到DummyClassifier类的一些策略总是预测同一个类。这可能会导致一些 scikit-learn 度量函数发出警告。我们将执行与在计算精度、召回率和 F1 评分配方中相同的分析,但添加了虚拟分类器。

怎么做...

  1. 进口情况如下:

    import numpy as np
    from sklearn import metrics
    import ch10util
    from sklearn.dummy import DummyClassifier
    from IPython.display import HTML
    import dautil as dl
    
  2. 加载数据如下:

    y_test = np.load('rain_y_test.npy')
    X_train = np.load('rain_X_train.npy')
    X_test = np.load('rain_X_test.npy')
    y_train = np.load('rain_y_train.npy')
    
  3. 创建虚拟分类器并使用它们进行预测:

    stratified = DummyClassifier(random_state=28)
    frequent = DummyClassifier(strategy='most_frequent',
                               random_state=28)
    prior = DummyClassifier(strategy='prior', random_state=29)
    uniform = DummyClassifier(strategy='uniform',
                              random_state=29)
    preds = ch10util.rain_preds()
    
    for clf in [stratified, frequent, prior, uniform]:
        clf.fit(X_train, y_train)
        preds.append(clf.predict(X_test))
    
  4. 使用预测计算指标,如下所示:

    accuracies = [metrics.accuracy_score(y_test, p)
                  for p in preds]
    precisions = [metrics.precision_score(y_test, p)
                  for p in preds]
    recalls = [metrics.recall_score(y_test, p)
               for p in preds]
    f1s = [metrics.f1_score(y_test, p)
           for p in preds]
    
  5. 绘制虚拟和常规分类器的度量:

    labels = ch10util.rain_labels()
    labels.extend(['stratified', 'frequent',
                   'prior', 'uniform'])
    
    sp = dl.plotting.Subplotter(2, 2, context)
    ch10util.plot_bars(sp.ax, accuracies, labels, rotate=True)
    sp.label()
    
    ch10util.plot_bars(sp.next_ax(), precisions, labels, rotate=True)
    sp.label()
    
    ch10util.plot_bars(sp.next_ax(), recalls, labels, rotate=True)
    sp.label()
    
    ch10util.plot_bars(sp.next_ax(), f1s, labels, rotate=True)
    sp.label()
    sp.fig.text(0, 1, ch10util.classifiers(), fontsize=10)
    HTML(sp.exit())
    

有关最终结果,请参考以下屏幕截图:

How to do it...

代码在本书代码包的 dummy_clf.ipynb文件中。

另见

测定 MAPE 和多相萃取

平均百分比误差 (MPE )和平均绝对百分比误差 ( MAPE )将预测误差表示为比率,因此它们是无量纲的,易于解释。从下面的等式中可以看出,MPE 和 MAPE 的缺点是我们有被零除的风险:

Determining MAPE and MPE

目标变量等于零是完全有效的。对于温度,这恰好是冰点。冬天经常会结冰,所以我们要么忽略那些观测值,要么加入一个足够大的常数,避免被零值除。在下一节中,很明显,简单地忽略观察会导致奇怪的引导分布。

怎么做...

  1. 进口情况如下:

    import ch10util
    import dautil as dl
    from IPython.display import HTML
    
  2. 将自举指标绘制如下:

    sp = dl.plotting.Subplotter(3, 2, context)
    ch10util.plot_bootstrap('boosting',
                            dl.stats.mape, sp.ax)
    sp.label()
    
    ch10util.plot_bootstrap('boosting',
                            dl.stats.mpe, sp.next_ax())
    sp.label()
    
    ch10util.plot_bootstrap('etr',
                            dl.stats.mape, sp.next_ax())
    sp.label()
    
    ch10util.plot_bootstrap('etr',
                            dl.stats.mpe, sp.next_ax())
    sp.label()
    
    ch10util.plot_bootstrap('ransac',
                            dl.stats.mape, sp.next_ax())
    sp.label()
    
    ch10util.plot_bootstrap('ransac',
                            dl.stats.mpe, sp.next_ax())
    sp.label()
    sp.fig.text(0, 1, ch10util.regressors())
    HTML(sp.exit())
    

最终结果参见下面的截图:

How to do it...

代码在本书代码包的mape_mpe.ipynb文件中。

另见

与虚拟回归器进行比较

scikit-learn DummyRegressor类实现了随机猜测的几种策略,可以作为回归器的基线。这些战略如下:

  • mean:这预测了训练集的均值。
  • median:这预测了训练集的中位数。
  • quantile:当提供quantile参数时,这预测训练集的指定分位数。我们将通过指定第一个和第三个四分位数来应用这个策略。
  • constant:这预测了由用户提供的恒定值。

我们将使用 R 平方、均方误差、最大似然误差和最大似然误差将虚拟回归器与第 9 章、集成学习和降维中的回归器进行比较。

怎么做...

  1. 进口情况如下:

    import numpy as np
    from sklearn.dummy import DummyRegressor
    import ch10util
    from sklearn import metrics
    import dautil as dl
    from IPython.display import HTML
    
  2. 加载温度数据如下:

    y_test = np.load('temp_y_test.npy')
    X_train = np.load('temp_X_train.npy')
    X_test = np.load('temp_X_test.npy')
    y_train = np.load('temp_y_train.npy')
    
  3. 使用可用的策略创建虚拟回归,并用它们预测温度:

    mean = DummyRegressor()
    median = DummyRegressor(strategy='median')
    q1 = DummyRegressor(strategy='quantile', quantile=0.25)
    q3 = DummyRegressor(strategy='quantile', quantile=0.75)
    
    preds = ch10util.temp_preds()
    
    for reg in [mean, median, q1, q3]:
        reg.fit(X_train, y_train)
        preds.append(reg.predict(X_test))
    
  4. 计算常规回归和虚拟回归的 R 平方、均方误差、中值绝对误差和平均百分比误差:

    r2s = [metrics.r2_score(p, y_test) for p in preds]
    mses = [metrics.mean_squared_error(p, y_test)
            for p in preds]
    maes = [metrics.median_absolute_error(p, y_test)
            for p in preds]
    mpes = [dl.stats.mpe(y_test, p) for p in preds]
    
    labels = ch10util.temp_labels()
    labels.extend(['mean', 'median', 'q1', 'q3'])
    
  5. 将指标绘制如下:

    sp = dl.plotting.Subplotter(2, 2, context)
    ch10util.plot_bars(sp.ax, r2s, labels)
    sp.label()
    
    ch10util.plot_bars(sp.next_ax(), mses, labels)
    sp.label()
    
    ch10util.plot_bars(sp.next_ax(), maes, labels)
    sp.label()
    
    ch10util.plot_bars(sp.next_ax(), mpes, labels)
    sp.label()
    sp.fig.text(0, 1, ch10util.regressors())
    HTML(sp.exit())
    

有关最终结果,请参考以下屏幕截图:

How to do it...

代码在本书代码包的dummy_reg.ipynb文件中。

另见

计算平均绝对误差和残差平方和

平均绝对误差 ( 平均)和 残差平方和 ( RSS )是由以下公式给出的回归度量:

Calculating the mean absolute error and the residual sum of squares

平均绝对误差(10.11)类似于均方误差和最大误差,但在计算的一个步骤中有所不同。这些度量的共同特征是它们忽略了误差的符号,类似于方差。平均值大于或理想地等于零。

RSS (10.12)类似于 MSE,只是我们没有除以残差数。因此,使用 RSS 可以获得更大的值。然而,一个理想的适合给你一个零 RSS。

怎么做...

  1. 进口情况如下:

    import ch10util
    import dautil as dl
    from sklearn import metrics
    from IPython.display import HTML
    
  2. 将自举指标绘制如下:

    sp = dl.plotting.Subplotter(3, 2, context)
    ch10util.plot_bootstrap('boosting',
                            metrics.mean_absolute_error, sp.ax)
    sp.label()
    
    ch10util.plot_bootstrap('boosting',
                            dl.stats.rss, sp.next_ax())
    sp.label()
    
    ch10util.plot_bootstrap('etr',
                            metrics.mean_absolute_error, sp.next_ax())
    sp.label()
    
    ch10util.plot_bootstrap('etr',
                            dl.stats.rss, sp.next_ax())
    sp.label()
    
    ch10util.plot_bootstrap('ransac',
                            metrics.mean_absolute_error, sp.next_ax())
    sp.label()
    
    ch10util.plot_bootstrap('ransac',
                            dl.stats.rss, sp.next_ax())
    sp.label()
    sp.fig.text(0, 1, ch10util.regressors())
    HTML(sp.exit())
    

最终结果参见以下截图:

How to do it...

代码在本书代码包的mae_rss.ipynb文件中。

另见

检验分类的 kappa

Cohen 的 kappa 测量目标和预测类别之间的一致性,类似于精确度,但是它也考虑了获得预测的随机机会。科恩的 kappa 由下式给出:

Examining the kappa of classification

在这个方程中, p 0 是相对观测一致性, p e 是由数据推导出的随机一致性概率。Kappa 在负值和以下兰迪斯和科赫的粗略分类之间变化:

  • 不一致:kappa < 0
  • 略微一致:kappa = 0 至 0.2
  • 公平协议:kappa = 0.21 至 0.4
  • 中度一致:kappa = 0.41 至 0.6
  • 良好一致性:kappa = 0.61 至 0.8
  • 非常好的一致性:kappa = 0.81 到 1.0

我知道另外两个评定卡帕等级的方案,所以这些数字不是一成不变的。我想我们可以同意不接受低于 0.2 的 kappa。最合适的用例当然是对模型进行排名。科恩的 kappa 还有其他变体,但截至 2015 年 11 月,它们还没有在 scikit-learn 中实现。scikit-learn 0.17 通过cohen_kappa_score()功能增加了对 Cohen 的 kappa 的支持。

怎么做...

  1. 进口情况如下:

    import dautil as dl
    from sklearn import metrics
    import numpy as np
    import ch10util
    from IPython.display import HTML
    
  2. 计算雨预报器的准确度、精确度、召回率、F1 评分和 kappa:

    y_test = np.load('rain_y_test.npy')
    accuracies = [metrics.accuracy_score(y_test, preds)
                  for preds in ch10util.rain_preds()]
    precisions = [metrics.precision_score(y_test, preds)
                  for preds in ch10util.rain_preds()]
    recalls = [metrics.recall_score(y_test, preds)
               for preds in ch10util.rain_preds()]
    f1s = [metrics.f1_score(y_test, preds)
           for preds in ch10util.rain_preds()]
    kappas = [metrics.cohen_kappa_score(y_test, preds)
              for preds in ch10util.rain_preds()]
    
  3. 散点图针对 kappa 的指标如下:

    sp = dl.plotting.Subplotter(2, 2, context)
    dl.plotting.plot_text(sp.ax, accuracies, kappas,
                          ch10util.rain_labels(), add_scatter=True)
    sp.label()
    
    dl.plotting.plot_text(sp.next_ax(), precisions, kappas,
                          ch10util.rain_labels(), add_scatter=True)
    sp.label()
    
    dl.plotting.plot_text(sp.next_ax(), recalls, kappas,
                          ch10util.rain_labels(), add_scatter=True)
    sp.label()
    
    dl.plotting.plot_text(sp.next_ax(), f1s, kappas,
                          ch10util.rain_labels(), add_scatter=True)
    sp.label()
    sp.fig.text(0, 1, ch10util.classifiers())                     
    HTML(sp.exit())
    

有关最终结果,请参考以下屏幕截图:

How to do it...

代码在本书代码包的kappa.ipynb文件中。

它是如何工作的

从前两个图中,我们可以得出结论,bagging 分类器具有最高的准确度、精确度和 kappa。所有分类器的 kappa 都在 0.2 以上,所以它们至少在一定程度上是可以接受的。

另见

看一下马修斯相关系数

马修斯相关系数 ( MCC )或φ系数是 Brian Matthews 在 1975 年发明的二进制分类的评价指标。监控中心是目标和预测的相关系数,在-1 和 1 之间变化(最佳一致性)。MCC 是总结混淆矩阵的一个非常好的方法(参考用混淆矩阵配方直接得到分类),因为它使用了其中的所有四个数字。监控中心由以下等式给出:

Taking a look at the Matthews correlation coefficient

怎么做...

  1. 进口情况如下:

    import dautil as dl
    from sklearn import metrics
    import numpy as np
    import ch10util
    from IPython.display import HTML
    
  2. 计算降雨预报器的准确度、精确度、召回率、F1 分数和马修斯相关系数:

    y_test = np.load('rain_y_test.npy')
    accuracies = [metrics.accuracy_score(y_test, preds)
                  for preds in ch10util.rain_preds()]
    precisions = [metrics.precision_score(y_test, preds)
                  for preds in ch10util.rain_preds()]
    recalls = [metrics.recall_score(y_test, preds)
               for preds in ch10util.rain_preds()]
    f1s = [metrics.f1_score(y_test, preds)
           for preds in ch10util.rain_preds()]
    mc = [metrics.matthews_corrcoef(y_test, preds)
          for preds in ch10util.rain_preds()]
    
  3. 将指标绘制如下:

    sp = dl.plotting.Subplotter(2, 2, context)
    dl.plotting.plot_text(sp.ax, accuracies, mc,
                          ch10util.rain_labels(), add_scatter=True)
    sp.label()
    
    dl.plotting.plot_text(sp.next_ax(), precisions, mc,
                          ch10util.rain_labels(), add_scatter=True)
    sp.label()
    
    dl.plotting.plot_text(sp.next_ax(), recalls, mc,
                          ch10util.rain_labels(), add_scatter=True)
    sp.label()
    
    dl.plotting.plot_text(sp.next_ax(), f1s, mc,
                          ch10util.rain_labels(), add_scatter=True)
    sp.label()
    sp.fig.text(0, 1, ch10util.classifiers())
    HTML(sp.exit())
    

最终结果参见下面的截图:

How to do it...

代码在本书的代码包matthews_correlation.ipynb文件中。

另见

十一、分析图像

在本章中,我们将介绍以下食谱:

  • 设置 OpenCV
  • 应用尺度不变特征变换
  • 使用 SURF 检测要素
  • 量化颜色
  • 图像去噪
  • 从图像中提取面片
  • 用哈尔级联检测人脸
  • 寻找明亮的星星
  • 从图像中提取元数据
  • 从图像中提取纹理特征
  • 在图像上应用分层聚类
  • 用谱聚类分割图像

简介

图像处理是一个非常大的研究领域。用于图像处理的技术通常也可以应用于视频分析。我们可以将图像处理视为一种特殊类型的信号处理。信号处理包含在第 6 章信号处理和时间序列中。然而,图像带来了特殊的挑战,例如高维数(我们可以将每个图像像素定义为一个特征)和空间依赖性(像素位置很重要)。

与计算机相比,人类的视觉系统非常先进。我们能够识别物体、面部表情和物体运动。显然,这与食肉动物和它们食用人肉的倾向有关。在本章中,我们将集中精力寻找图像中的特征并对图像像素进行聚类(分割),而不是试图理解人类视觉是如何工作的。

在这一章中,我们经常使用 OpenCV 库,由于它是一个相当大的库,我决定只为这一章创建一个特殊的 Docker 容器。你可能已经知道了,我制作了一个名为pydacbk的 Docker 图像。嗯,本章的 Docker 容器名为pydacbk11

设置开放式课程

OpenCV ( 开源计算机视觉)是 2000 年创建的计算机视觉库,目前由 Itseez 维护。OpenCV 是用 C++编写的,但它也有针对 Python 和其他编程语言的绑定。支持多种操作系统和图形处理器。本章没有足够的篇幅来涵盖 OpenCV 的所有功能。即使是一本书可能也是不够的——对于皮德尼塔斯,我推荐约瑟夫·豪斯的《用 Python 打开计算机视觉》。

OpenCV 2.x.x 包中的一些第三方专利算法,如 SIFT 和 SURF(参考本章中的相关食谱),已经被转移到一个特殊的 GitHub 存储库中。您仍然可以使用它们,但是您需要在安装过程中明确包含它们。

OpenCV 构建过程有许多选项。如果您不确定哪些选项最适合您,请阅读 OpenCV 文档或使用适合您的操作系统的软件包管理器。一般来说,你不应该使用太多的选项。尽管您可以灵活地关闭某些模块,但其他模块可能会依赖于它们,这可能会导致一连串的错误。

做好准备

如果你在 Windows 或者 Fedora 上,可以在http://docs . opencv . org/3 . 0 . 0/da/df6/tutorial _ py _ table _ contents _ setup . html阅读相应教程(2015 年 12 月检索)。对于基于 Ubuntu 的 Docker 容器,我需要用以下命令安装一些先决条件:

$ apt-get update
$ apt-get install -y cmake make git g++

怎么做...

以下说明作为示例,并对您的设置做了一些假设。例如,它假设您正在 Python 3 中使用 Anaconda。为了方便起见,我将基于 Ubuntu 的 Docker 容器的所有指令组织在一个 shell 脚本中;但是,如果您愿意,也可以在终端中分别键入每一行。

  1. 下载核心 OpenCV 项目的代码(如果没有 Git,也可以从 GitHub 网站下载代码):

    $ cd /opt
    $ git clone https://github.com/Itseez/opencv.git
    $ cd opencv
    $ git checkout tags/3.0.0
    
    
  2. 下载 OpenCV(第三方)投稿的代码(如果没有 Git,也可以从 GitHub 网站下载代码):

    $ cd /opt
    $ git clone https://github.com/Itseez/opencv_contrib
    $ cd opencv_contrib
    $ git checkout tags/3.0.0
    $ cd /opt/opencv
    
    
  3. 创建一个构建目录并导航到它:

    $ mkdir build
    $ cd build
    
    
  4. 此步骤显示了您可以使用的一些构建选项(您不必使用所有这些选项):

    $ ANACONDA=~/anaconda
    $ cmake -D CMAKE_BUILD_TYPE=RELEASE \
     -D BUILD_PERF_TESTS=OFF \
     -D BUILD_opencv_core=ON \
     -D BUILD_opencv_python2=OFF \
     -D BUILD_opencv_python3=ON \
     -D BUILD_opencv_cuda=OFF \
     -D BUILD_opencv_java=OFF \
     -D BUILD_opencv_video=ON \
     -D BUILD_opencv_videoio=ON \
     -D BUILD_opencv_world=OFF \
     -D BUILD_opencv_viz=ON \
     -D WITH_CUBLAS=OFF \
     -D WITH_CUDA=OFF \
     -D WITH_CUFFT=OFF \
     -D WITH_FFMPEG=OFF \
     -D PYTHON3_EXECUTABLE=${ANACONDA}/bin/python3 \
     -D PYTHON3_LIBRARY=${ANACONDA}/lib/libpython3.4m.so \
     -D PYTHON3_INCLUDE_DIR=${ANACONDA}/include/python3.4m \
     -D PYTHON3_NUMPY_INCLUDE_DIRS=${ANACONDA}/lib/python3.4/site-packages/numpy/core/include \
     -D PYTHON3_PACKAGES_PATH=${ANACONDA}/lib/python3.4/site-packages \
     -D BUILD_opencv_latentsvm=OFF \
     -D BUILD_opencv_xphoto=OFF \
     -D BUILD_opencv_xfeatures2d=ON \
     -D OPENCV_EXTRA_MODULES_PATH=/opt/opencv_contrib/modules \
     /opt/opencv
    
    
  5. 运行make命令(8 芯)安装如下:

    $ make -j8
    $ sudo make install
    
    

它是如何工作的

前面的说明假设您是第一次安装 OpenCV。如果您是从 OpenCV 2.x.x 升级,您将不得不采取额外的预防措施。另外,我假设您不想要某些选项,并且正在 Python 3 中使用 Anaconda。下表解释了我们使用的一些构建选项:

|

[计]选项

|

描述

|
| --- | --- |
| BUILD_opencv_python2 | 支持 Python 2 |
| BUILD_opencv_python3 | 支持 Python 3 |
| BUILD_opencv_java | 支持 OpenCV Java 绑定 |
| PYTHON3_EXECUTABLE | Python 3 可执行文件的位置 |
| PYTHON3_LIBRARY | Python 3 库的位置 |
| PYTHON3_INCLUDE_DIR | Python 3 include目录的位置 |
| PYTHON3_NUMPY_INCLUDE_DIRS | NumPy include目录的位置 |
| PYTHON3_PACKAGES_PATH | Python 3 包的位置 |
| BUILD_opencv_xfeatures2d | 支持某些第三方算法,如 SIFT 和 SURF |
| OPENCV_EXTRA_MODULES_PATH | OpenCV 第三方贡献代码的位置 |

还有更多

如果您没有足够的空间,例如在 Docker 容器中,那么您可以使用以下命令进行清理:

$ rm -rf /opt/opencv
$ rm -rf /opt/opencv_contrib

应用尺度不变特征变换

SIFT 算法(1999)在图像或视频中发现特征,并获得了不列颠哥伦比亚大学的专利。通常,我们可以使用特征进行分类或聚类。SIFT 在平移、缩放和旋转方面是不变的。

算法步骤如下:

  1. 使用高斯模糊滤镜以不同的比例模糊图像。
  2. 一个八度 对应的是两倍的滤镜标准差。将模糊的图像按八度音分组,并加以区别。
  3. 为差分图像找到跨尺度的局部极值。
  4. 将与局部极值相关的每个像素与同一尺度和相邻尺度中的相邻像素进行比较。
  5. 从比较中选择最大值或最小值。
  6. 拒绝对比度低的点。
  7. 插值候选关键点(图像特征)以获得原始图像上的位置。

做好准备

按照中的说明设置 OpenCV 配方。

怎么做...

  1. 进口情况如下:

    import cv2
    import matplotlib.pyplot as plt
    import dautil as dl
    from scipy.misc import face
    
  2. 将原始图像绘制如下:

    img = face()
    plt.title('Original')
    dl.plotting.img_show(plt.gca(), img)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    
  3. 将灰度图像绘制如下:

    plt.figure()
    plt.title('Gray')
    dl.plotting.img_show(plt.gca(), gray, cmap=plt.cm.gray)
    
  4. 用关键点(蓝色)绘制图像,如下所示:

    sift = cv2.xfeatures2d.SIFT_create()
    (kps, descs) = sift.detectAndCompute(gray, None)
    img2 = cv2.drawKeypoints(gray, kps, None, (0, 0, 255))
    
    plt.figure()
    plt.title('With Keypoints')
    dl.plotting.img_show(plt.gca(), img2)
    

有关最终结果,请参考以下屏幕截图:

How to do it...

程序在本书代码包的applying_sift.ipynb文件中。

另见

利用 SURF 检测特征

加速鲁棒特征 ( SURF )是一种类似于 SIFT 并受其启发的专利算法(参考应用比例不变特征变换配方)。SURF 于 2006 年推出,使用哈尔小波(参考应用离散小波变换配方)。SURF 最大的优点就是比 SIFT 快。

看看下面的等式:

Detecting features with SURF

算法步骤如下:

  1. 如有必要,变换图像以获得等效灰度。
  2. 计算不同尺度下的 积分图像,它是一个像素上面和左边的像素之和,如公式(11.1)所示。积分图像代替了 SIFT 中的高斯滤波器。
  3. 将包含灰度图像二阶导数的 黑森矩阵 (11.2)定义为像素位置 p 和比例 σ (11.3)的函数。
  4. 行列式是与方阵相关的值。黑森矩阵的行列式对应于一个点的局部变化。选择行列式最大的点。
  5. 音阶σ由 11.3 定义,就像 SIFT 一样,我们可以定义音阶八度。SURF 通过改变过滤器内核的大小来工作,而 SIFT 改变图像大小。在比例和图像空间中插入上一步的最大值。
  6. 将哈尔小波变换应用于关键点周围的圆。
  7. 使用滑动窗口对响应求和。
  8. 根据响应总和确定方向。

做好准备

按照中的说明设置 OpenCV 配方。

怎么做...

  1. 进口如下:

    import cv2
    import matplotlib.pyplot as plt
    import dautil as dl
    
  2. 将原始图像绘制如下:

    img = cv2.imread('covers.jpg')
    plt.title('Original')
    dl.plotting.img_show(plt.gca(), img)
    
  3. 将灰度图像绘制如下:

    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    
    plt.figure()
    plt.title('Gray')
    dl.plotting.img_show(plt.gca(), gray, cmap=plt.cm.gray)
    surf = cv2.xfeatures2d.SURF_create()
    (kps, descs) = surf.detectAndCompute(gray, None)
    img2 = cv2.drawKeypoints(gray, kps, None, (0, 0, 255))
    
  4. 用关键点(蓝色)绘制图像,如下所示:

    plt.figure()
    plt.title('With Keypoints')
    dl.plotting.img_show(plt.gca(), img2)
    

有关最终结果,请参考以下屏幕截图:

How to do it...

代码在本书代码包的 applying_surf.ipynb文件中。

另见

量化颜色

在古代,电脑游戏几乎是单色的。许多年后,互联网允许我们下载图像,但网络速度很慢,所以首选颜色很少的紧凑图像。我们可以得出结论,限制颜色的数量是传统的。颜色是图像的一个维度,所以如果我们从图像中去除颜色,我们就可以说是降维。实际过程叫做 颜色量化

通常,我们在三维空间中为每个像素表示 RGB ( 红色绿色蓝色)值,然后对这些点进行聚类。对于每个集群,我们都有相应的平均颜色。在这个配方中,我们将使用 k-means 聚类(参考用 Spark 配方对流式数据进行聚类),尽管这不一定是最好的算法。

做好准备

按照中的说明设置 OpenCV 配方。

怎么做...

代码在本书代码包的quantizing_colors.ipynb文件中:

  1. 进口情况如下:

    import numpy as np
    import cv2
    import matplotlib.pyplot as plt
    import dautil as dl
    from scipy.misc import face
    
  2. 将原始图像绘制如下:

    sp = dl.plotting.Subplotter(2, 2, context)
    img = face()
    dl.plotting.img_show(sp.ax, img)
    sp.label()
    Z = img.reshape((-1, 3))
    
    Z = np.float32(Z)
    
  3. 应用 k-均值聚类并绘制结果:

    criteria = (cv2.TERM_CRITERIA_MAX_ITER, 7, 1.0)
    
    for k in [2, 4, 8]:
        _, label, center = cv2.kmeans(Z, k, None, criteria, 7,
                                      cv2.KMEANS_RANDOM_CENTERS)
    
        center = np.uint8(center)
        res = center[label.flatten()]
        res2 = res.reshape((img.shape))
    
        dl.plotting.img_show(sp.next_ax(), res2)
        sp.label()
    

最终结果参见以下截图:

How to do it...

另见

图像去噪

噪声是数据和图像中的常见现象。当然,噪音是不可取的,因为它不会给我们的分析增加任何价值。我们通常假设噪声通常分布在零附近。我们认为像素值是真实值和噪声(如果有的话)的总和。我们还假设噪声值是独立的,即一个像素的噪声值独立于另一个像素。

一个简单的想法是在一个小窗口中平均像素,因为我们假设噪声的期望值为零。这是模糊背后的一般想法。我们可以将这个想法更进一步,在一个像素周围定义多个窗口,然后我们可以对相似的面片进行平均。

OpenCV 有几个去噪功能,通常我们需要指定过滤器的强度、搜索窗口的大小以及模板窗口的大小来进行相似性检查。您应该注意不要将滤镜强度设置得太高,因为这可能会使图像不仅更干净,而且有点模糊。

做好准备

按照中的说明设置 OpenCV 配方。

怎么做...

  1. 进口情况如下:

    import cv2
    import matplotlib.pyplot as plt
    from sklearn.datasets import load_sample_image
    import numpy as np
    import dautil as dl
    
  2. 将原始图像绘制如下:

    img = load_sample_image('china.jpg')
    dl.plotting.img_show(plt.gca(), img) 
    plt.title('Original')
    Z = img.reshape((-1, 3))
    
  3. 向图像添加噪点并绘制噪点图像:

    np.random.seed(59)
    noise = np.random.random(Z.shape) < 0.99
    
    noisy = (Z * noise).reshape((img.shape))
    
    plt.figure()
    plt.title('Noisy')
    dl.plotting.img_show(plt.gca(), noisy)
    
  4. 清洁图像并显示:

    cleaned = cv2.fastNlMeansDenoisingColored(noisy, None, 10, 10, 7, 21)
    plt.figure()
    plt.title('Cleaned')
    dl.plotting.img_show(plt.gca(), cleaned)
    

有关最终结果,请参考以下屏幕截图:

How to do it...

代码在本书代码包的denoising_images.ipynb文件中。

另见

从图像中提取斑块

图像分割是将图像分割成多个片段的过程。这些片段具有相似的颜色或强度。这些片段在医学、交通、天文学或其他方面通常也有的含义。

分割图像最简单的方法是使用阈值,这将产生两个片段(如果值等于阈值,我们将它们放在两个片段之一中)。大津阈值方法最小化两个片段的加权方差(参考以下等式):

Extracting patches from an image

如果我们分割图像,去除噪声或外来伪像是个好主意。借助膨胀(参见也参见部分),我们可以找到图像中属于背景和前景的部分。然而,膨胀给我们留下了未识别的像素。

做好准备

按照中的说明设置 OpenCV

怎么做...

  1. 进口情况如下:

    import numpy as np
    import cv2
    from matplotlib import pyplot as plt
    from sklearn.datasets import load_sample_image
    import dautil as dl
    from IPython.display import HTML
    
  2. 将原始图像绘制如下:

    sp = dl.plotting.Subplotter(2, 2, context)
    img = load_sample_image('flower.jpg')
    dl.plotting.img_show(sp.ax, img)
    sp.label()
    
  3. 如下绘制大津阈值图像:

    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    _, thresh = cv2.threshold(gray, 0, 255,
                              cv2.THRESH_OTSU)
    
    dl.plotting.img_show(sp.next_ax(), thresh)
    sp.label()
    
  4. 绘制前景和背景分散的图像,如下所示:

    kernel = np.ones((3, 3), np.uint8)
    opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN,
                               kernel, iterations=2)
    
    bg = cv2.dilate(opening, kernel, iterations=3)
    
    dist_transform = cv2.distanceTransform(opening, cv2.DIST_L2, 5)
    _, fg = cv2.threshold(dist_transform, 0.7 * dist_transform.max(),
                          255, 0)
    
    fg = np.uint8(fg)
    rest = cv2.subtract(bg, fg)
    
    dl.plotting.img_show(sp.next_ax(), rest)
    sp.label()
    
  5. 用标记绘制图像,如下所示:

    _, markers = cv2.connectedComponents(fg)
    markers += 1
    markers[rest == 255] = 0
    
    dl.plotting.img_show(sp.next_ax(), markers)
    sp.label()
    
    HTML(sp.exit())
    

有关最终结果,请参考以下屏幕截图:

How to do it...

代码在本书代码包的 extracting_patches.ipynb文件中。

另见

用哈尔级联检测人脸

人脸是人体解剖学的一个识别特征。严格来说,许多动物也有脸,但这与大多数实际应用不太相关。人脸检测试图在图像中找到代表人脸的(矩形)区域。人脸检测是物体检测的一种,因为人脸是物体的一种。

大多数人脸检测算法都擅长检测干净的人脸,因为大多数训练图像都属于这一类。倾斜的面部、明亮的灯光或嘈杂的图像可能会导致面部检测出现问题。从一张脸推断年龄、性别或种族(例如内眦赘皮的存在)是可能的,这当然对营销有用。

一个可能的应用是分析社交媒体网站上的个人资料图片。OpenCV 使用基于哈尔特征的级联分类器系统来检测人脸。该系统还被命名为“紫百合-琼斯目标检测框架”T4,以其在 2001 年提出的“T5”清单命名。

该算法具有以下步骤:

  1. 哈尔特征选择:哈尔特征类似于哈尔小波(如第 6 章信号处理和时间序列应用离散小波变换配方所述)。
  2. 创建完整图像(参考使用 SURF 配方检测特征)。
  3. Adaboost 训练(参考第九章集成学习和降维Boosting 更好的学习食谱)。
  4. 级联分类器。

当我们看人脸图像时,我们可以创建与亮度相关的试探法。

例如,鼻子区域比直接位于其左右两侧的区域更亮。因此,我们可以定义一个覆盖鼻子的白色矩形和覆盖邻近区域的黑色矩形。当然 Viola-Jones 系统并不知道鼻子的确切位置,但是通过定义不同大小的窗口并寻找相应的白色和黑色矩形,就有机会匹配到鼻子。实际的哈尔特征被定义为黑色矩形的亮度总和和相邻矩形的亮度总和。对于 24 x 24 的窗口,我们有超过 16 万个功能(大约是 24 的四次方)。

训练集由大量正面(有脸)图像和负面(没有脸)图像组成。只有大约 0.01%的窗口(大约 24×24 像素)实际包含人脸。级联分类器逐步过滤掉负像区域。在每个渐进阶段,分类器在更少的图像窗口上逐渐使用更多的特征。这个想法是把大部分时间花在包含人脸的图像块上。维奥拉和琼斯的原始论文有 38 个阶段,前五个阶段有 1、10、25、25 和 50 个特征。平均每个图像窗口评估 10 个特征。

在 OpenCV 中,可以自己训练一个级联分类器,如中所述。然而,OpenCV 有预先训练好的人脸、眼睛和其他特征的分类器。这些分类器的配置存储为 XML 文件,可以在安装 OpenCV 的文件夹中找到(在我的机器上,/usr/local/share/OpenCV/haarcascades /)。

做好准备

按照中的说明设置 OpenCV

怎么做...

  1. 进口情况如下:

    import cv2
    from scipy.misc import lena
    import matplotlib.pyplot as plt
    import numpy as np
    import dautil as dl
    import os
    from IPython.display import HTML
    
  2. 定义以下功能,绘制检测到人脸的图像(如果检测到):

    def plot_with_rect(ax, img):
        img2 = img.copy()
    
        for x, y, w, h in face_cascade.detectMultiScale(img2, 1.3, 5):
            cv2.rectangle(img2, (x, y), (x + w, y + h), (255, 0, 0), 2)
    
        dl.plotting.img_show(ax, img2, cmap=plt.cm.gray)
    
  3. 下载 XML 配置文件,创建分类器:

    # dir = '/usr/local/share/OpenCV/haarcascades/'
    base = 'https://raw.githubusercontent.com/Itseez/opencv/master/data/'
    url = base + 'haarcascades/haarcascade_frontalface_default.xml'
    path = os.path.join(dl.data.get_data_dir(),
                        'haarcascade_frontalface_default.xml')
    
    if not dl.conf.file_exists(path):
        dl.data.download(url, path)
    
    face_cascade = cv2.CascadeClassifier(path)
    
  4. 用检测到的人脸绘制原始图像:

    sp = dl.plotting.Subplotter(2, 2, context)
    img = lena().astype(np.uint8)
    plot_with_rect(sp.ax, img)
    sp.label()
    
  5. 绘制轻微旋转的图像(检测失败):

    rows, cols = img.shape
    mat = cv2.getRotationMatrix2D((cols/2, rows/2), 21, 1)
    rot = cv2.warpAffine(img, mat, (cols, rows))
    plot_with_rect(sp.next_ax(), rot)
    sp.label()
    
  6. 绘制添加了噪声的图像(检测失败):

    np.random.seed(36)
    noisy = img * (np.random.random(img.shape) < 0.6)
    plot_with_rect(sp.next_ax(), noisy)
    sp.label()
    
  7. 用检测到的人脸绘制模糊图像:

    blur = cv2.blur(img, (9, 9))
    plot_with_rect(sp.next_ax(), blur)
    sp.label()
    
    HTML(sp.exit())
    

有关最终结果,请参考以下屏幕截图:

How to do it...

代码在本书代码包的detecting_faces.ipynb文件中。

另见

寻找明亮的星星

许多星星在晚上是可见的,甚至不用望远镜或任何其他光学设备。一般来说,恒星比行星地球大,但在它们进化的某些阶段,它们可以更小。由于距离很远,它们看起来像小点。通常,这些点由两颗(双星系统)或更多恒星组成。不是所有的恒星都发出可见光,也不是所有的星光都能到达我们这里。

我们有许多方法可以在星空图像中找到明亮的星星。在这个食谱中,我们将寻找亮度的局部最大值,它也高于一个阈值。为了确定亮度,我们将把图像转换到 HSV 颜色空间。在这个颜色空间中,三个维度是色调、饱和度和值(亮度)。OpenCV split()将颜色空间中的图像值转换为组成值,例如色调、饱和度和亮度。这是一个相对缓慢的操作。为了找到最大值,我们可以应用 SciPy argrelmax()函数。

做好准备

按照中的说明设置 OpenCV 配方。

怎么做...

  1. 进口情况如下:

    import dautil as dl
    import os
    import cv2
    import matplotlib.pyplot as plt
    from scipy.signal import argrelmax
    import numpy as np
    from IPython.display import HTML
    
  2. 定义以下功能来扫描水平或垂直轴上的局部亮度峰值:

    def scan_axis(v, axis):
        argmax = argrelmax(v, order=int(np.sqrt(v.shape[axis])),
                           axis=axis)
    
        return set([(i[0], i[1]) for i in np.column_stack(argmax)])
    
  3. 下载图片分析:

    dir = dl.data.get_data_dir()
    path = os.path.join(dir, 'night-927168_640.jpg')
    base = 'https://pixabay.com/static/uploads/photo/2015/09/06/10/19/'
    url = base + 'night-927168_640.jpg'
    
    if not dl.conf.file_exists(path):
        dl.data.download(url, path)
    
  4. 从图像中提取亮度值:

    img = cv2.imread(path)
    hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
    
    h, s, v = cv2.split(hsv)
    
    # Transform for normalization
    v = v.astype(np.uint16) ** 2
    
  5. 绘制亮度值直方图:

    sp = dl.plotting.Subplotter(2, 2, context)
    sp.ax.hist(v.ravel(), normed=True)
    sp.label()
    
  6. 绘制轴 0 的亮度值直方图:

    dl.plotting.hist_norm_pdf(sp.next_ax(), v.mean(axis=0))
    sp.label()
    
  7. 绘制轴 1 的亮度值直方图:

    dl.plotting.hist_norm_pdf(sp.next_ax(), v.mean(axis=1))
    sp.label()
    
  8. 用我们认为包含亮星的点绘制图像:

    points = scan_axis(v, 0).intersection(scan_axis(v, 1))
    
    limit = np.percentile(np.unique(v.ravel()), 95)
    
    kp = [cv2.KeyPoint(p[1], p[0], 1) for p in points
          if v[p[0], p[1]] > limit]
    with_kp = cv2.drawKeypoints(img, kp, None, (255, 0, 0))
    
    dl.plotting.img_show(sp.next_ax(), with_kp)
    sp.label()
    
    HTML(sp.exit())
    

有关最终结果,请参考以下屏幕截图:

How to do it...

代码在本书代码包的 searching_stars.ipynb文件中。

另见

从图像中提取元数据

数字照片经常包含额外的文本元数据,例如时间戳、曝光信息和地理位置。其中一些元数据可由相机所有者编辑。例如,在营销环境中,从社交媒体网站上的个人资料(或其他)图像中提取元数据可能会很有用。据称,告密者爱德华·斯诺登声称美国国家安全局正在从全球在线数据中收集 EXIF 元数据。

做好准备

在这个食谱中,我们将使用 ExifRead 提取 EXIF 元数据。

按照以下步骤安装 ExifRead:

$ pip install ExifRead

我用 ExifRead 2.1.2 测试了代码。

怎么做...

  1. 进口情况如下:

    import exifread
    import pprint
    
  2. 打开图像如下:

    f = open('covers.jpg', 'rb')
    
  3. 打印标签和密钥如下:

    # Return Exif tags
    tags = exifread.process_file(f)
    print(tags.keys())
    pprint.pprint(tags)
    f.close()
    

参考以下最终结果:

dict_keys(['EXIF Flash', 'Image Make', 'EXIF Contrast', 'EXIF DateTimeOriginal', 'Image ResolutionUnit', 'EXIF ComponentsConfiguration', 'EXIF ISOSpeedRatings', 'Image ExifOffset', 'Image ImageDescription', 'EXIF MaxApertureValue', 'EXIF ExposureBiasValue', 'Image YResolution', 'Image Orientation', 'EXIF DateTimeDigitized', 'EXIF MeteringMode', 'EXIF Sharpness', 'EXIF WhiteBalance', 'EXIF ExposureTime', 'Image Model', 'EXIF SceneCaptureType', 'Image Software', 'EXIF SceneType', 'EXIF SubjectDistanceRange', 'EXIF LightSource', 'EXIF FocalLengthIn35mmFilm', 'Image XResolution', 'Image DateTime', 'EXIF FileSource', 'EXIF ExposureProgram', 'EXIF FocalLength', 'EXIF FNumber', 'EXIF Saturation', 'EXIF ExifImageWidth', 'EXIF ExposureMode', 'EXIF DigitalZoomRatio', 'EXIF FlashPixVersion', 'EXIF ExifVersion', 'EXIF ColorSpace', 'EXIF CustomRendered', 'EXIF GainControl', 'EXIF CompressedBitsPerPixel', 'EXIF ExifImageLength'])
{'EXIF ColorSpace': (0xA001) Short=sRGB @ 406,
 'EXIF ComponentsConfiguration': (0x9101) Undefined=YCbCr @ 298,
 'EXIF CompressedBitsPerPixel': (0x9102) Ratio=2 @ 650,
 'EXIF Contrast': (0xA408) Short=Normal @ 550,
 'EXIF CustomRendered': (0xA401) Short=Normal @ 466,
 'EXIF DateTimeDigitized': (0x9004) ASCII=0000:00:00 00:00:00 @ 630,
 'EXIF DateTimeOriginal': (0x9003) ASCII=0000:00:00 00:00:00 @ 610,
 'EXIF DigitalZoomRatio': (0xA404) Ratio=0 @ 682,
 'EXIF ExifImageLength': (0xA003) Long=240 @ 430,
 'EXIF ExifImageWidth': (0xA002) Long=940 @ 418,
 'EXIF ExifVersion': (0x9000) Undefined=0220 @ 262,
 'EXIF ExposureBiasValue': (0x9204) Signed Ratio=0 @ 658,
 'EXIF ExposureMode': (0xA402) Short=Auto Exposure @ 478,
 'EXIF ExposureProgram': (0x8822) Short=Program Normal @ 238,
 'EXIF ExposureTime': (0x829A) Ratio=10/601 @ 594,
 'EXIF FNumber': (0x829D) Ratio=14/5 @ 602,
 'EXIF FileSource': (0xA300) Undefined=Digital Camera @ 442,
 'EXIF Flash': (0x9209) Short=Flash fired, auto mode @ 370,
 'EXIF FlashPixVersion': (0xA000) Undefined=0100 @ 394,
 'EXIF FocalLength': (0x920A) Ratio=39/5 @ 674,
 'EXIF FocalLengthIn35mmFilm': (0xA405) Short=38 @ 514,
 'EXIF GainControl': (0xA407) Short=None @ 538,
 'EXIF ISOSpeedRatings': (0x8827) Short=50 @ 250,
 'EXIF LightSource': (0x9208) Short=Unknown @ 358,
 'EXIF MaxApertureValue': (0x9205) Ratio=3 @ 666,
 'EXIF MeteringMode': (0x9207) Short=Pattern @ 346,
 'EXIF Saturation': (0xA409) Short=Normal @ 562,
 'EXIF SceneCaptureType': (0xA406) Short=Standard @ 526,
 'EXIF SceneType': (0xA301) Undefined=Directly Photographed @ 454,
 'EXIF Sharpness': (0xA40A) Short=Normal @ 574,
 'EXIF SubjectDistanceRange': (0xA40C) Short=0 @ 586,
 'EXIF WhiteBalance': (0xA403) Short=Auto @ 490,
 'Image DateTime': (0x0132) ASCII=0000:00:00 00:00:00 @ 184,
 'Image ExifOffset': (0x8769) Long=204 @ 126,
 'Image ImageDescription': (0x010E) ASCII=           @ 134,
 'Image Make': (0x010F) ASCII=NIKON @ 146,
 'Image Model': (0x0110) ASCII=E7900 @ 152,
 'Image Orientation': (0x0112) Short=Horizontal (normal) @ 54,
 'Image ResolutionUnit': (0x0128) Short=Pixels/Inch @ 90,
 'Image Software': (0x0131) ASCII=E7900v1.1 @ 174,
 'Image XResolution': (0x011A) Ratio=300 @ 158,
 'Image YResolution': (0x011B) Ratio=300 @ 166}

代码在本书代码包的img_metadata.py文件中。

另见

从图像中提取纹理特征

纹理是图像的空间和视觉质量。在这个食谱中,我们将看看 哈拉利克纹理特征。这些特征基于如下定义的共现矩阵 (11.5) :

Extracting texture features from images

在等式 11.5 中, ij 是强度,而 pq 是位置。哈拉里克特征是从共现矩阵中导出的 13 个度量,其中一些在等式 11.6 中给出。更多完整列表,请参考http://murphylab . web . CMU . edu/publications/Boland/Boland _ node 26 . html(2015 年 12 月检索)。

我们将使用 mahotas API 计算 Haralick 特征,并将其应用于 scikit-learn 的手写数字数据集。

做好准备

按照以下步骤安装 maho tas:

$ pip install mahotas

我用 mahotas 1.4.0 测试了代码。

怎么做...

  1. 进口如下:

    import mahotas as mh
    import numpy as np
    from sklearn.datasets import load_digits
    import matplotlib.pyplot as plt
    from tpot import TPOT
    from sklearn.cross_validation import train_test_split
    import dautil as dl
    
  2. 加载 sci kit-学习数字数据如下:

    digits = load_digits()
    X = digits.data.copy()
    
  3. 创建哈拉利克特征并添加它们:

    for i, img in enumerate(digits.images):
        np.append(X[i], mh.features.haralick(
            img.astype(np.uint8)).ravel())
    
  4. 用 TPOT(或我的叉子,如第 9 章集成学习和降维所述)拟合模型并评分:

    X_train, X_test, y_train, y_test = train_test_split(
        X, digits.target, train_size=0.75)
    
    tpot = TPOT(generations=6, population_size=101,
                random_state=46, verbosity=2)
    tpot.fit(X_train, y_train)
    
    print('Score {:.2f}'.format(tpot.score(X_train, y_train, X_test, y_test)))
    
  5. 将第一张原图绘制如下:

    dl.plotting.img_show(plt.gca(), digits.images[0])
    plt.title('Original Image')
    
  6. 绘制该图像的核心特征:

    plt.figure()
    dl.plotting.img_show(plt.gca(), digits.data[0].reshape((8, 8)))
    plt.title('Core Features')
    
  7. 绘制该图像的哈拉利克特征:

    plt.figure()
    dl.plotting.img_show(plt.gca(), mh.features.haralick(
        digits.images[0].astype(np.uint8)))
    plt.title('Haralick Features')
    

有关最终结果,请参考以下屏幕截图:

How to do it...

代码在本书代码包的extracting_texture.ipynb文件中。

另见

对图像应用层次聚类

我们在第 9 章集成学习和降维中遇到了层次聚类的概念。在这个食谱中,我们将通过分层聚类来分割图像。我们将应用凝聚聚类 O(n 3 ,这是的一种层次聚类。

在聚集聚类中,每个项目在初始化时被分配其自己的聚类。随后,这些集群合并(聚集)并根据需要向上移动。显然,我们只合并在某种程度上相似的集群。

初始化之后,我们找到距离最近的一对,并将其合并。合并后的集群是由较低级别的集群组成的较高级别的集群。之后,我们再次找到最接近的一对并将其合并,以此类推。在此过程中,集群可以有任意数量的项目。当我们达到一定数量的集群后,或者当集群相距太远时,我们停止集群。

怎么做...

  1. 进口情况如下:

    import numpy as np
    from scipy.misc import ascent
    import matplotlib.pyplot as plt
    from sklearn.feature_extraction.image import grid_to_graph
    from sklearn.cluster import AgglomerativeClustering
    import dautil as dl
    
  2. 加载图像并将其加载到数组中:

    img = ascent()
    X = np.reshape(img, (-1, 1))
    
  3. 将图像聚类,聚类数设置为9(猜测):

    connectivity = grid_to_graph(*img.shape)
    NCLUSTERS = 9
    ac = AgglomerativeClustering(n_clusters=NCLUSTERS,
                                 connectivity=connectivity)
    ac.fit(X)
    label = np.reshape(ac.labels_, img.shape)
    
  4. 绘制叠加了簇段的图像:

    for l in range(NCLUSTERS):
        plt.contour(label == l, contours=1,
                    colors=[plt.cm.spectral(l/float(NCLUSTERS)), ])
    
    dl.plotting.img_show(plt.gca(), img, cmap=plt.cm.gray)
    

最终结果参见下面的截图:

How to do it...

代码在本书代码包的 clustering_hierarchy.ipynb文件中。

另见

利用光谱聚类分割图像

光谱聚类是一种可以用来分割图像的聚类技术。scikit-learn spectral_clustering()函数实现了归一化图割谱聚类算法。该算法将图像表示为单位图。这里的“图”与第八章文本挖掘和社交网络分析中的数学概念相同。该算法试图分割图像,同时最小化片段大小和沿切割的强度梯度比率。

怎么做...

  1. 进口情况如下:

    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn.feature_extraction.image import img_to_graph
    from sklearn.cluster import spectral_clustering
    from sklearn.datasets import load_digits
    
  2. 加载数字数据集如下:

    digits = load_digits()
    img = digits.images[0].astype(float)
    mask = img.astype(bool)
    
  3. 根据图像创建图形:

    graph = img_to_graph(img, mask=mask)
    graph.data = np.exp(-graph.data/graph.data.std())
    
  4. 应用谱聚类得到三个聚类:

    labels = spectral_clustering(graph, n_clusters=3)
    label_im = -np.ones(mask.shape)
    label_im[mask] = labels
    
  5. 将原始图像绘制如下:

    plt.matshow(img, False)
    plt.gca().axis('off')
    plt.title('Original')
    
  6. 将图像与三个簇绘制如下:

    plt.figure()
    plt.matshow(label_im, False)
    plt.gca().axis('off')
    plt.title('Clustered')
    

有关最终结果,请参考以下屏幕截图:

How to do it...

代码在本书代码包的clustering_spectral.ipynb文件中。

另见

十二、并行性和性能

在本章中,我们将介绍以下食谱:

  • 用 Numba 进行即时编译
  • 用 Numexpr 加速数值表达式
  • 使用threading模块运行多个线程
  • 使用concurrent.futures模块启动多个任务
  • asyncio模块异步访问资源
  • execnet的分布式处理
  • 分析内存使用情况
  • 即时计算平均值、方差、偏斜度和峰度
  • 使用最近最少使用的缓存进行缓存
  • 缓存 HTTP 请求
  • 使用最小计数草图进行流式计数
  • 利用 OpenCL 开发图形处理器的能力

简介

1943 年至 1946 年间建造的 ENIAC,用一万八千根管子填满了一个大房间,拥有 20 位内存。从那以后,我们走过了漫长的道路。摩尔定律也预测了这种指数增长。当然,我们面对的是一个自我实现的预言还是一个基本现象,这很难说。据称,增长开始减速。

鉴于我们目前对技术、热力学和量子力学的了解,我们可以为摩尔定律设定硬性限制。然而,我们的假设可能是错误的;例如,科学家和工程师可能会想出从根本上更好的技术来制造芯片。(其中一个发展就是量子计算,目前还远未普及。)最大的障碍是散热,散热通常以 kT 为单位测量,K 为玻尔兹曼常数(约 10-23 J/K),而 T 以开尔文为单位(冰点为 273.15 K)。一个芯片的每比特散热量至少为kT(350K 时为 10-20 J)。90 年代的半导体至少消耗了十万 kT 。计算系统在运行过程中会经历能量水平的变化。能量上最小的容许差大概是 100 kT 。即使我们设法避免了这种限制,我们也很快会在接近原子水平的水平上运行,由于量子力学的原因,这是不切实际的(关于粒子的信息从根本上是有限的),除非我们谈论的是量子计算机。目前的共识是,我们将在几十年内达到极限。另一个考虑是芯片的复杂布线。复杂的布线大大降低了芯片的预期寿命。

本章是关于软件性能的;然而,还有其他更重要的软件方面,例如可维护性、健壮性和可用性。押注摩尔定律是有风险的,也是不现实的,因为我们还有其他提高性能的可能性。第一种选择是使用多台机器、单台机器上的内核、GPU 或其他专用硬件(如)尽可能并行地完成工作。例如,我正在八核机器上测试代码。作为一名学生,我有幸参与了一个以创建网格为目标的项目。网格被认为是将大学计算机聚集到一个单一的计算环境中。在后期,也有计划连接其他计算机,有点像 SETI 项目。(众所周知,很多办公电脑在周末和晚上都是闲置的,为什么不让它们也工作呢?)

当然,目前有各种各样的商业云系统,比如亚马逊和谷歌提供的。我不会讨论这些,因为我觉得这些是更专业的主题,尽管我在 Python 数据分析中确实介绍了一些 Python 特有的云系统。

提高性能的第二种方法是应用缓存,从而避免不必要的函数调用。我在第 9 章集成学习和降维中介绍了具有缓存功能的 joblib 库。Python 3 为我们带来了并行性和缓存的新特性。

第三种方法是靠近金属。众所周知,Python 是一种带有虚拟机和解释器的高级编程语言。Python 有一个额外的层,这是一种不同于 C 语言的语言。当我还是个学生的时候,我们被教导 C 是一种高级语言,以汇编和机器码为低级。据我所知,现在,几乎没有人用汇编语言编写代码。通过 Cython(包含在 Python 数据分析中)和类似的软件,我们可以编译我们的代码来获得与 C 和 C++相当的性能。编译是一件麻烦的事情,也是有问题的,因为它降低了平台依赖性带来的可移植性。一个常见的解决方案是使用 shell 脚本自动编译并生成文件。Numba 和其他类似的项目通过即时编译让生活变得更加容易,尽管有一些限制。

用 Numba 及时编译

Numba 软件使用特殊的函数装饰器执行即时编译。编译会自动生成本机机器代码。生成的代码可以在 CPU 和 GPU 上运行。Numba 的主要用例是使用 NumPy 数组的数学密集型代码。

我们可以用带有可选函数签名的@numba.jit装饰器(例如int32(int32))编译代码。这些类型对应于相似的 NumPy 类型。Numba 在nopythonobject模式下运行。nopython模式速度更快,但限制更多。我们也可以通过nogil选项释放 全局解释器锁 ( GIL )。您可以通过使用cache参数请求文件缓存来缓存编译结果。

@vectorize装饰器将带有标量参数的函数转换成 NumPy ufuncs。向量化提供了额外的优势,例如自动广播,并且可以在单核、多核并行或 GPU 上使用。

做好准备

使用以下命令安装 Numba:

$ pip/conda install numba

我用 Numba 0.22.1 测试了代码。

怎么做...

  1. 进口情况如下:

    from numba import vectorize
    from numba import jit
    import numpy as np
    
  2. 定义以下功能来使用@vectorize装饰器:

    @vectorize
    def vectorize_version(x, y, z):
        return x ** 2 + y ** 2 + z ** 2
    
  3. 定义以下功能来使用@jit装饰器:

    @jit(nopython=True)
    def jit_version(x, y, z):
        return x ** 2 + y ** 2 + z ** 2
    
  4. 定义一些随机数组如下:

    np.random.seed(36)
    x = np.random.random(1000)
    y = np.random.random(1000)
    z = np.random.random(1000)
    
  5. 测量数组平方和所需的时间:

    %timeit x ** 2 + y ** 2 + z ** 2
    %timeit vectorize_version(x, y, z)
    %timeit jit_version(x, y, z)
    jit_version.inspect_types()
    

最终结果参见以下截图:

How to do it...

代码在本书代码包的compiling_numba.ipynb文件中。

它是如何工作的

在我的机器上测得的最佳时间是 1.82 微秒,这比正常 Python 代码测得的时间快得多。在截图的最后,我们看到了编译的结果,最后一部分被省略了,因为它太长了,很难阅读。我们会收到警告,这很可能是由 CPU 缓存引起的。我是故意留下的,但是你可以使用不适合缓存的更大的数组来删除它们。

另见

用 Numexpr 加速数值表达式

Numexpr 是一个数值数组表达式求值的软件包,在你安装 pandas 的时候也安装了,你可能在其他菜谱的水印里看到过公布(用 Numexpr 2.3.1 测试过)。Numexpr 试图通过避免创建临时变量来加快计算速度,因为读取变量可能是一个潜在的瓶颈。对于无法容纳在中央处理器高速缓存中的数组,预计加速比最大。

Numexpr 将大数组分割成块,这些块可以放入缓存中,并且它还在可能的情况下并行使用多个内核。它有一个evaluate()函数,该函数接受简单表达式并对其求值(有关支持特性的完整列表,请参考文档)。

怎么做...

  1. 进口情况如下:

    import numexpr as ne
    import numpy as np
    
  2. 生成随机数组,数组应该太大,无法保存在缓存中:

    a = np.random.rand(1e6)
    b = np.random.rand(1e6)
    
  3. 计算一个简单的算术表达式并测量执行时间:

    %timeit 2 * a ** 3 + 3 * b ** 9
    %timeit ne.evaluate("2 * a ** 3 +3 * b ** 9 ")
    

有关最终结果,请参考以下屏幕截图:

How to do it...

代码在本书代码包的speeding_numexpr.ipynb文件中。

它是如何工作的

我们生成了不应该放在缓存中的随机数据,以避免缓存效应,因为这是 Numexpr 的最佳用例。缓存的大小因机器而异,因此如有必要,请为数组使用更大或更小的大小。在示例中,我们放入了一个包含简单算术表达式的字符串,尽管我们可以使用稍微复杂一点的表达式。有关更多详细信息,请参考文档。我用一台有八个内核的机器测试了代码。加速比大于八倍,所以这显然是由于 Numexpr。

另见

用线程模块运行多个线程

计算机进程是正在运行的程序的一个实例。进程实际上是重量级的,所以我们可能更喜欢线程,线程更轻。事实上,线程通常只是进程的子单元。进程是相互分离的,而线程可以共享指令和数据。

操作系统通常为每个内核分配一个线程(如果有多个),或者定期在线程之间切换;这叫 时间切片。线程作为进程可以有不同的优先级,操作系统有后台运行的守护线程,优先级非常低。

线程之间的切换比进程之间的切换更容易;但是,由于线程共享信息,因此使用起来更加危险。例如,如果多个线程能够同时增加一个计数器,这将使代码不确定,并且可能不正确。最小化风险的一种方法是确保一次只有一个线程可以访问共享变量或共享函数。这个策略在 Python 中实现为 GIL。

怎么做...

  1. 进口情况如下:

    import dautil as dl
    import ch12util
    from functools import partial
    from queue import Queue
    from threading import Thread
    import matplotlib.pyplot as plt
    import numpy as np
    from scipy.stats import skew
    from IPython.display import HTML
    
    STATS = []
    
  2. 定义以下功能重新采样:

    def resample(arr):
        sample = ch12util.bootstrap(arr)
        STATS.append((sample.mean(), sample.std(), skew(sample)))
    
  3. 定义以下要引导的类:

    class Bootstrapper(Thread):
        def __init__(self, queue, data):
            Thread.__init__(self)
            self.queue = queue
            self.data = data
            self.log = dl.log_api.conf_logger(__name__)
    
        def run(self):
            while True:
                index = self.queue.get()
    
                if index % 10 == 0:
                    self.log.debug('Bootstrap {}'.format(
                        index))
    
                resample(self.data)
                self.queue.task_done()
    
  4. 定义以下函数来执行串行重采样:

    def serial(arr, n):
        for i in range(n):
            resample(arr)
    
  5. 定义以下函数执行并行重采样:

    def threaded(arr, n):
        queue = Queue()
    
        for x in range(8):
            worker = Bootstrapper(queue, arr)
            worker.daemon = True
            worker.start()
    
        for i in range(n):
            queue.put(i)
    
        queue.join()
    
  6. 绘制时刻分布和执行时间:

    sp = dl.plotting.Subplotter(2, 2, context)
    temp = dl.data.Weather.load()['TEMP'].dropna().values
    np.random.seed(26)
    threaded_times = ch12util.time_many(partial(threaded, temp))
    serial_times = ch12util.time_many(partial(serial, temp))
    
    ch12util.plot_times(sp.ax, serial_times, threaded_times)
    
    stats_arr = np.array(STATS)
    ch12util.plot_distro(sp.next_ax(), stats_arr.T[0], temp.mean())
    sp.label()
    
    ch12util.plot_distro(sp.next_ax(), stats_arr.T[1], temp.std())
    sp.label()
    
    ch12util.plot_distro(sp.next_ax(), stats_arr.T[2], skew(temp))
    sp.label()
    
    HTML(sp.exit())
    

有关最终结果,请参考以下屏幕截图:

How to do it...

代码在本书的代码包中的 running_threads.ipynb文件中。

另见

用并发期货模块启动多个任务

concurrent.futures模块是一个 Python 模块,我们可以用它异步执行可调用的东西。如果您熟悉 Java 并浏览该模块,您会注意到与等效 Java API 的一些相似之处,例如类名和体系结构。根据 Python 文档,这不是巧合。

在这种情况下,任务是一个独立的工作单元。例如,打印文档可以被认为是一项任务,但通常我们会考虑更小的任务,例如添加两个数字。

怎么做...

  1. 进口情况如下:

    import dautil as dl
    import ch12util
    from functools import partial
    import matplotlib.pyplot as plt
    import numpy as np
    from scipy.stats import skew
    import concurrent.futures
    from IPython.display import HTML
    
    STATS = []
    
  2. 定义以下函数进行重采样:

    def resample(arr):
        sample = ch12util.bootstrap(arr)
        STATS.append((sample.mean(), sample.std(), skew(sample)))
    
  3. 定义以下要引导的类:

    class Bootstrapper():
        def __init__(self, data):
            self.data = data
            self.log = dl.log_api.conf_logger(__name__)
    
        def run(self, index):
            if index % 10 == 0:
                self.log.debug('Bootstrap {}'.format(
                    index))
    
            resample(self.data)
    
  4. 定义以下函数来执行串行重采样:

    def serial(arr, n):
        for i in range(n):
            resample(arr)
    
  5. 定义以下函数执行并行重采样:

    def parallel(arr, n):
        executor = concurrent.futures.ThreadPoolExecutor(max_workers=8)
        bootstrapper = Bootstrapper(arr)
    
        for x in executor.map(bootstrapper.run, range(n)):
            pass
    
        executor.shutdown()
    
  6. 绘制时刻分布和执行时间:

    rain = dl.data.Weather.load()['RAIN'].dropna().values
    np.random.seed(33)
    parallel_times = ch12util.time_many(partial(parallel, rain))
    serial_times = ch12util.time_many(partial(serial, rain))
    
    sp = dl.plotting.Subplotter(2, 2, context)
    ch12util.plot_times(sp.ax, serial_times, parallel_times)
    
    STATS = np.array(STATS)
    ch12util.plot_distro(sp.next_ax(), STATS.T[0], rain.mean())
    sp.label()
    
    ch12util.plot_distro(sp.next_ax(), STATS.T[1], rain.std())
    sp.label()
    
    ch12util.plot_distro(sp.next_ax(), STATS.T[2], skew(rain))
    sp.label()
    HTML(sp.exit())
    

有关最终结果,请参考以下屏幕截图:

How to do it...

代码在本书的代码包中的 launching_futures.ipynb文件中。

另见

与异步模块异步访问资源

输入/输出(例如,文件或数据库访问)缓慢是生活中的一个基本事实。I/O 不仅慢,而且不可预测。在一个常见的场景中,我们等待数据(来自 web 服务或传感器)并将数据写入文件系统或数据库。在这种情况下,我们会发现自己受到输入/输出的限制,花费更多的时间等待数据,而不是实际处理数据。我们可以定期轮询数据或触发事件(检查您的手表或设置警报)。图形用户界面通常有特殊的线程,在无限循环中等待用户输入。

异步输入输出的 Python asyncio模块使用了的概念,并通过相关的函数装饰器来协调。在第 5 章网络挖掘、数据库和大数据刮网配方中也给出了该模块的一个简单示例。子程序可以看作是协同程序的一个特例。一个子例程有一个开始和结束点,要么通过一个返回语句提前结束,要么到达子例程定义的末尾。相比之下,一个协同程序可以通过调用另一个协同程序,然后从该退出点恢复执行,来产生yield from语句。可以说,科罗廷正在让另一个科罗廷接管,并回到睡眠状态,直到它再次被激活。

子程序可以放在一个堆栈上。然而,协同程序需要多个堆栈,这使得理解代码和潜在的异常更加复杂。

怎么做...

代码在本书代码包的accessing_asyncio.ipynb文件中:

  1. 进口情况如下:

    import dautil as dl
    import ch12util
    from functools import partial
    import matplotlib.pyplot as plt
    import numpy as np
    from scipy.stats import skew
    import asyncio
    import time
    from IPython.display import HTML
    
    STATS = []
    
  2. 定义以下函数进行重采样:

    def resample(arr):
        sample = ch12util.bootstrap(arr)
        STATS.append((sample.mean(), sample.std(), skew(sample)))
    
  3. 定义以下要引导的类:

    class Bootstrapper():
        def __init__(self, data, queue):
            self.data = data
            self.log = dl.log_api.conf_logger(__name__)
            self.queue = queue
    
        @asyncio.coroutine
        def run(self):
            while not self.queue.empty():
                index = yield from self.queue.get()
    
                if index % 10 == 0:
                    self.log.debug('Bootstrap {}'.format(
                        index))
    
                resample(self.data)
                # simulates slow IO
                yield from asyncio.sleep(0.01)
    
  4. 定义以下功能执行串行重采样:

    def serial(arr, n):
        for i in range(n):
            resample(arr)
            # simulates slow IO
            time.sleep(0.01)
    
  5. 定义以下函数执行并行重采样:

    def parallel(arr, n):
        q = asyncio.Queue()
    
        for i in range(n):
            q.put_nowait(i)
    
        bootstrapper = Bootstrapper(arr, q)
        policy = asyncio.get_event_loop_policy()
        policy.set_event_loop(policy.new_event_loop())
        loop = asyncio.get_event_loop()
    
        tasks = [asyncio.async(bootstrapper.run())
                 for i in range(n)]
    
        loop.run_until_complete(asyncio.wait(tasks))
        loop.close()
    
  6. 绘制力矩和执行时间的分布:

    pressure = dl.data.Weather.load()['PRESSURE'].dropna().values
    np.random.seed(33)
    parallel_times = ch12util.time_many(partial(parallel, pressure))
    serial_times = ch12util.time_many(partial(serial, pressure))
    
    dl.options.mimic_seaborn()
    ch12util.plot_times(plt.gca(), serial_times, parallel_times)
    
    sp = dl.plotting.Subplotter(2, 2, context)
    ch12util.plot_times(sp.ax, serial_times, parallel_times)
    
    STATS = np.array(STATS)
    ch12util.plot_distro(sp.next_ax(), STATS.T[0], pressure.mean())
    sp.label()
    
    ch12util.plot_distro(sp.next_ax(), STATS.T[1], pressure.std())
    sp.label()
    
    ch12util.plot_distro(sp.next_ax(), STATS.T[2], skew(pressure))
    sp.label()
    HTML(sp.exit())
    

最终结果参见下面的截图:

How to do it...

另见

使用 execnet 进行分布式处理

execnet模块采用无共享模式,使用通道进行通信。这个上下文中的通道是用于在(分布式)计算机进程之间发送和接收消息的软件抽象。execnet最适用于将异构计算环境与不同的 Python 解释器和安装的软件相结合。这些环境可以有不同的操作系统和 Python 实现(CPython、Jython、pypypy 或其他)。

无共享架构中,计算节点不共享内存或文件。因此,该架构是完全分散的,具有完全独立的节点。明显的优势是我们不依赖任何一个节点。

做好准备

使用以下命令安装 execnet:

$ pip/conda install execnet 

我用 execnet 1.3.0 测试了代码。

怎么做...

  1. 进口情况如下:

    import dautil as dl
    import ch12util
    from functools import partial
    import matplotlib.pyplot as plt
    import numpy as np
    import execnet
    
    STATS = []
    
  2. 定义以下助手函数:

    def run(channel, data=[]):
        while not channel.isclosed():
            index = channel.receive()
    
            if index % 10 == 0:
                print('Bootstrap {}'.format(
                    index))
    
            total = 0
    
            for x in data:
                total += x
    
            channel.send((total - data[index])/(len(data) - 1))
    
  3. 定义以下函数来执行串行重采样:

    def serial(arr, n):
        for i in range(n):
            total = 0
    
            for x in arr:
                total += x
    
            STATS.append((total - arr[i])/(len(arr) - 1))
    
  4. 定义以下函数执行并行重采样:

    def parallel(arr, n):
        gw = execnet.makegateway()
        channel = gw.remote_exec(run, data=arr.tolist())
    
        for i in range(n):
            channel.send(i)
            STATS.append(channel.receive())
    
        gw.exit()
    
  5. 绘制平均值和执行时间的分布:

    ws = dl.data.Weather.load()['WIND_SPEED'].dropna().values
    np.random.seed(33)
    parallel_times = ch12util.time_many(partial(parallel, ws))
    serial_times = ch12util.time_many(partial(serial, ws))
    
    %matplotlib inline
    dl.options.mimic_seaborn()
    ch12util.plot_times(plt.gca(), serial_times, parallel_times)
    plt.legend(loc='best')
    
    plt.figure()
    STATS = np.array(STATS)
    ch12util.plot_distro(plt.gca(), STATS, ws.mean())
    plt.title('Distribution of the Means')
    plt.legend(loc='best')
    

有关最终结果,请参考以下屏幕截图:

How to do it...

代码在书的代码包中的distributing_execnet.ipynb文件中。

另见

分析内存使用情况

Python 数据分析中,我们使用了各种分析工具。这些工具主要与测量执行时间有关。然而,记忆也很重要,尤其是当我们没有足够的记忆时。内存泄漏 是计算机程序的常见问题,我们可以通过执行内存分析来发现。当我们不释放不需要的内存时,就会发生泄漏。当我们使用需要比我们需要更多内存的数据类型时,也可能会出现问题,例如,整数数组就可以使用 NumPy float64数组。

Python memory_profiler模块可以逐行剖析代码的内存使用情况。安装后,您还可以通过各种神奇的命令在 IPython 笔记本中使用该模块。该模块通过与操作系统通信来工作。在 Windows 上,您将需要 Python psutil包进行通信。

做好准备

用以下命令安装memory_profiler:

$ pip install memory-profiler 

我用 memory_profiler 0.39 测试了代码。

创建一个脚本进行分析(参见本书代码包中的mem_test.py文件):

import numpy as np

def test_me():
    a = np.random.random((999, 99))
    b = np.random.random((99, 99))
    a.ravel()
    b.tolist()

怎么做...

  1. 进口情况如下:

    import dautil as dl
    from mem_test import test_me
    
  2. 按如下方式加载 IPython 扩展:

    %load_ext memory_profiler
    
  3. 使用以下命令逐行剖析测试脚本:

    %mprun -f test_me test_me()
    

有关最终结果,请参考以下屏幕截图:

How to do it...

代码在本书的代码包中的 profiling_memory.ipynb文件中。

另见

即时计算平均值、方差、偏斜度和峰度

均值、方差、偏斜度和峰度是统计学中的重要量。一些计算涉及平方的和,对于大的值可能导致溢出。为了避免精度的损失,我们必须认识到方差在偏移某个常数时是不变的。

当我们在内存中有足够的空间时,我们可以直接计算力矩,必要时考虑数值问题。然而,我们可能不想将数据保存在内存中,因为它很多,或者因为它更便于计算飞行中的时刻。

一个在线和数值稳定的算法来计算方差已经提供了特里贝里(特里贝里,蒂莫西(2007),计算高阶矩在线)。我们将把这个算法与LiveStats模块中的实现进行比较,尽管它不是最好的算法。如果你对改进的算法感兴趣,看看中列出的维基百科页面,也可以参见部分。

看看下面的等式:

Calculating the mean, variance, skewness, and kurtosis on the fly

偏斜度由 12.6 给出,峰度由 12.7 给出。

做好准备

使用以下命令安装 LiveStats:

$ pip install LiveStats 

我用 LiveStats 1.0 测试了代码。

怎么做...

  1. 进口情况如下:

    from livestats import livestats
    from math import sqrt
    import dautil as dl
    import numpy as np
    from scipy.stats import skew
    from scipy.stats import kurtosis
    import matplotlib.pyplot as plt
    
  2. 定义以下函数来实现力矩计算的方程:

    # From https://en.wikipedia.org/wiki/
    # Algorithms_for_calculating_variance
    def online_kurtosis(data):
        n = 0
        mean = 0
        M2 = 0
        M3 = 0
        M4 = 0
        stats = []
    
        for x in data:
            n1 = n
            n = n + 1
            delta = x - mean
            delta_n = delta / n
            delta_n2 = delta_n ** 2
            term1 = delta * delta_n * n1
            mean = mean + delta_n
            M4 = M4 + term1 * delta_n2 * (n**2 - 3*n + 3) + \
                6 * delta_n2 * M2 - 4 * delta_n * M3
            M3 = M3 + term1 * delta_n * (n - 2) - 3 * delta_n * M2
            M2 = M2 + term1
            s = sqrt(n) * M3 / sqrt(M2 ** 3)
            k = (n*M4) / (M2**2) - 3
            stats.append((mean, sqrt(M2/(n - 1)), s, k))
    
        return np.array(stats)
    
  3. 初始化并加载数据如下:

    test = livestats.LiveStats([0.25, 0.5, 0.75])
    
    data = dl.data.Weather.load()['TEMP'].\
        resample('M').dropna().values
    
  4. 用 LiveStats 计算各种统计,上节提到的算法,和对比一下我们一次对所有数据应用 NumPy 函数时的结果:

    ls = []
    truth = []
    
    test.add(data[0])
    
    for i in range(1, len(data)):
        test.add(data[i])
        q1, q2, q3 = test.quantiles()
    
        ls.append((test.mean(), sqrt(test.variance()),
                  test.skewness(), test.kurtosis(), q1[1], q2[1], q3[1]))
        slice = data[:i]
        truth.append((slice.mean(), slice.std(),
                      skew(slice), kurtosis(slice),
                      np.percentile(slice, 25), np.median(slice),
                      np.percentile(slice, 75)))
    
    ls = np.array(ls)
    truth = np.array(truth)
    ok = online_kurtosis(data)
    
  5. 将结果绘制为如下:

    dl.options.mimic_seaborn()
    cp = dl.plotting.CyclePlotter(plt.gca())
    cp.plot(ls.T[0], label='LiveStats')
    cp.plot(truth.T[0], label='Truth')
    cp.plot(data)
    plt.title('Live Stats Means')
    plt.xlabel('# points')
    plt.ylabel('Mean')
    plt.legend(loc='best')
    
    plt.figure()
    
    mses = [dl.stats.mse(truth.T[i], ls.T[i])
            for i in range(7)]
    mses.extend([dl.stats.mse(truth.T[i], ok[1:].T[i])
                 for i in range(4)])
    dl.plotting.bar(plt.gca(),
                    ['mean', 'std', 'skew', 'kurt',
                     'q1', 'q2', 'q3',
                     'my_mean', 'my_std', 'my_skew', 'my_kurt'], mses)
    plt.title('MSEs for Various Statistics')
    plt.ylabel('MSE')
    

最终结果参见以下截图:

How to do it...

代码在本书的代码包中的 calculating_moments.ipynb文件中。

另见

使用最近最少使用的缓存进行缓存

缓存包括将结果(通常来自函数调用)存储在内存或磁盘上。如果操作正确,缓存有助于减少函数调用的次数。一般来说,出于空间原因,我们希望保持缓存较小。如果我们能够在缓存中找到项目,我们就谈论命中;否则,我们会有失误。显然,我们希望有尽可能多的命中和尽可能少的失误。这意味着我们希望最大化命中率。

缓存算法有很多,其中我们将介绍最近最少使用的(LRU)算法。该算法跟踪缓存项何时被使用。如果缓存即将超过其最大指定大小,LRU 会删除最近最少使用的项目。理由是这些项目可能更古老,因此不再相关。LRU 有几种变体。其他算法则相反——删除最近的项目、最不常用的项目或随机项目。

标准的 Python 库有一个 LRU 的实现,但也有一个专门的 Python 库,其中一些部分用 C 实现,因此它可能更快。我们将使用 NLTK lemmatize()方法比较两种实现(参考第 8 章文本挖掘和社交网络分析中的词干、引理、过滤和 TF-IDF 评分方法)。

做好准备

按照以下步骤安装 fastcache:

$ pip/conda install fastcache

我用 fastcache 1.0.2 测试了代码。

怎么做...

  1. 进口情况如下:

    from fastcache import clru_cache
    from functools import lru_cache
    from nltk.corpus import brown
    from nltk.stem import WordNetLemmatizer
    import dautil as dl
    import numpy as np
    from IPython.display import HTML
    
  2. 定义以下函数进行缓存:

    def lemmatize(word, lemmatizer):
        return lemmatizer.lemmatize(word.lower())
    
  3. 定义以下函数来衡量缓存的效果:

    def measure(impl, words, lemmatizer):
        cache = dl.perf.LRUCache(impl, lemmatize)
        times = []
        hm = []
    
        for i in range(5, 12):
            cache.maxsize = 2 ** i
            cache.cache()
            with dl.perf.StopWatch() as sw:
                _ = [cache.cached(w, lemmatizer) for w in words]
    
            hm.append(cache.hits_miss())
            times.append(sw.elapsed)
            cache.clear()
    
        return (times, hm)
    
  4. 初始化一个单词列表和一个 NLTK WordNetLemmatizer对象:

    words = [w for w in brown.words()]
    lemmatizer = WordNetLemmatizer()
    
  5. 测量执行时间如下:

    with dl.perf.StopWatch() as sw:
        _ = [lemmatizer.lemmatize(w.lower()) for w in words]
    
    plain = sw.elapsed
    
    times, hm = measure(clru_cache, words, lemmatizer)
    
  6. 绘制不同缓存大小的结果:

    sp = dl.plotting.Subplotter(2, 2, context)
    sp.ax.plot(2 ** np.arange(5, 12), times)
    sp.ax.axhline(plain, lw=2, label='Uncached')
    sp.label()
    
    sp.next_ax().plot(2 ** np.arange(5, 12), hm)
    sp.label()
    
    times, hm = measure(lru_cache, words, lemmatizer)
    sp.next_ax().plot(2 ** np.arange(5, 12), times)
    sp.ax.axhline(plain, lw=2, label='Uncached')
    sp.label()
    
    sp.next_ax().plot(2 ** np.arange(5, 12), hm)
    sp.label()
    HTML(sp.exit())
    

最终结果参见以下截图:

How to do it...

代码在本书代码包的caching_lru.ipynb文件中。

另见

缓存 HTTP 请求

有时,数据可以通过 HTTP 上的网络服务获得。这样做的好处是,我们不必太在意发送方使用的技术。例如,这与电子邮件的工作方式类似。然而,我们必须通过 HTTP GET(通常)或 HTTP POST(按照惯例大写)方法显式地请求信息。每当我们请求一个网页或下载一个文件时,我们通常会执行一个 GET 请求。另一端的 web 服务器必须处理该请求。如果有很多请求,我们可能会降低服务器的速度,因此组织通常会采取措施来防止这种情况。这可能意味着您的进一步请求将被阻止。

出于效率原因,避免多次发出相同的请求也是有利的。网络浏览器用缓存解决了这个问题,我们可以用requests-cache包做同样的事情。默认情况下,缓存存储在 SQLite 数据库中。

我们将不涉及的一个常见用例是用 HTTP 定期检索信息。显然,如果没有任何变化,我们不想检索内容。HTTP 协议提供了确定内容是否被修改的有效机制。但是,web 服务器不需要报告内容更改。

做好准备

使用以下命令安装请求缓存:

$ pip install --upgrade requests-cache 

我用请求测试了代码——缓存 0.4.10。

怎么做...

  1. 进口情况如下:

    import requests
    import requests_cache
    
  2. 安装缓存(这将默认创建一个 SQLite 数据库):

    requests_cache.install_cache()
    
  3. 请求建立缓存的网站:

    %time requests.get('http://google.com')
    
  4. 请求现在应该来自本地缓存的相同网站:

    %time requests.get('http://google.com')
    
  5. 清除缓存如下:

    requests_cache.clear()
    
  6. 再次请求网站(缓存现在应该是空的):

    %time requests.get('http://google.com')
    

有关最终结果,请参考以下屏幕截图:

How to do it...

代码在本书代码包的 caching_requests.ipynb文件中。

另见

使用最小计数草图进行流式计数

流式或在线算法很有用,因为它们不需要像其他算法那样多的内存和处理能力。本章有一个在线计算统计矩的方法(参考即时计算平均值、方差、偏斜度和峰度)。

此外,在第 5 章网络挖掘、数据库和大数据用 Spark 配方聚类流数据中,我介绍了另一种流算法。

由于基本原因或舍入误差,流算法通常是近似的。因此,如果可能的话,你应该尝试使用其他算法。当然,在许多情况下,近似结果已经足够好了。例如,一个用户在社交媒体网站上有 500 或 501 个连接并不重要。如果你只是发送成千上万的邀请,你迟早会到达那里。

素描是你可能从绘画中知道的东西。在这种情况下,素描意味着勾勒出没有任何细节的物体的大致轮廓。类似的概念存在于流算法的世界中。

在这个食谱中,我涵盖了格雷厄姆·科尔莫和 s .穆图·穆图克里希南(S. Muthu Muthukrishnan)的计数-min 草图 (2003 年),这在排名的上下文中很有用。例如,我们可能想知道新闻网站上浏览次数最多的文章、热门话题、点击次数最多的广告或联系最多的用户。天真的方法需要在字典或表格中记录每个条目的数量。字典使用散列函数来计算作为关键字的识别整数。出于理论上的原因,我们可能会有冲突——这意味着两个或多个项目有相同的密钥。Count-min 草图是一个二维表格数据结构,它很小,并且对每一行都使用散列函数。它容易发生碰撞,导致过度安装。

当事件发生时,例如有人观看广告,我们会执行以下操作:

  1. 对于草图中的每一行,我们应用相关的散列函数,例如,使用广告标识符来获得列索引。
  2. 递增与行和列对应的值。

每个事件都清晰地映射到草图中的每一行。当我们请求计数时,我们遵循相反的路径来获得多个计数。最低计数给出了该项目的估计计数。

这种设置背后的想法是,频繁项目可能会主导不太常见的项目。热门物品与冷门物品碰撞的概率大于热门物品之间的碰撞概率。

怎么做...

  1. 进口情况如下:

    from nltk.corpus import brown
    from nltk.corpus import stopwords
    import dautil as dl
    from collections import defaultdict
    import matplotlib.pyplot as plt
    import numpy as np
    from IPython.display import HTML
    
  2. 将 NLTK Brown 和 stop 单词语料库的单词存储在列表中:

    words_dict = dl.collect.IdDict()
    dd = defaultdict(int)
    fid = brown.fileids(categories='news')[0]
    words = brown.words(fid)
    sw = set(stopwords.words('english'))
    
  3. 计算每个停止字的出现次数:

    for w in words:
        if w in sw:
            dd[w] += 1
    
  4. 绘制最小计数草图各种参数的计数误差分布:

    sp = dl.plotting.Subplotter(2, 2, context)
    actual = np.array([dd[w] for w in sw])
    errors = []
    
    for i in range(1, 4):
        cm = dl.perf.CountMinSketch(depth=5 * 2 ** i,
                                    width=20 * 2 ** i)
    
        for w in words:
            cm.add(words_dict.add_or_get(w.lower()))
    
        estimates = np.array([cm.estimate_count(words_dict.add_or_get(w))
                            for w in sw])
        diff = estimates - actual
        errors.append(diff)
    
        if i > 1:
            sp.next_ax()
    
        sp.ax.hist(diff, normed=True,
                bins=dl.stats.sqrt_bins(actual))
        sp.label()
    
    sp.next_ax().boxplot(errors)
    sp.label()
    HTML(sp.exit())
    

有关最终结果,请参考以下屏幕截图:

How to do it...

代码在本书代码包的 stream_demo.py文件中。

另见

利用 OpenCL 利用图形处理器的能力

开放计算语言 ( OpenCL )最初由苹果公司(Apple Inc .)开发,是一个开放的程序技术标准,可以在各种设备上运行,包括在商品硬件上可用的 CPU 和 GPU,比如我用于这个配方的机器。自 2009 年以来,OpenCL 一直由 Khronos 计算工作组维护。许多硬件厂商,包括我比较喜欢的厂商,都有 OpenCL 的实现。

OpenCL 是一种类似 C 的语言(实际上有多种 C 语言方言或版本),其功能称为 内核。内核可以在多个处理元件上并行运行。硬件供应商给出了处理元素的定义。OpenCL 程序是为了可移植性而在运行时编译的。

可移植性是 OpenCL 相对于类似技术(如作为 NVIDIA 产品的 CUDA)的最大优势。另一个优势是能够在中央处理器、图形处理器和其他设备之间共享工作。有人建议使用机器学习来优化分工。

皮奥尼塔斯可以用 PyOpenCL 包编写 OpenCL 程序。PyOpenCL 向 Python 异常添加了额外的功能,例如对象清理和错误转换。许多其他库使用并在某些方面增强了 pyoppencl(请参考 pyoppencl 文档)。

做好准备

使用以下命令安装pyopencl :

$ pip install pyopencl 

我用 PyOpenCL 2015.2.3 测试了代码。更多信息请参考https://wiki.tiker.net/OpenCLHowTo

怎么做...

代码在本书代码包的opencl_demo.ipynb文件中:

  1. 进口情况如下:

    import pyopencl as cl
    from pyopencl import array
    import numpy as np
    
  2. 定义以下函数来接受 NumPy 数组并执行简单的计算:

    def np_se(a, b):
        return (a - b) ** 2
    
  3. 定义以下函数,使用 OpenCL 进行与上一步相同的计算:

    def gpu_se(a, b, platform, device, context, program):
    
  4. 创建一个队列,启用概要分析(仅用于演示)和缓冲区,以便在

        queue = cl.CommandQueue(context,
                                properties=cl.command_queue_properties.
                                PROFILING_ENABLE)
        mem_flags = cl.mem_flags
        a_buf = cl.Buffer(context,
                          mem_flags.READ_ONLY | mem_flags.COPY_HOST_PTR,
                          hostbuf=a)
        b_buf = cl.Buffer(context,
                          mem_flags.READ_ONLY | mem_flags.COPY_HOST_PTR, hostbuf=b)
        error = np.empty_like(a)
        destination_buf = cl.Buffer(context,
                                    mem_flags.WRITE_ONLY,
                                    error.nbytes)
    

    周围洗牌

  5. 执行 OpenCL 程序并分析代码:

        exec_evt = program.mean_squared_error(queue, error.shape, None,
                                              a_buf, b_buf, destination_buf)
        exec_evt.wait()
        elapsed = 1e-9*(exec_evt.profile.end - exec_evt.profile.start)
    
        print("Execution time of OpenCL: %g s" % elapsed)
    
        cl.enqueue_copy(queue,
                        error, destination_buf)
    
        return error
    
  6. 生成随机数据如下:

    np.random.seed(51)
    a = np.random.rand(4096).astype(np.float32)
    b = np.random.rand(4096).astype(np.float32)
    
  7. 访问中央处理器和图形处理器。这部分依赖于硬件,所以您可能需要更改这些行:

    platform = cl.get_platforms()[0]
    device = platform.get_devices()[2]
    context = cl.Context([device])
    
  8. 用 OpenCL 语言定义一个内核:

    program = cl.Program(context, """
        __kernel void mean_squared_error(__global const float *a,
        __global const float *b, __global float *result)
        {
            int gid = get_global_id(0);
            float temp = a[gid] - b[gid];
            result[gid] =  temp * temp;
        }
            """).build()
    
  9. 用 NumPy 和 OpenCL (GPU)计算平方误差,并测量执行时间:

    gpu_error = gpu_se(a, b, platform, device, context, program)
    
    np_error = np_se(a, b)
    print('GPU error', np.mean(gpu_error))
    print('NumPy error', np.mean(np_error))
    %time np_se(a, b)
    

有关最终结果,请参考以下屏幕截图:

How to do it...

另见

十三、附录 a:术语表

本附录是贯穿 Python 数据分析和本书使用的技术概念的简要词汇表。

美国信息交换标准码 ( ASCII )在 2007 年底之前一直是互联网上的主导编码标准,UTF-8 (8 位 Unicode)取而代之。ASCII 仅限于英文字母,不支持其他字母。

方差分析 ( 方差分析)是统计学家罗纳德·费雪发明的一种统计数据分析方法。这种方法使用一个或多个对应分类变量的值来分析方差,从而对连续变量的数据进行划分。方差分析是线性建模的一种形式。

Anaconda 是一个免费的 Python 发行版,用于数据分析和科学计算。它有自己的套餐经理,康达

安斯科姆的四重奏是一个经典的例子,它说明了可视化数据为什么重要。四重奏由四个具有相似统计属性的数据集组成。每个数据集都有一系列的 x 值和从属的 y 值。

单词包模型:一种简化的文本模型,其中文本由一个单词包(一个集合,其中某个可以出现多次)来表示。在此表示中,单词的顺序被忽略。通常,字数或某些单词的存在被用作该模型中的特征。

金融中的贝塔系数是一个线性回归模型的 T2 斜率,这个线性回归模型包括资产收益和一个基准的收益,例如标准普尔 500 指数。

缓存包括将结果(通常来自函数调用)存储在内存或磁盘上。如果操作正确,缓存有助于减少函数调用的次数。一般来说,出于空间原因,我们希望保持缓存较小。

一个是一个完整的子图。这相当于拉帮结派的一般概念,其中每个人都认识其他所有的人。

聚类旨在将数据划分为称为聚类的组。聚类是无监督的,因为训练数据没有标记。一些聚类算法需要猜测聚类的数量,而其他算法则不需要。

科恩的 kappa 衡量目标和预测类别之间的一致性(在分类的背景下)——类似于准确性,但它也考虑了获得预测的随机机会。Kappa 在负值和 1 之间变化。

一个完全图是一个图,其中每对节点通过一个唯一的连接连接。

混淆矩阵是一个通常用来总结分类结果的表格。表的两个维度是预测类和目标类。

列联表:包含两个分类变量所有组合的计数的表。

余弦相似度是一种常用的距离度量,用来衡量两个文档的相似度。对于这个度量,我们需要计算两个特征向量的内积。向量的余弦相似性对应于向量之间角度的余弦,因此得名。

互相关使用滑动内积测量两个信号之间的相关性。我们可以使用互相关来测量两个信号之间的时间延迟。

数据科学工具箱 ( DST )是基于 Ubuntu 使用 Python 和 r 进行数据分析的虚拟环境, DST 既然是虚拟环境,我们可以安装在各种操作系统上。

离散余弦变换 ( 离散余弦变换)是一种类似于傅里叶变换的变换,但它试图仅通过余弦项的和来表示信号。

有效市场假说(【EMH】)规定,平均而言,你不能通过挑选更好的股票或把握市场时机来“击败市场”。根据 EMH 的说法,关于市场的所有信息都可以以这样或那样的形式立即提供给每个市场参与者,并立即反映在资产价格中。

特征值是方程 Ax = ax 的标量解,其中 A 是二维矩阵, x 是一维向量。

特征向量是对应于特征值的向量。

指数平滑是低通滤波器,旨在去除噪声。

人脸检测试图找到图像中代表人脸的(矩形)区域。

快速傅立叶变换 ( 快速傅立叶变换):一种计算傅立叶变换的快速算法。FFT 是 O(N log N) ,这是对旧算法的巨大改进。

滤波是一种信号处理技术,涉及部分信号的去除或抑制。存在许多滤波器类型,包括中值滤波器和维纳滤波器。

傅里叶分析是基于傅里叶级数T5【以数学家约瑟夫·傅里叶命名。傅立叶级数是一种将函数表示为正弦和余弦项的无限级数的数学方法。所讨论的函数可以是实数或复数。

遗传算法基于生物进化论。这种类型的算法对于搜索和优化很有用。

图形处理器 ( 图形处理器单元)是用于高效显示图形的专用电路。最近,图形处理器被用来执行大规模并行计算(例如,训练神经网络)。

Hadoop 分布式文件系统 ( HDFS )是 Hadoop 大数据框架的存储组件。HDFS 是一个分布式文件系统,它在多个系统上传播数据,并受到谷歌文件系统的启发,谷歌将其用于搜索引擎。

蜂巢图是一种用于绘制网络图的可视化技术。在蜂房图中,我们把边画成曲线。我们根据一些属性对节点进行分组,并在径向轴上显示它们。

影响图考虑了各个数据点的残差、影响和杠杆,类似于气泡图。残差的大小绘制在纵轴上,可以指示数据点是异常值。

折刀是一种确定性的算法,用于估计置信区间。它属于重采样算法家族。通常,我们通过删除一个值(我们也可以删除两个或更多的值)在折刀算法下生成新的数据集。

JSON ( JavaScript 对象符号)是一种数据格式。在这种格式中,数据是用 JavaScript 符号写下来的。JSON 比其他数据格式更简洁,比如 XML。

K 折叠交叉验证是一种涉及 K(一个小整数)随机数据分区的交叉验证形式,称为折叠。在 k 次迭代中,每个折叠使用一次进行验证,其余的数据用于训练。迭代的结果可以在最后合并。

线性判别分析 ( LDA )是一种寻找特征线性组合的算法,以便在类别之间区分。它可以通过投影到低维子空间来用于分类或降维。

学习曲线:一种将学习算法的行为可视化的方法。它是一系列训练数据大小的训练和测试分数图。

对数图(或对数图)是使用对数刻度的图。当数据变化很大时,这种类型的图很有用,因为它们显示数量级。

逻辑回归是一种分类算法。这种算法可以用来预测一个类或事件发生的概率。逻辑回归基于逻辑函数,其的输出值在 0 到 1 的范围内,就像概率一样。因此,逻辑函数可用于将任意值转换为概率。

隆布-斯卡格尔周期图是一种对数据进行正弦拟合的频谱估计方法,常用于采样不均匀的数据。这种方法以尼古拉斯·隆布和杰弗里·斯卡格尔的名字命名。

马修斯相关系数 ( MCC )或φ系数是 Brian Matthews 在 1975 年发明的二进制分类的评价指标。 MCC 是目标和预测的相关系数,在-1 和 1 之间变化(最佳一致性)。

内存泄漏是计算机程序中常见的问题,我们可以通过执行内存分析来发现。当我们不释放不需要的内存时,就会发生泄漏。

摩尔定律是现代计算机芯片中晶体管数量每两年翻一番的观察。自 1970 年左右摩尔定律形成以来,这一趋势一直在持续。还有第二个摩尔定律,也叫洛克定律。该定律指出,集成电路的研发和制造成本呈指数级增长。

命名实体识别(【NER】)试图检测文本中的人名、组织名、地点名和其他名称。一些 NER 系统几乎和人类一样好,但这不是一件容易的事情。命名实体通常以大写字母开头,例如 Ivan。因此,在应用 NER 时,我们不应该改变用词的情况。

对象关系映射 ( ORM ):一种用于数据库模式和面向对象编程语言之间转换的软件架构模式。

开放计算语言 ( OpenCL )最初由苹果公司开发,是程序的开放技术标准,可以在各种设备上运行,包括在商品硬件上可用的 CPU 和 GPU。

OpenCV ( 开源计算机视觉)是一个计算机视觉库,创建于 2000 年,目前由 Itseez 维护。OpenCV 是用 C++编写的,但它也有到 Python 和其他编程语言的绑定。

观点挖掘情感分析是一个以高效发现和评价文本中的观点和情感为目标的研究领域。

主成分分析 ( PCA )是卡尔·皮尔逊在 1901 年发明的,是一种将数据转换成不相关的正交特征的算法,称为主成分。主成分是协方差矩阵的特征向量。

泊松分布以法国数学家泊松的名字命名,他于 1837 年发表了这个分布。泊松分布是一种离散分布,通常与固定时间或空间间隔内的计数相关联。

稳健回归旨在比普通回归更好地处理数据中的异常值。这种回归使用特殊的稳健估计。

散点图:显示笛卡尔坐标系统中两个变量之间关系的二维图。一个变量的值在一个轴上表示,另一个变量由另一个轴表示。我们可以通过这种方式快速可视化相关性。

无共享架构中,计算节点不共享内存或文件。因此,该架构是完全分散的,具有完全独立的节点。显而易见的优点是,我们不依赖于任何一个节点。第一个商业无共享数据库创建于 20 世纪 80 年代。

信号处理是工程和应用数学的一个领域,处理与随时间变化的变量相对应的模拟和数字信号的分析。

结构化查询语言 ( SQL )是一种用于关系数据库查询和操作的专用语言。这包括创建、插入行和删除表。

短时傅立叶变换(STFT):STFT 将时域中的信号分成相等的部分,然后将快速傅立叶变换应用于每个部分。

停词:信息价值低的常用词。停止词通常在分析文本之前被删除。虽然过滤停用词是常见的做法,但停用词没有标准的定义。

斯皮尔曼等级相关性使用等级将两个变量与皮尔逊相关性相关联。等级是按排序顺序排列的值的位置。值相等的项目会获得一个等级,这是它们位置的平均值。例如,如果我们有两个同等价值的项目分配到位置 2 和 3,则两个项目的排名都是 2.5。

光谱聚类是一种聚类技术,可用于分割图像。

星型模式是一种便于报告的数据库模式。星型模式适用于处理网站访问、广告点击或金融交易等事件。事件信息(如温度或购买量等指标)存储在事实表中,事实表链接到小得多的维度表。星型模式是非规范化的,这将完整性检查的责任放在了应用程序代码上。因此,我们应该只以受控的方式写入数据库。

术语 频率-逆文档频率 ( tf-idf )是衡量一个单词在语料库中的重要性的度量。它由术语频率号和逆文档频率号组成。术语“频率”统计单词在文档中出现的次数。逆文档频率计算单词出现的文档数,取该数的倒数。

时间序列:从最早的测量值开始的数据点的有序列表。通常,每个数据点都有一个相关的时间戳。

Violin 图将箱式图和核密度图或直方图结合在一种类型的图中。

winsoring是处理异常值的一种技术,以 Charles Winsor 命名。实际上,Winsorising 以对称的方式将异常值剪切到给定的百分位数。

十四、附录 b:函数参考

本附录是功能的简短参考,并不意味着是详尽的文档,而是在您暂时无法查阅文档时的额外帮助。这些功能由不同库的包来组织。

IPython

下面在所有前端显示一个 Python 对象:

IPython.core.display.display(*objs, **kwargs)

以下内容呈现 HTML 内容:

IPython.display.HTML(TextDisplayObject)

下面显示了连接到函数的交互式小部件。第一个参数应该是一个函数:

IPython.html.widgets.interaction.interact (__interact_f=None, **kwargs)

该函数的以下参数是作为关键字参数传入的小部件缩写,它们构建了一组绑定到__interact_f的交互式小部件,并将该组放在一个容器中:

IPython.html.widgets.interaction.interactive (__interact_f, **kwargs)

Matplotlib

以下方法用于获取或设置轴属性。例如,axis('off')关闭轴线和标签:

matplotlib.pyplot.axis(*v, **kwargs)

以下参数创建了一个新图形:

matplotlib.pyplot.figure(num=None, figsize=None, dpi=None, facecolor=None, edgecolor=None, frameon=True, FigureClass=<class 'matplotlib.figure.Figure'>, **kwargs)

以下参数打开或关闭绘图网格:

matplotlib.pyplot.grid(b=None, which='major', axis='both', **kwargs)

以下参数绘制了直方图:

matplotlib.pyplot.hist(x, bins=10, range=None, normed=False, weights=None, cumulative=False, bottom=None, histtype='bar', align='mid', orientation='vertical', rwidth=None, log=False, color=None, label=None, stacked=False, hold=None, **kwargs)

下面显示了类似数组的数据的图像:

matplotlib.pyplot.imshow(X, cmap=None, norm=None, aspect=None, interpolation=None, alpha=None, vmin=None, vmax=None, origin=None, extent=None, shape=None, filternorm=1, filterrad=4.0, imlim=None, resample=None, url=None, hold=None, **kwargs)

以下显示了可选指定位置(例如plt.legend(loc='best'))的图例:

matplotlib.pyplot.legend(*args, **kwargs)

以下参数创建一个二维图,其中包含单个或多个 xy 对以及相应的可选格式字符串:

matplotlib.pyplot.plot(*args, **kwargs)

下面创建两个数组的散点图:

matplotlib.pyplot.scatter(x, y, s=20, c='b', marker='o', cmap=None, norm=None, vmin=None, vmax=None, alpha=None, linewidths=None, verts=None, hold=None, **kwargs)

以下参数显示了一个图:

matplotlib.pyplot.show(*args, **kw)

给定绘图的行号、列号和索引号,下面的参数创建子绘图。所有这些数字都从一开始。例如,plt.subplot(221)在一个二乘二的网格中创建第一个子图:

matplotlib.pyplot.subplot(*args, **kwargs)

以下论点为情节加上了标题:

matplotlib.pyplot.title(s, *args, **kwargs)

NumPy

下面创建一个 NumPy 数组,其值在指定范围内均匀分布:

numpy.arange([start,] stop[, step,], dtype=None)

以下参数返回将对输入数组进行排序的索引:

numpy.argsort(a, axis=-1, kind='quicksort', order=None)

下面从类似数组的序列(如 Python 列表)创建一个 NumPy 数组:

numpy.array(object, dtype=None, copy=True, order=None, subok=False, ndmin=0)

以下参数计算两个数组的点积:

numpy.dot(a, b, out=None)

以下参数返回身份矩阵:

numpy.eye(N, M=None, k=0, dtype=<type 'float'>)

以下参数从.npy.npz或腌菜加载 NumPy 数组或腌菜对象。内存映射数组存储在文件系统中,不必完全加载到内存中。这对于大型数组尤其有用:

numpy.load(file, mmap_mode=None)

以下参数将文本文件中的数据加载到 NumPy 数组中:

numpy.loadtxt(fname, dtype=<type 'float'>, comments='#', delimiter=None, converters=None, skiprows=0, usecols=None, unpack=False, ndmin=0)

下面计算沿给定轴的算术平均值:

numpy.mean(a, axis=None, dtype=None, out=None, keepdims=False)

以下参数计算给定轴的中间值:

numpy.median(a, axis=None, out=None, overwrite_input=False)

下面创建一个指定形状和数据类型的 NumPy 数组,包含:

numpy.ones(shape, dtype=None, order='C')

下面执行最小二乘多项式拟合:

numpy.polyfit(x, y, deg, rcond=None, full=False, w=None, cov=False)

以下内容更改了 NumPy 数组的形状:

numpy.reshape(a, newshape, order='C')

以下参数将 NumPy 数组保存到 NumPy .npy格式的文件中:

numpy.save(file, arr)

以下参数将 NumPy 数组保存到文本文件中:

numpy.savetxt(fname, X, fmt='%.18e', delimiter=' ', newline='\n', header='', footer='', comments='# ')

以下参数设置打印选项:

numpy.set_printoptions(precision=None, threshold=None, edgeitems=None, linewidth=None, suppress=None, nanstr=None, infstr=None, formatter=None)

以下参数返回沿给定轴的标准偏差:

numpy.std(a, axis=None, dtype=None, out=None, ddof=0, keepdims=False)

下面根据布尔条件从输入数组中选择数组元素:

numpy.where(condition, [x, y])

下面创建一个指定形状和数据类型的 NumPy 数组,包含零:

numpy.zeros(shape, dtype=float, order='C')

Pandas

以下创建固定频率的日期时间索引:

pandas.date_range(start=None, end=None, periods=None, freq='D', tz=None, normalize=False, name=None, closed=None)

以下参数生成各种汇总统计,忽略NaN值:

pandas.DataFrame.describe(self, percentile_width=None, percentiles=None, include=None, exclude=None)

下面从类似数组的对象或字典的字典中创建一个DataFrame对象:

pandas.DataFrame. from_dict(data, orient='columns', dtype=None)

以下参数查找 NaN 和 None 值:

pandas.isnull(obj)

以下参数将DataFrame对象与列或索引上类似数据库的连接合并:

pandas.merge(left, right, how='inner', on=None, left_on=None, right_on=None, left_index=False, right_index=False, sort=False, suffixes=('_x', '_y'), copy=True)

下面从 CSV 文件创建一个DataFrame对象:

pandas.read_csv(filepath_or_buffer, sep=',', dialect=None, compression=None, doublequote=True, escapechar=None, quotechar='"', quoting=0, skipinitialspace=False, lineterminator=None, header='infer', index_col=None, names=None, prefix=None, skiprows=None, skipfooter=None, skip_footer=0, na_values=None, na_fvalues=None, true_values=None, false_values=None, delimiter=None, converters=None, dtype=None, usecols=None, engine='c', delim_whitespace=False, as_recarray=False, na_filter=True, compact_ints=False, use_unsigned=False, low_memory=True, buffer_lines=None, warn_bad_lines=True, error_bad_lines=True, keep_default_na=True, thousands=Nment=None, decimal='.', parse_dates=False, keep_date_col=False, dayfirst=False, date_parser=None, memory_map=False, nrows=None, iterator=False, chunksize=None, verbose=False, encoding=None, squeeze=False, mangle_dupe_cols=True, tupleize_cols=False, infer_datetime_format=False)

科学学习

以下参数将seed转换为numpy.random.RandomState实例:

sklearn.utils.check_random_state(seed)

以下对给定的估计器超参数值执行网格搜索:

sklearn.grid_search.GridSearchCV estimator, param_grid, scoring=None, fit_params=None, n_jobs=1, iid=True, refit=True, cv=None, verbose=0, pre_dispatch='2*n_jobs', error_score='raise')

以下参数将数组分成随机训练集和测试集:

sklearn.cross_validation.train_test_split(*arrays, **options)

下面返回准确度分类分数:

sklearn.metrics.accuracy_score(y_true, y_pred, normalize=True, sample_weight=None)

黑桃

以下计算数据的相对最大值:

scipy.signal.argrelmax(data, axis=0, order=1, mode='clip')

以下参数计算数据集的峰度:

scipy.stats.kurtosis(a, axis=0, fisher=True, bias=True)

下面对数组应用中值滤波:

scipy.signal.medfilt(volume, kernel_size=None)

以下参数计算数据集的偏斜度:

scipy.stats.skew(a, axis=0, bias=True)

海鸟

以下参数绘制了观测值的单变量分布:

seaborn.distplot(a, bins=None, hist=True, kde=True, rug=False, fit=None, hist_kws=None, kde_kws=None, rug_kws=None, fit_kws=None, color=None, vertical=False, norm_hist=False, axlabel=None, label=None, ax=None)

以下参数将表格数据绘制为颜色编码矩阵:

seaborn.heatmap(data, vmin=None, vmax=None, cmap=None, center=None, robust=False, annot=False, fmt='.2g', annot_kws=None, linewidths=0, linecolor='white', cbar=True, cbar_kws=None, cbar_ax=None, square=False, ax=None, xticklabels=True, yticklabels=True, mask=None, **kwargs)

以下参数绘制数据和相应的线性回归模型拟合:

seaborn.regplot(x, y, data=None, x_estimator=None, x_bins=None, x_ci='ci', scatter=True, fit_reg=True, ci=95, n_boot=1000, units=None, order=1, logistic=False, lowess=False, robust=False, logx=False, x_partial=None, y_partial=None, truncate=False, dropna=True, x_jitter=None, y_jitter=None, label=None, color=None, marker='o', scatter_kws=None, line_kws=None, ax=None)

以下参数将所有 matplotlib RC 参数恢复为默认设置:

seaborn.reset_defaults()

以下参数将所有 matplotlib RC 参数恢复为原始设置:

seaborn.reset_orig()

以下参数绘制了线性回归的残差:

seaborn.residplot(x, y, data=None, lowess=False, x_partial=None, y_partial=None, order=1, robust=False, dropna=True, label=None, color=None, scatter_kws=None, line_kws=None, ax=None)

以下参数设置美学参数:

seaborn.set(context='notebook', style='darkgrid', palette='deep', font='sans-serif', font_scale=1, color_codes=False, rc=None)

状态模型

以下参数从互联网下载并返回 R 数据集:

statsmodels.api.datasets.get_rdataset(dataname, package='datasets', cache=False)

以下参数绘制了一个 Q-Q 图:

statsmodels.api.qqplot(data, dist, distargs=(), a=0, loc=0, scale=1, fit=False, line=None, ax=None)

以下参数为一个或多个拟合线性模型创建了方差分析表:

statsmodels.stats.anova.anova_lm()

十五、附录 c:在线资源

以下是资源的简短列表,包括演示文稿、文档链接、免费提供的 IPython 笔记本和数据。

IPython 笔记本和开放数据

关于 IPython 笔记本和开放数据的更多信息,可以参考以下内容:

数学和统计学

陈述

十六、附录 d:命令行和其他工具的提示和技巧

在本书中,我们使用了各种工具,例如 IPython 笔记本和 Unix shell 命令。我们有一个简短的提示列表,这并不意味着是详尽的。对于使用数据库,我推荐在 https://www.dbvis.com/ T2(2016 年 1 月检索)可用的数据库可视化软件。它支持所有主要的数据库产品和操作系统。另外,我喜欢在桌面环境中使用文本扩展器。

IPython 笔记本电脑

我解释了笔记本电脑的最低工作流程。此外,我制作了简单的 IPython 小部件,本书通篇都在使用这些小部件,因此我将在这里描述它们。要运行 IPython 笔记本代码,请执行以下步骤:

  1. 用图形用户界面或以下命令启动 IPython 笔记本:

    $ jupyter notebook
    
    
  2. 逐个单元格或一次运行代码。

我制作了一个小部件来设置 matplotlib 的一些属性。设置存储在当前文件夹的dautil.json文件中。这些文件也应该是代码包的一部分。

另一个 IPython 小部件帮助设置子场景。它负责设置标题、图例和标签。我认为这些字符串是配置,因此也将它们存储在dautil.json文件中。

命令行工具

其中一些工具有图形用户界面的替代品,但并不总是被提及。在我看来,学习使用命令行工具是一个好主意,即使你后来决定你更喜欢图形用户界面选项。Linux 是许多支持命令行界面的流行操作系统之一。你可以在【http://tldp.org/】(2016 年 1 月检索)找到关于 Linux 工具的好文档。网站上的大多数信息都是通用的,在其他操作系统上也很有用,比如操作系统 x

在命令行界面世界中,导航通常很麻烦。我发现巴什马克是一个很好的工具来帮助你。你可以在https://github.com/huyng/bashmarks找到巴什马克(2016 年 1 月检索)。安装 bashmarks 的步骤如下:

  1. 在终端中键入以下内容:

    $ git clone git://github.com/huyng/bashmarks.git
    
    
  2. 现在,在终端中输入这个:

    $ cd bashmarks
    
    
  3. 接下来,键入以下内容:

    $ make install
    
    
  4. 来源于配置文件或当前会话:

    $ source ~/.local/bin/bashmarks.sh
    
    

下表列出了 bashmarks 命令:

|

命令

|

描述

|
| --- | --- |
| s <bookmark_name> | 这将把当前目录保存为bookmark_name |
| g <bookmark_name> | 这将转到与bookmark_name关联的目录 |
| p <bookmark_name> | 这会打印与bookmark_name关联的目录 |
| d <bookmark_name> | 这会删除书签 |
| l | 这将列出所有可用的书签 |

别名命令

alias命令允许为长命令定义一个短助记符。例如,当我们键入ipnb时,我们可以定义以下别名来启动 IPython 服务器:

$ alias ipnb='ipython notebook'

我们只能为当前会话定义别名,但通常我们在主目录中找到的.bashrc启动文件(文件名中的点表示是隐藏文件)中定义别名。如果您发现自己有许多别名,创建一个包含所有别名的文件可能会很有用。然后,您可以从.bashrc获取该文件。

命令行历史

命令行历史是一种最小化击键次数的机制。大家可以在http://www . tldp . org/LDP/GNU-Linux-Tools-Summary/html/x 1712 . htm(2016 年 1 月检索)上了解更多。

要简单地执行最后一次运行命令,请再次键入以下内容:

$ !!

根据您所处的 shell 模式(viemacs),您可能更喜欢其他方式来浏览历史。键盘上的向上和向下箭头也可以让你浏览历史。

一个常见的用例是搜索我们过去执行的一个长命令,然后再次运行它。我们可以搜索历史,如下所示:

$ history|grep <search for something>

当然,您可以使用别名机制或桌面文本扩展器来缩短这一时间。搜索给出了一个命令列表,其中的数字按时间顺序排列。例如,您可以执行编号为328的命令,如下所示:

$ !328

例如,如果您希望执行最后一个以python开始的命令,请键入以下内容:

$ !python

可重复的会话

第 1 章为可重现数据分析奠定基础阐述了可重现分析的价值。在这种情况下,我们有script命令,这是一种捕获命令和会话输出的方式。

码头工人提示

Docker 是一项伟大的技术,但我们必须小心不要让我们的图像太大,并在可能的情况下删除图像文件。https://gist.github.com/michaelneale/1366325a7737c4cb80b0docker-clean脚本(2016 年 1 月检索)有助于回收空间。

我发现有一个安装脚本很有用,它只是一个普通的 shell 脚本,我把它添加到Dockerfile中如下:

ADD install.sh /root/install.sh

Python 创建__pycache__目录是为了优化(我们可以通过各种方式禁用这个选项)。这些不是严格需要的,可以很容易地移除,如下所示:

find /opt/conda -name \__pycache__ -depth -exec rm -rf {} \;

Anaconda 在它的pkgs目录下放了很多文件,我们可以删除如下:

rm -r /opt/conda/pkgs/*

有些人建议删除测试代码;然而,在某些罕见的情况下,非测试代码依赖于测试代码。同样,有测试代码以防万一也是有用的。

使用 Docker 时,需要注意一些问题。例如,我们必须如下设置PATH环境变量:

export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PATH}

对于 Python 脚本,我们还需要设置如下语言设置:

ENV LANG=C.UTF-8

一般建议用pipconda安装软件时指定软件包版本,比如这样:

$ conda install scipy=0.15.0
$ pip install scipy==0.15.0

使用conda安装时,也建议一次安装多个软件包,以免安装多个版本的公共依赖项:

$ conda install scipy=0.15.0 curl=7.26.0

我的主 Docker 存储库的 Docker 设置由一个Dockerfile脚本和一个安装脚本(install.sh)组成。Dockerfile的内容如下:

FROM continuumio/miniconda3

ADD install.sh /root/install.sh
RUN sh -x /root/install.sh

ENV LANG=C.UTF-8

我用–x开关执行安装脚本,这给出了更详细的输出。

install.sh的内容如下:

export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${PATH}
apt-get install -y libgfortran3
conda config --set always_yes True
conda install beautiful-soup bokeh=0.9.1 execnet=1.3.0 \ fastcache=1.0.2 \
    joblib=0.8.4 jsonschema ipython=3.2.1 lxml mpmath=0.19 \
    networkx=1.9.1 nltk=3.0.2 numba=0.22.1 numexpr=2.3.1 \
    pandas=0.16.2 pyzmq scipy=0.16.0 seaborn=0.6.0 \
    sqlalchemy=0.9.9 statsmodels=0.6.1 terminado=0.5 tornado 
conda install matplotlib=1.5.0 numpy=1.10.1 scikit-learn=0.17
pip install dautil==0.0.1a29
pip install hiveplot==0.1.7.4
pip install landslide==1.1.3
pip install LiveStats==1.0
pip install mpld3==0.2
pip install pep8==1.6.2
pip install requests-cache==0.4.10
pip install tabulate==0.7.5

find /opt/conda -name \__pycache__ -depth -exec rm -rf {} \;
rm -r /opt/conda/pkgs/*

mkdir -p /.cache/dautil/log
mkdir -p /.local/share/dautil
posted @ 2025-10-23 15:21  绝不原创的飞龙  阅读(9)  评论(0)    收藏  举报