Python-数据分析教程-全-

Python 数据分析教程(全)

原文:Python Data Analysis

协议:CC BY-NC-SA 4.0

零、前言

数据分析在自然科学、生物医学和社会科学领域有着丰富的历史。由于围绕数据科学的大肆宣传,数据分析最近在几乎每个行业领域都变得流行起来。数据分析和数据科学试图从数据中提取信息。为此,我们使用了统计学、机器学习、信号处理、自然语言处理和计算机科学的技术。

在本书的第一章中可以找到一个思维导图可视化 Python 软件,它可以用于数据分析。首先值得注意的是,Python 生态系统非常成熟、多样、丰富。它包括著名的软件包,如 NumPy、SciPy 和 matplotlib。这不应该令人惊讶,因为 Python 从 1989 年就出现了。Python 易于学习和使用,比其他编程语言不那么冗长,可读性很强。即使你不懂 Python,你也可以在几天内学会基础知识,尤其是如果你有另一种编程语言的经验。要享受这本书,你不需要更多的基础知识。有很多教授 Python 的书籍、课程和在线教程。

这本书涵盖了什么

第 1 章python 库入门,给出安装 Python 和基础 Python 数据分析库的说明。我们使用 NumPy 创建了一个小应用程序,并用 matplotlib 绘制了一些基本的图。

第二章NumPy 数组,为我们介绍 NumPy 的基础知识和数组。到本章结束时,我们将对 NumPy 数组和相关函数有基本的了解。

第三章Pandas 入门,向我们介绍 Pandas 的基本功能、数据结构和操作。

第四章统计与线性代数,快速概述线性代数与统计函数。

第 5 章检索、处理和存储数据,说明如何获取各种格式的数据,以及如何清理原始数据并存储。

第 6 章数据可视化,概述了如何使用 matplotlib 和 pandas 绘图功能绘制数据。

第 7 章信号处理和时间序列,包含使用太阳黑子周期数据的时间序列和信号处理示例。示例使用了 NumPy/SciPy 以及 statsmodels。

第 8 章使用数据库,提供各种数据库(关系数据库和 NoSQL 数据库)和相关应用编程接口的信息。

第九章分析文本数据和社交媒体,分析文本进行情感分析和话题提取。文中还给出了一个网络分析的小例子。

第 10 章预测性分析和机器学习使用 scikit-learn 以天气预测为运行示例,讲解人工智能。其他 API 用于 scikit-learn 未涵盖的算法。

第 11 章Python 生态系统和云计算之外的环境,给出了各种如何集成非 Python 编写的现有代码的例子。此外,还将演示在云中使用 python。

第 12 章性能调优、性能分析和并发给出了以性能分析和 Cythoning 为关键技术来提高性能的提示。还讨论了多核和分布式系统的相关框架。

附录 A关键概念,给出了关键术语及其描述。

附录 B有用功能,提供了库的关键功能列表,可以作为现成的参考。

附录 C在线资源,为读者提供了进一步探究书中所涵盖主题的链接。

这本书你需要什么

本书中的代码示例应该适用于大多数现代操作系统。对于所有章节,Python > 3.5.0 和 pip3 是必需的。可以从https://www.python.org/downloads/下载 Python 3.5.x。在此网页上,您可以找到 Windows 和 Mac OS X 的安装程序,以及 Linux、Unix 和 Mac OS X 的源代码存档。您可以在此网页上找到各种操作系统的 python 安装和使用说明:https://docs.python.org/3/using/index.html.大多数情况下,我们需要以管理员权限运行以下命令来安装本书内容所需的各种 python 库:

$ pip3 install <some library>

以下是用于示例的 python 库列表:

  • numpy
  • 我的天啊
  • Pandas
  • matplotlib
  • 伊普提洪伊普提翁伊普提翁伊普提翁伊普提翁伊普提翁伊普提翁伊普提翁伊普提翁伊普提翁伊普提翁伊普提翁
  • Jupyter
  • 笔记本
  • 读行
  • scikit-learn
  • rp2
  • 什么时候
  • statsmodels
  • feed parser(feed parser)
  • 美丽的组合 4
  • xml 格式
  • numexpr
  • 桌子
  • openpyxl
  • xls xwritter
  • xlrd . xlrd . xlrd . xlrd . xlrd . xlrd . xlrd . xlrd . xlrd
  • 矮种马
  • 资料组
  • 皮蒙戈
  • redis
  • python 3 memcache(python 3 内存高速缓存)
  • 卡珊德拉-司机
  • sqllcemy(SQL 语法)
  • 我是 nltk
  • 网络 x
  • 剧院
  • 鼻部 _ 参数化
  • pydot2
  • deap!deap
  • JPipes1
  • gprof 2 点
  • 线条分析器
  • 西顿
  • 细胞 olz
  • joblib
  • 瓶颈
  • 水壶
  • mpi4py

除了 python 库,我们还需要以下软件:

  • 重定向服务器
  • 卡桑德拉
  • Java 8
  • Graphviz(图形 viz)
  • 八度音阶
  • 稀有
  • 大喝
  • 断续器
  • 促进
  • gfortran 先生
  • 平均弹着点

通常,可用的最新版本应该适用于上述库和软件。

列出的一些软件仅用于一个例子;因此,在安装软件之前,请首先检查该示例是否与您相关。

要卸载 pip 安装的 Python 包,请使用以下命令:

   $ pip3 uninstall <some library>

这本书是给谁的

这本书是给有 Python 和数学基础知识,想学习如何使用 Python 库分析数据的人看的。我们试图让事情变得简单,但不可能非常详细地涵盖所有主题。使用汗学院和 Coursera 等在线资源更新您的数学知识可能会对您有用。

我会推荐帕克特出版社的以下书籍供进一步阅读:

  • 用 Python 构建机器学习系统,威利·里歇特和路易斯·佩德罗·科埃略(2013)
  • 学习 Cython 编程,Philip Herron (2013)
  • 学习 NumPy 数组,伊万·伊德里斯(2014)
  • 学习 scikit-learn:Python 中的机器学习,劳尔·加雷塔和吉列尔莫·蒙切奇(2013)
  • 数值和科学计算学习科学,弗朗西斯科·布兰科-席尔瓦(2013)
  • 面向 Python 开发人员的 Matplotlib,Sandro Tosi (2009)
  • 《新手指南-第二版》,伊万·伊德里斯(2013)
  • 伊万·伊德里斯(2012 年)
  • Python 并行编程,扬·帕拉奇(2014)
  • 《Python 数据可视化食谱》,伊戈尔·米洛瓦诺维奇(2013 年)
  • 金融 Python,闫宇星(2014)
  • 用 NLTK 2.0 食谱处理 Python 文本,雅各布·帕金斯(2010)

惯例

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

文本中的码字、数据库表名、文件夹名、文件名、文件扩展名、路径名、伪 URL、用户输入和 Twitter 句柄如下所示:“如果您的当前用户在您的系统上没有足够的权限,可能有必要在此命令前添加sudo

代码块设置如下:

def pythonsum(n): 
   a = list(range(n)) 
   b = list(range(n)) 
   c = [] 

   for i in range(len(a)): 
       a[i] = i ** 2 
       b[i] = i ** 3 
       c.append(a[i] + b[i]) 

   return c

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

$ pip3 install numpy scipy pandas matplotlib jupyter notebook

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

型式

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

读者反馈

我们随时欢迎读者的反馈。让我们知道你对这本书的看法——你喜欢或不喜欢什么。读者反馈对我们来说很重要,因为它有助于我们开发出你真正能从中获益的标题。要向我们发送一般反馈,只需给 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

这本书的代码包也托管在 GitHubhttps://GitHub . com/PacktPublishing/Python-Data-Analysis-第二版。我们还有来自丰富的图书和视频目录的其他代码包,可在https://github.com/PacktPublishing/获得。看看他们!

下载本书的彩色图片

我们还为您提供了一个 PDF 文件,其中包含本书中使用的截图/图表的彩色图像。彩色图像将帮助您更好地理解输出中的变化。您可以从https://www . packtpub . com/sites/default/files/downloads/PitOnDaanalysisSessional _ color images . pdf下载此文件。

勘误表

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

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

盗版

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

请联系我们在 copyright@packtpub.com 的链接到可疑的盗版材料。

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

问题

如果你对这本书的任何方面有问题,你可以联系我们在 questions@packtpub.com,我们将尽最大努力解决这个问题。

一、Python 库入门

欢迎光临!我们开始吧。Python 已经成为数据分析和数据科学事实上的标准语言和平台之一。您将很快看到的思维导图描述了数据分析师和数据科学家使用的 Python 生态系统中的一些可用库。NumPy、SciPy、Pandas 和 Matplotlib 库奠定了 Python 数据分析的基础,现在是 SciPy Stack 1.0(http://www.scipy.org/stackspec.html)的一部分。我们将学习如何安装 SciPy Stack 1.0 和 Jupyter Notebook,并编写一些简单的数据分析代码作为热身练习。

以下是 Python 生态系统中可供数据分析师和数据科学家使用的库:

  • NumPy :这是一个通用库,提供数值数组,以及高效操作数组的函数。
  • SciPy :这是一个提供科学和工程相关功能的科学计算库。SciPy 补充并略微重叠 NumPy。NumPy 和 SciPy 在历史上共享他们的代码库,但后来分开了。
  • Pandas:这是一个数据操作库,提供数据结构和操作来操作表格和时间序列数据。
  • Matplotlib :这是一个 2D 绘图库,为生成绘图、图表和图形提供支持。Matplotlib 由 SciPy 使用,支持 NumPy。
  • IPython :这为 Python 提供了强大的交互外壳,为 Jupyter 提供了内核,并支持交互数据可视化。我们将在本章稍后介绍 IPython 外壳。
  • Jupyter Notebook :这提供了一个基于网络的交互式外壳,用于通过实时代码和可视化来创建和共享文档。Jupyter Notebook 通过 IPython 提供的内核支持多个版本的 Python。我们将在本章稍后介绍 Jupyter 笔记本。

其他所需软件的安装说明将在适当的时候在整本书中给出。在这一章的最后,你会发现一些提示,告诉你如果你陷入困境或者不确定解决问题的最佳方法,如何在网上找到更多的信息:

Getting Started with Python Libraries

在本章中,我们将涵盖以下主题:

  • 安装 Python 3
  • 使用 IPython 作为外壳
  • 阅读手册页
  • Jupyter 笔记型电脑
  • NumPy 数组
  • 简单的应用程序
  • 在哪里可以找到帮助和参考资料
  • 列出 Python 库中的模块
  • 使用 matplotlib 可视化数据

安装 Python 3

本书使用的软件基于 Python 3,所以需要安装 Python 3。在一些操作系统上,已经安装了 Python 3。Python 有许多实现,包括商业实现和发行版。在本书中,我们将重点介绍标准 Python 实现,它保证与 NumPy 兼容。

可以从https://www.python.org/downloads/下载 Python 3.5.x。在这个网页上,你可以找到 Windows 和 Mac OS X 的安装程序,以及 Linux、Unix 和 Mac OS X 的源文件库。你可以在https://docs.python.org/3/using/index.html找到各种操作系统安装和使用 Python 的说明。

我们将在本章安装的软件有 Windows 的二进制安装程序、各种 Linux 发行版和 Mac OS X。如果您愿意,也有源代码发行版。您需要在系统上安装 Python 3.5.x 或更高版本。Python 2.7 的日落日期从 2015 年移动到 2020 年,因此 Python 2.7 将被支持和维护到 2020 年。出于这些原因,我们为 Python 3 更新了这本书。

安装数据分析库

我们将学习如何在 Windows、Linux 和 Mac OS X 上安装和设置 NumPy、SciPy、Pandas、Matplotlib、IPython 和 Jupyter Notebook,让我们详细了解一下这个过程。我们将使用pip3来安装库。从 3.4 版本开始,默认情况下,Python 安装中包含了pip3

在 Linux 或者 Mac OS X 上

要安装基础库,请运行以下命令行指令:

$ pip3 install numpy scipy pandas matplotlib jupyter notebook 

如果您的当前用户在您的系统上没有足够的权限,可能有必要将sudo添加到该命令之前。

在窗户上

在撰写本书时,我们在 Windows 10 虚拟机上安装了以下软件作为先决条件:

http://www.lfd.uci.edu/~gohlke/pythonlibs/为您的 Windows 平台下载并安装适当的预建 NumPy 和 Scipy 二进制文件:

  • 我们下载了numpy-1 . 12 . 0+mkl-cp36-cp36m-win _ amd 64 . whlscipy-0 . 18 . 1-cp36-cp36m-win _ amd 64 . whl
  • 下载后,我们执行pip3 install Downloads\numpy-1.12.0+mkl-cp36-cp36m-win_amd64.whlpip3 install Downloads\scipy-0.18.1-cp36-cp36m-win_amd64.whl命令

安装这些先决条件后,要安装其余的基础库,请运行以下命令行指令:

$ pip3 install pandas matplotlib jupyter

型式

安装 Jupyter 使用这些命令,安装所有需要的软件包,比如笔记本和 IPython。

使用 IPython 作为外壳

数据分析师、数据科学家和工程师习惯于实验。IPython 是由科学家在实验的基础上创造出来的。IPython 提供的交互环境与 Matlab、Mathematica 和 Maple 提供的交互计算环境相当。

以下是 IPython 外壳的功能列表:

  • 选项卡完成,这有助于您找到命令
  • 历史机制
  • 内嵌编辑
  • 能够用%run调用外部 Python 脚本
  • 访问系统命令
  • 对 Python 调试器和分析器的访问

以下列表描述了如何使用 IPython 外壳:

  • Starting a session: To start a session with IPython,enter the following instruction on the command line:

    $ ipython3
    Python 3.5.2 (default, Sep 28 2016, 18:08:09) 
    Type "copyright", "credits" or "license" for more information.
     IPython 5.1.0 -- An enhanced Interactive Python.
    ?         -> Introduction and overview of IPython's features.
    %quickref -> Quick reference.
    help      -> Python's own help system.
    object?   -> Details about 'object', use 'object??' for extra 
                         details.
    In [1]: quit()
    
    

    型式

    quit()功能或 Ctrl + D 退出 IPython 外壳。

  • Saving a session: We might want to be able to go back to our experiments. In IPython, it is easy to save a session for later use with the following command:

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

    日志记录可以按如下方式关闭:

    In [9]: %logoff
    Switching logging OFF
    
    
  • Executing a system shell command: Execute a system shell command in the default IPython profile by prefixing the command with the ! symbol. For instance, the following input will get the current date:

    In [1]: !date
    
    

    事实上,任何以!为前缀的行都会被发送到系统外壳。我们还可以存储命令输出,如下所示:

    In [2]: thedate = !date
    In [3]: thedate
    
    
  • Displaying history: We can show the history of our commands with the %hist command. For example:

    In [1]: a = 2 + 2
    In [2]: a
    Out[2]: 4
    In [3]: %hist
    a = 2 + 2
    a
    %hist
    
    

    这是命令行界面 ( CLI )环境中的常见功能。我们也可以通过-g开关搜索历史,如下所示:

    In [5]: %hist -g a = 2
     1: a = 2 + 2
    
    

我们看到了一些所谓的神奇功能在发挥作用。这些功能以%字符开始。如果魔法功能单独在一行上使用,%前缀是可选的。

阅读手册页

在 IPython 中导入库时,我们可以用help命令打开库函数的手动页面。不必知道函数的名称。我们可以键入几个字符,然后让标签完成它的工作。例如,让我们浏览arange()功能的可用信息。

我们可以通过以下两种方式浏览可用信息:

  • Calling the help function: Type in help( followed by a few characters of the function and press the Tab key. A list of functions will appear. Select the function from the list using the arrow keys and press the Enter key. Close the help function call with )  and press the Enter key.

    Reading manual pages

  • Querying with a question mark: Another option is to append a question mark to the function name. You will then, of course, need to know the function name, but you don't have to type help, for example:

    In [3]: numpy.arange?
    
    

    标签完成依赖于readline,所以你需要确定它已经安装。通过输入以下命令,可以安装pip:

    $ pip3 install readline
    
    

    问号提供了来自文档字符串的信息。

Jupyter 笔记本电脑

Jupyter Notebook,以前被称为 IPython Notebooks,它提供了一个工具,可以创建和共享带有特殊格式的文本、图表和 Python 代码的网页。请通过以下链接查看这些笔记本系列:

通常,笔记本被用作教育工具,或者演示 Python 软件。我们可以从普通 Python 代码或特殊笔记本格式导入或导出笔记本。笔记本可以在本地运行,或者我们可以通过运行专用的笔记本服务器使它们在线可用。某些云计算解决方案,如 Wakari 和 PiCloud,允许您在云中运行笔记本电脑。云计算是第 11 章Python 生态系统外的环境和云计算的主题之一。

要启动与 Jupyter 笔记本的会话,请在命令行中输入以下指令:

$ jupyter-notebook

这将启动笔记本服务器,并打开一个网页,显示将从中执行命令的文件夹的内容。然后可以选择新建 | Python 3 在 Python 3 中开始一个新的笔记本。

也可以打开本书代码包中提供的ch-01.ipynbch-01笔记本文件包含我们稍后将描述的简单应用程序的代码。

NumPy 数组

完成 NumPy 的安装后,是时候看一下 NumPy 数组了。在数值运算方面,NumPy 数组比 Python 列表更高效。事实上,NumPy 数组是经过大量优化的专用对象。NumPy 代码比同等的 Python 代码需要更少的显式循环。这是基于向量化的。

如果我们回到高中数学,那么我们应该记住标量和向量的概念。例如,数字 2 是一个标量。当我们将 2 加 2 时,我们执行的是标量加法。我们可以用一组标量组成一个向量。在 Python 编程术语中,我们将有一个一维数组。当然,这个概念可以扩展到更高的维度。在两个数组上执行操作,如加法,可以简化为一组标量操作。在直接 Python 中,我们将这样做,循环遍历第一个数组中的每个元素,并将其添加到第二个数组中的相应元素中。然而,这比数学中的方式更冗长。在数学中,我们把两个向量的相加当作一次运算。NumPy 数组也是这样做的,并且使用低级 C 例程进行了某些优化,使这些基本操作更加高效。我们将在第 2 章NumPy 数组中详细介绍 NumPy 数组。

简单的应用

假设我们要加上两个向量,分别叫做ab向量这个词在这里是数学意义上的,意思是一维数组。我们将在第 4 章统计学和线性代数中学习表示矩阵的专用 NumPy 数组。向量a保存整数0n 的平方;例如,如果n等于3a包含014。向量b保存整数0n 的立方,所以如果n等于3,那么向量b等于01或者8。使用普通的 Python 你会怎么做?在我们想出一个解决方案后,我们将把它与 NumPy 等效方案进行比较。

以下函数使用不带 NumPy 的纯 Python 解决了向量加法问题:

def pythonsum(n): 
   a = list(range(n)) 
   b = list(range(n)) 
   c = [] 

   for i in range(len(a)): 
       a[i] = i ** 2 
       b[i] = i ** 3 
       c.append(a[i] + b[i]) 

   return c 

下面是一个用 NumPy 解决向量加法问题的函数:

def numpysum(n): 
  a = numpy.arange(n) ** 2 
  b = numpy.arange(n) ** 3 
  c = a + b 
  return c 

注意numpysum()不需要for循环。我们还使用了 NumPy 中的arange()函数,该函数为我们创建了一个 NumPy 数组,其中包含从0n 的整数。arange()功能已导入;这就是为什么它的前缀是numpy

有趣的部分来了。我们之前提到,在数组操作方面,NumPy 更快。不过,Numpy 要快多少?下面的程序将通过测量numpysum()pythonsum()功能的经过时间(以微秒计)来显示。它还打印向量和的最后两个元素。让我们检查一下是否使用 Python 和 NumPy 得到了相同的答案:

#!/usr/bin/env/python 

import sys 
from datetime import datetime 
import numpy as np 

""" 
This program demonstrates vector addition the Python way. 
Run the following from the command line: 

  python vectorsum.py n 

Here, n is an integer that specifies the size of the vectors. 

The first vector to be added contains the squares of 0 up to n. 
The second vector contains the cubes of 0 up to n. 
The program prints the last 2 elements of the sum and the elapsed  time: 
""" 

def numpysum(n): 
   a = np.arange(n) ** 2 
   b = np.arange(n) ** 3 
   c = a + b 

   return c 

def pythonsum(n): 
   a = list(range(n)) 
   b = list(range(n)) 
   c = [] 

   for i in range(len(a)): 
       a[i] = i ** 2 
       b[i] = i ** 3 
       c.append(a[i] + b[i]) 

   return c 

size = int(sys.argv[1]) 

start = datetime.now() 
c = pythonsum(size) 
delta = datetime.now() - start 
print("The last 2 elements of the sum", c[-2:]) 
print("PythonSum elapsed time in microseconds", delta.microseconds) 

start = datetime.now() 
c = numpysum(size) 
delta = datetime.now() - start 
print("The last 2 elements of the sum", c[-2:]) 
print("NumPySum elapsed time in microseconds", delta.microseconds) 

100020003000向量元素的程序输出如下:

$ python3 vectorsum.py 1000
The last 2 elements of the sum [995007996, 998001000]
PythonSum elapsed time in microseconds 976
The last 2 elements of the sum [995007996 998001000]
NumPySum elapsed time in microseconds 87
$ python3 vectorsum.py 2000
The last 2 elements of the sum [7980015996, 7992002000]
PythonSum elapsed time in microseconds 1623
The last 2 elements of the sum [7980015996 7992002000]
NumPySum elapsed time in microseconds 143
$ python3 vectorsum.py 4000
The last 2 elements of the sum [63920031996, 63968004000]
PythonSum elapsed time in microseconds 3417
The last 2 elements of the sum [63920031996 63968004000]
NumPySum elapsed time in microseconds 237

显然,NumPy 比同等的正常 Python 代码快得多。有一点是肯定的;无论是否使用 NumPy,我们都会得到相同的结果。但是,打印的结果在表示上有所不同。注意numpysum()函数的结果没有逗号。怎么会这样显然,我们处理的不是 Python 列表,而是 NumPy 数组。我们将在第 2 章NumPy 数组中了解更多关于 NumPy 数组的信息。

哪里可以找到帮助和推荐人

下表列出了我们在本章中讨论的 Python 数据分析库的文档网站。

| **包装** | **描述** | | NumPy 和 SciPy | NumPy 和 SciPy 的主要文档网站在[http://docs.scipy.org/doc/](http://docs.scipy.org/doc/)。通过此网页,您可以浏览 NumPy 和 SciPy 用户指南和参考指南,以及几个教程。 | | Pandas | [http://pandas.pydata.org/pandas-docs/stable/](http://pandas.pydata.org/pandas-docs/stable/)。 | | Matplotlib | [http://matplotlib.org/contents.html](http://matplotlib.org/contents.html)。 | | 伊普提洪伊普提翁伊普提翁伊普提翁伊普提翁伊普提翁伊普提翁伊普提翁伊普提翁伊普提翁伊普提翁伊普提翁 | [http://ipython . readthedocs . io/en/stable/](http://ipython.readthedocs.io/en/stable/)。 | | Jupyter 笔记型电脑 | [http://jupyter-notebook.readthedocs.io/en/latest/](http://jupyter-notebook.readthedocs.io/en/latest/) . |

流行的堆栈溢出软件开发论坛有数百个问题,分别标记为 NumPy、SciPy、Pandas、Matplotlib、IPython 和 Jupyter Notebook。要查看它们,请转到http://stackoverflow.com/questions/tagged/<your-tag-word-here>

如果你真的遇到了问题,或者你想随时了解这些库的发展,你可以订阅他们各自的讨论邮件列表。每天的电子邮件数量因列表而异。积极参与这些库开发的开发人员回答了邮件列表中的一些问题。

对于 IRC 用户,在irc://irc.freenode.net上有一个 IRC 频道。这个频道叫做#scipy,但是你也可以问 NumPy 问题,因为 SciPy 用户也有 NumPy 的知识,因为 SciPy 是基于 NumPy 的。SciPy 频道始终至少有 50 名成员。

列出 Python 库中的模块

ch-01.ipynb文件包含查看 NumPy、SciPy、Pandas 和 Matplotlib 库中模块的代码。不要担心理解代码,只是现在尝试运行它。您可以修改这段代码来查看其他库中的模块。

使用 Matplotlib 可视化数据

我们将在后面的章节中学习可视化数据。现在,让我们尝试加载两个样本数据集并构建一个基本图。首先,安装 sklearn 库,我们将使用以下命令从中加载数据:

$ pip3 install scikit-learn 

使用以下命令导入数据集:

from sklearn.datasets import load_iris 
from sklearn.datasets import load_boston 

导入 Matplotlib 绘图模块:

from matplotlib import pyplot as plt 
%matplotlib inline 

加载iris数据集,打印数据集描述,将第 1 列(萼片长度)绘制为x,将第 2 列(萼片宽度)绘制为y:

iris = load_iris() 
print(iris.DESCR) 
data=iris.data 
plt.plot(data[:,0],data[:,1],".") 

生成的图将如下图所示:

Visualizing data using Matplotlib

加载 boston 数据集,打印数据集描述,将第 3 列(非零售业务占比)标为x,第 5 列(一氧化氮浓度)标为y,图上各点标有 + 符号:

boston = load_boston()
print(boston.DESCR)
data=boston.data
plt.plot(data[:,2],data[:,4],"+")

生成的图将如下图所示:

Visualizing data using Matplotlib

总结

在这一章中,我们安装了 NumPy、SciPy、Pandas、Matplotlib、IPython 和 Jupyter Notebook,所有这些都将在本书中使用。我们让一个向量加法程序工作,并了解了 NumPy 如何提供卓越的性能。此外,我们还探索了可用的文档和在线资源。我们执行代码来查找库中的模块,并加载了一些示例数据集来使用 Matplotlib 绘制一些基本图。

在下一章第二章NumPy 数组中,我们将在 NumPy 的兜帽下看一看,探索一些基本概念,包括数组和数据类型。

二、NumPy 数组

现在,我们已经研究了一个利用 SciPy 堆栈中的基础数据分析库的真实例子,是时候了解 NumPy 数组了。本章让您了解 NumPy 数组的基础知识。在本章的最后,您将对 NumPy 数组和相关函数有一个基本的了解。

我们将在本章中讨论的主题如下:

  • NumPy 数组对象
  • 创建多维数组
  • 选择 NumPy 数组元素
  • 数字类型
  • 一维切片和索引
  • 操作数组形状
  • 创建数组视图和副本
  • 花式索引
  • 用位置列表索引
  • 用布尔值索引 NumPy 数组
  • 广播 NumPy 数组

您可能想打开 Jupyter 笔记本中的ch-02.ipynb文件,按照本章中的示例进行操作,或者将它们键入您自己的新笔记本中。

NumPy 数组对象

NumPy 提供了一个名为ndarray的多维数组对象。NumPy 数组是固定大小的类型化数组。Python 列表是异构的,因此列表的元素可以包含任何对象类型,而 NumPy 数组是同构的,只能包含一种类型的对象。一ndarray由两部分组成,如下:

  • 存储在连续内存块中的实际数据
  • 描述实际数据的元数据

由于实际数据存储在连续的内存块中,因此将大数据集加载为ndarray,它会受到足够大的连续内存块的可用性的影响。NumPy 中的大多数数组方法和函数不会影响实际数据,只修改元数据。

我们在前面的章节中已经发现了如何通过应用arange()函数来产生数组。实际上,我们制作了一个一维数组,其中包含一组数字。ndarray可以有多个维度。

NumPy 数组的优势

通常,NumPy 数组是同构的(有一个特定的记录数组类型是异构的),数组中的项目必须是相同的类型。这样做的好处是,如果我们知道数组中的项目属于同一类型,就很容易确定数组所需的存储大小。NumPy 数组可以执行向量化操作,处理一个完整的数组,这与 Python 列表不同,在 Python 列表中,您通常必须循环遍历列表并对每个元素执行操作。NumPy 数组是从 0 开始索引的,就像 Python 中的列表一样。NumPy 利用优化的 C 语言应用编程接口,使数组操作特别快。

我们将再次用arange()子程序进行数组。在本章中,您将看到 Jupyter 笔记本会话的片段,其中 NumPy 已经使用指令import numpy as np导入。以下是获取数组数据类型的方法:

In: a = np.arange(5) 
In: a.dtype 
Out: dtype('int64')

数组a的数据类型是int64(至少在我的电脑上是这样的),但是如果你使用的是 32 位 Python,你可能会得到int32作为输出。在这两种情况下,我们都在处理整数(64 位或 32 位)。除了数组的数据类型之外,知道它的形状也很重要。第 1 章Python 库入门中的例子演示了如何创建向量(实际上是一维 NumPy 数组)。数学中通常使用向量,但大多数时候我们需要更高维的对象。让我们找出几分钟前产生的向量的形状:

In: a 
Out: array([0, 1, 2, 3, 4]) 
In: a.shape 
Out: (5,) 

可以看到,向量有五个分量,取值范围从04。数组的shape属性是一个元组;在这种情况下,1元素的元组,它保存每个维度中的长度。

创建多维数组

现在我们知道了如何创建向量,我们将创建多维 NumPy 数组。生成矩阵后,我们将再次需要显示它,如以下代码片段所示:

  1. 创建多维数组,如下所示:

            In: m = np.array([np.arange(2), np.arange(2)]) 
            In: m 
            Out: 
            array([[0, 1], 
                   [0, 1]]) 
    
    
  2. 我们可以将数组形状显示如下:

            In: m.shape 
            Out: (2, 2) 
    
    

我们用arange()子程序做了一个 2x2 数组。array()函数根据传递给它的对象创建一个数组。对象必须是一个数组,例如 Python 列表。在前面的例子中,我们传递了一个数组列表。对象是array()功能唯一需要的参数。NumPy 函数往往有一堆带有预定义默认选项的可选参数。

选择 NumPy 数组元素

有时,我们会希望选择一个数组的特定组成部分。我们将了解如何做到这一点,但首先,让我们再次制作一个 2x2 矩阵:

In: a = np.array([[1,2],[3,4]]) 
In: a 
Out: 
array([[1, 2], 
       [3, 4]]) 

这次通过给array()函数一个列表来创建矩阵。我们现在将一次选择矩阵中的每一项,如下面的代码片段所示。回想一下,索引号从0开始:

In: a[0,0] 
Out: 1 
In: a[0,1] 
Out: 2 
In: a[1,0] 
Out: 3 
In: a[1,1] 
Out: 4 

如您所见,选择数组元素相当简单。对于数组a,我们只使用符号a[m,n],其中mn是数组中项目的索引。请看下图,以供参考:

Selecting NumPy array elements

数值类型

Python 有integer类型、float类型、complex类型;尽管如此,这还不足以进行科学计算。实际上,我们仍然需要更多精度不同的数据类型,因此需要不同的类型存储大小。因此,NumPy 有更多的数据类型。大多数 NumPy 数学类型以数字结尾。该数字表示与类型相关的位数。下表(改编自 NumPy 用户指南)概述了 NumPy 数字类型:

|

类型

|

描述

|
| --- | --- |
| bool | 布尔(TrueFalse)存储为一位 |
| inti | 平台整数(通常为int32int64) |
| int8 | 字节(-128 到 127) |
| int16 | 整数(-32768 到 32767) |
| int32 | 整数(-2 ** 31 至 2 ** 31 -1) |
| int64 | 整数(-2 ** 63 至 2 ** 63 -1) |
| uint8 | 无符号整数(0 到 255) |
| uint16 | 无符号整数(0 到 65535) |
| uint32 | 无符号整数(0 到 2 ** 32 - 1) |
| uint64 | 无符号整数(0 到 2 ** 64 - 1) |
| float16 | 半精密浮子;符号位、5 位指数和 10 位尾数 |
| float32 | 单精度浮动;符号位、8 位指数和 23 位尾数 |
| float64float | 双精度浮动;符号位、11 位指数和 52 位尾数 |
| complex64 | 复数,由两个 32 位浮点(实部和虚部)表示 |
| complex128complex | 复数,由两个 64 位浮点(实部和虚部)表示 |

对于每种数据类型,都有一个匹配的转换函数:

In: np.float64(42) 
Out: 42.0 
In: np.int8(42.0) 
Out: 42 
In: np.bool(42) 
Out: True 
In: np.bool(0) 
Out: False 
In: np.bool(42.0) 
Out: True 
In: np.float(True) 
Out: 1.0 
In: np.float(False) 
Out: 0.0 

许多函数都有一个数据类型参数,它通常是可选的:

In: np.arange(7, dtype= np.uint16) 
Out: array([0, 1, 2, 3, 4, 5, 6], dtype=uint16)

请注意,不允许将复数转换为整数,这一点很重要。试图做到这一点会引发TypeError:

In: np.int(42.0 + 1.j)Traceback (most recent call last):
<ipython-input-24-5c1cd108488d> in <module>()
----> 1 np.int(42.0 + 1.j)
TypeError: can't convert complex to int

复数到浮点数的转换也是如此。顺便说一下,j分量是一个复数的虚部系数。即便如此,你也可以把一个浮点数转换成复数;例如,complex(1.0)。复数的实数和虚数可以分别用real()imag()函数取出。

数据类型对象

数据类型对象是numpy.dtype类的实例。同样,数组也有数据类型。确切地说,NumPy 数组中的每个元素都有相同的数据类型。数据类型对象可以告诉您数据的大小(以字节为单位)。字节大小由dtype类的itemsize属性给出:

In: a.dtype.itemsize 
Out: 8 

字符代码

包含字符代码是为了向后兼容数字。NumPy 的前身是 NumPy。不建议使用它,但这里提供了代码,因为它会在不同的位置弹出。你应该用dtype对象代替。下表列出了几种不同的数据类型和与之相关的字符代码:

|

类型

|

字符代码

|
| --- | --- |
| integer | i |
| unsigned integer | u |
| single precision float | f |
| double precision float | d |
| bool | b |
| complex | D |
| string | S |
| unicode | U |
| void | V |

看看下面的代码,生成一个单精度浮点数组:

In: arange(7, dtype='f') 
Out: array([ 0.,  1.,  2.,  3.,  4.,  5.,  6.], dtype=float32) 

同样,下面的代码创建了一个复数数组:

In: arange(7, dtype='D') 
Out: array([ 0.+0.j,  1.+0.j,  2.+0.j,  3.+0.j,  4.+0.j,  5.+0.j,   6.+0.j]) 

数据类型构造函数

我们有多种方法来创建数据类型。以浮点数据为例(看看本书代码包中的dtypeconstructors.py):

  • 我们可以使用通用 Python float,如下面几行代码所示:

            In: np.dtype(float) 
            Out: dtype('float64') 
    
    
  • 我们可以用字符代码指定一个单精度浮点数:

            In: np.dtype('f') 
            Out: dtype('float32') 
    
    
  • 我们可以使用双精度浮点数,其字符代码为:

            In: np.dtype('d') 
            Out: dtype('float64') 
    
    
  • We can pass the dtype constructor a two character code. The first character stands for the type; the second character is a number specifying the number of bytes in the type (the numbers 2, 4, and 8 correspond to floats of 16, 32, and 64 bits, respectively):

            In: np.dtype('f8') 
            Out: dtype('float64') 
    
    

    通过应用sctypeDict.keys(),可以找到所有完整数据类型代码的(截断的)列表:

    In: np.sctypeDict.keys() 
    Out: dict_keys(['?', 0, 'byte', 'b', 1, 'ubyte', 'B', 2, 'short', 'h', 3, 'ushort', 'H', 4, 'i', 5, 'uint', 'I', 6, 'intp', 'p', 7, 'uintp', 'P', 8, 'long', 'l', 'L', 'longlong', 'q', 9, 'ulonglong', 'Q', 10, 'half', 'e', 23, 'f', 11, 'double', 'd', 12, 'longdouble', 'g', 13, 'cfloat', 'F', 14, 'cdouble', 'D', 15, 'clongdouble', 'G', 16, 'O', 17, 'S', 18, 'unicode', 'U', 19, 'void', 'V', 20, 'M', 21, 'm', 22, 'bool8', 'Bool', 'b1', 'float16', 'Float16', 'f2', 'float32', 'Float32', 'f4', 'float64', 'Float64', 'f8', 'float128', 'Float128', 'f16', 'complex64', 'Complex32', 'c8', 'complex128', 'Complex64', 'c16', 'complex256', 'Complex128', 'c32', 'object0', 'Object0', 'bytes0', 'Bytes0', 'str0', 'Str0', 'void0', 'Void0', 'datetime64', 'Datetime64', 'M8', 'timedelta64', 'Timedelta64', 'm8', 'int64', 'uint64', 'Int64', 'UInt64', 'i8', 'u8', 'int32', 'uint32', 'Int32', 'UInt32', 'i4', 'u4', 'int16', 'uint16', 'Int16', 'UInt16', 'i2', 'u2', 'int8', 'uint8', 'Int8', 'UInt8', 'i1', 'u1', 'complex_', 'int0', 'uint0', 'single', 'csingle', 'singlecomplex', 'float_', 'intc', 'uintc', 'int_', 'longfloat', 'clongfloat', 'longcomplex', 'bool_', 'unicode_', 'object_', 'bytes_', 'str_', 'string_', 'int', 'float', 'complex', 'bool', 'object', 'str', 'bytes', 'a']) 
    
    

数据类型属性

dtype类有许多有用的属性。例如,我们可以通过dtype的属性获取数据类型的字符代码信息:

In: t = np.dtype('Float64') 
In: t.char 
Out: 'd' 

type 属性对应于数组元素的对象类型:

In: t.type 
Out: numpy.float64 

dtypestr属性给出了数据类型的字符串表示。它以一个表示字符顺序的字符开始,如果合适的话,然后是一个字符代码,后面是一个对应于每个数组项所需字节数的数字。这里的字节序是指字节在 32 或 64 位字中的排列方式。在大端顺序中,最重要的字节首先被存储,由>指示。按照小端顺序,首先存储最低有效字节,由<表示,如以下代码行所示:

In: t.str 
Out: '<f8' 

一维切片和索引

一维 NumPy 数组的切片就像标准 Python 列表的切片一样工作。让我们定义一个包含数字 0、1、2 等直到 8 的数组。我们可以从索引 3 到 7 中选择数组的一部分,它提取数组 3 到 6 的元素:

In: a = np.arange(9) 
In: a[3:7] 
Out: array([3, 4, 5, 6]) 

我们可以从 0 到 7 的索引中选择元素,增量为 2:

In: a[:7:2] 
Out: array([0, 2, 4, 6]) 

就像在 Python 中一样,我们可以使用负索引并反转数组:

In: a[::-1] 
Out: array([8, 7, 6, 5, 4, 3, 2, 1, 0]) 

操纵数组形状

我们已经了解了reshape()功能。另一个重复的任务是展平数组。在这种情况下,展平需要将多维数组转换为一维数组。让我们创建一个数组 b,我们将使用它来练习进一步的示例:

In: b = np.arange(24).reshape(2,3,4) 

In: print(b) 

Out: [[[ 0,  1,  2,  3], 
        [ 4,  5,  6,  7], 
        [ 8,  9, 10, 11]], 

       [[12, 13, 14, 15], 
        [16, 17, 18, 19], 
        [20, 21, 22, 23]]]) 

我们可以使用以下函数来操作数组形状:

  • 拉威尔:我们可以通过ravel()功能来完成,如下所示:

            In: b 
            Out: 
            array([[[ 0,  1,  2,  3], 
                    [ 4,  5,  6,  7], 
                    [ 8,  9, 10, 11]], 
                   [[12, 13, 14, 15], 
                    [16, 17, 18, 19], 
                    [20, 21, 22, 23]]]) 
            In: b.ravel() 
            Out: 
            array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12,  13, 
            14, 15, 16, 17, 18, 19, 20, 21, 22, 23]) 
    
    
  • 展平:适当命名的功能flatten()ravel()相同。然而,flatten()总是分配新的内存,而ravel返回数组的视图。这意味着我们可以直接操作数组如下:

            In: b.flatten() 
            Out: 
            array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12,  13, 
            14, 15, 16, 17, 18, 19, 20, 21, 22, 23]) 
    
    
  • 用元组设置形状:除了reshape()函数,我们还可以直接用元组定义形状,如下图所示:

            In: b.shape = (6,4) 
            In: b 
            Out: 
            array([[ 0,  1,  2,  3], 
                   [ 4,  5,  6,  7], 
                   [ 8,  9, 10, 11], 
                   [12, 13, 14, 15], 
                   [16, 17, 18, 19], 
                   [20, 21, 22, 23]]) 
    
    
  • 正如您所理解的,前面的代码立即改变了数组。现在,我们有一个 6x4 数组。

  • 转置:线性代数中,矩阵转置很常见。换位是一种转换数据的方式。对于二维表,换位意味着行变成列,列变成行。我们也可以通过使用以下代码来做到这一点:

            In: b.transpose() 
            Out: 
            array([[ 0,  4,  8, 12, 16, 20], 
                   [ 1,  5,  9, 13, 17, 21], 
                   [ 2,  6, 10, 14, 18, 22], 
                   [ 3,  7, 11, 15, 19, 23]]) 
    
    
  • 调整的大小:resize()方法的工作原理与reshape()方法相同,但改变了它所作用的数组:

            In: b.resize((2,12)) 
            In: b 
            Out: 
            array([[ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11], 
                   [12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23]]) 
    
    

堆叠数组

数组可以水平、深度或垂直堆叠。为了这个目标,我们可以使用vstack()dstack()hstack()column_stack()row_stack()concatenate()功能。首先,让我们设置一些数组:

In: a = np.arange(9).reshape(3,3) 
In: a 
Out: 
array([[0, 1, 2], 
       [3, 4, 5], 
       [6, 7, 8]]) 
In: b = 2 * a 
In: b 
Out: 
array([[ 0,  2,  4], 
       [ 6,  8, 10], 
       [12, 14, 16]]) 

如前所述,我们可以使用以下技术堆叠数组:

  • 水平堆叠:从水平堆叠开始,我们将形成一个ndarrays元组,交给hstack()函数来堆叠数组。如下所示:

            In: np.hstack((a, b)) 
            Out: 
            array([[ 0,  1,  2,  0,  2,  4], 
                   [ 3,  4,  5,  6,  8, 10], 
                   [ 6,  7,  8, 12, 14, 16]]) 
    
    
  • We can attain the same thing with the concatenate() function, which is shown as follows:

            In: np.concatenate((a, b), axis=1) 
            Out: 
            array([[ 0,  1,  2,  0,  2,  4], 
                   [ 3,  4,  5,  6,  8, 10], 
                   [ 6,  7,  8, 12, 14, 16]]) 
    
    

    下图描述了水平堆叠:

    Stacking arrays

  • Vertical stacking: With vertical stacking, a tuple is formed again. This time it is given to the vstack() function to stack the arrays. This can be seen as follows:

            In: np.vstack((a, b)) 
            Out: 
            array([[ 0,  1,  2], 
                   [ 3,  4,  5], 
                   [ 6,  7,  8], 
                   [ 0,  2,  4], 
                   [ 6,  8, 10], 
                   [12, 14, 16]]) 
    
    

    concatenate()功能给出相同的结果,轴参数固定为0。这是 axis 参数的默认值,如以下代码所示:

              In: np.concatenate((a, b), axis=0) 
              Out: 
              array([[ 0,  1,  2], 
                   [ 3,  4,  5], 
                   [ 6,  7,  8], 
                   [ 0,  2,  4], 
                   [ 6,  8, 10], 
                   [12, 14, 16]]) 
    
    

    垂直堆放见下图:

    Stacking arrays

  • 深度堆叠:首先,深度堆叠使用dstack()和元组,当然。这需要沿着第三个轴(深度)堆叠数组列表。例如,我们可以将图像数据的 2D 数组堆叠在彼此之上,如下所示:

            In: np.dstack((a, b)) 
            Out: 
            array([[[ 0,  0], 
                    [ 1,  2], 
                    [ 2,  4]], 
                   [[ 3,  6], 
                    [ 4,  8], 
                    [ 5, 10]], 
                   [[ 6, 12], 
                    [ 7, 14], 
                    [ 8, 16]]]) 
    
    
  • Column stacking: The column_stack() function stacks 1D arrays column-wise. This is shown as follows:

            In: oned = np.arange(2) 
            In: oned 
            Out: array([0, 1]) 
            In: twice_oned = 2 * oned 
            In: twice_oned 
            Out: array([0, 2]) 
            In: np.column_stack((oned, twice_oned)) 
            Out: 
            array([[0, 0], 
                   [1, 2]]) 
    
    

    2D 数组的堆叠方式与hstack()函数的堆叠方式相同,如下面几行代码所示:

              In: np.column_stack((a, b)) 
              Out: 
              array([[ 0,  1,  2,  0,  2,  4], 
                     [ 3,  4,  5,  6,  8, 10], 
                     [ 6,  7,  8, 12, 14, 16]]) 
              In: np.column_stack((a, b)) == np.hstack((a, b)) 
              Out: 
              array([[ True,  True,  True,  True,  True,  True], 
                     [ True,  True,  True,  True,  True,  True], 
                     [ True,  True,  True,  True,  True,  True]],  
                     dtype=bool) 
    
    

    是的,你猜对了!我们使用==运算符比较了两个数组。

  • Row stacking: NumPy, naturally, also has a function that does row-wise stacking. It is named row_stack() and for 1D arrays, it just stacks the arrays in rows into a 2D array:

            In: np.row_stack((oned, twice_oned)) 
            Out: 
            array([[0, 1], 
                   [0, 2]]) 
    
    

    2D 数组的row_stack()函数结果等于vstack()函数结果:

              In: np.row_stack((a, b)) 
              Out: 
              array([[ 0,  1,  2], 
                     [ 3,  4,  5], 
                     [ 6,  7,  8], 
                     [ 0,  2,  4], 
                     [ 6,  8, 10], 
                     [12, 14, 16]]) 
              In: np.row_stack((a,b)) == np.vstack((a, b)) 
              Out: 
              array([[ True,  True,  True], 
                     [ True,  True,  True], 
                     [ True,  True,  True], 
                     [ True,  True,  True], 
                     [ True,  True,  True], 
                     [ True,  True,  True]], dtype=bool) 
    
    

拆分 NumPy 数组

数组可以垂直、水平或深度拆分。涉及的功能有hsplit()vsplit()dsplit()split()。我们可以将数组拆分成相同形状的数组,或者指出拆分应该发生的位置。让我们详细看看每个函数:

  • Horizontal splitting: The following code splits a 3x3 array on its horizontal axis into three parts of the same size and shape:

            In: a 
            Out: 
            array([[0, 1, 2], 
                   [3, 4, 5], 
                   [6, 7, 8]]) 
            In: np.hsplit(a, 3) 
            Out: 
            [array([[0], 
                   [3], 
                   [6]]), 
             array([[1], 
                   [4], 
                   [7]]), 
             array([[2], 
                   [5], 
                   [8]])] 
    
    

    将它比作对split()函数的调用,还有一个额外的参数axis=1:

              In: np.split(a, 3, axis=1) 
              Out: 
              [array([[0], 
                     [3], 
                     [6]]), 
               array([[1], 
                     [4], 
                     [7]]), 
               array([[2], 
                     [5], 
                     [8]])] 
    
    
  • Vertical splitting: vsplit() splits along the vertical axis:

            In: np.vsplit(a, 3) 
            Out: [array([[0, 1, 2]]), array([[3, 4, 5]]), 
                  array([[6, 7,  8]])] 
    
    

    带有axis=0split()功能也沿垂直轴拆分:

              In: np.split(a, 3, axis=0) 
              Out: [array([[0, 1, 2]]), array([[3, 4, 5]]), 
                    array([[6, 7,  8]])] 
    
    
  • 深度分裂:不出所料,dsplit()函数会深度分裂。我们需要一组等级3来开始:

            In: c = np.arange(27).reshape(3, 3, 3) 
            In: c 
            Out: 
            array([[[ 0,  1,  2], 
                    [ 3,  4,  5], 
                    [ 6,  7,  8]], 
                   [[ 9, 10, 11], 
                    [12, 13, 14], 
                    [15, 16, 17]], 
                   [[18, 19, 20], 
                    [21, 22, 23], 
                    [24, 25, 26]]]) 
            In: np.dsplit(c, 3) 
            Out: 
            [array([[[ 0], 
                    [ 3], 
                    [ 6]], 
                   [[ 9], 
                    [12], 
                    [15]], 
                   [[18], 
                    [21], 
                    [24]]]), 
             array([[[ 1], 
                    [ 4], 
                    [ 7]], 
                   [[10], 
                    [13], 
                    [16]], 
                   [[19], 
                    [22], 
                    [25]]]), 
             array([[[ 2], 
                    [ 5], 
                    [ 8]], 
                   [[11], 
                    [14], 
                    [17]], 
                   [[20], 
                    [23], 
                    [26]]])] 
    
    

NumPy 数组属性

让我们借助一个例子来了解更多关于 NumPy 数组属性的信息。让我们创建一个数组b,我们将使用它来练习进一步的示例:

In: b = np.arange(24).reshape(2, 12) 
In: b 
Out: 
array([[ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11], 
       [12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23]]) 

除了shapedtype属性外,ndarray还有许多其他属性,如下表所示:

  • ndim属性给出了维数,如下面的代码片段所示:

            In: b.ndim 
            Out: 2 
    
    
  • size属性保存元素的计数。如下所示:

            In: b.size 
            Out: 24 
    
    
  • itemsize属性返回数组中每个元素的字节数,如下面的代码片段所示:

            In: b.itemsize 
            Out: 8 
    
    
  • 如果您需要数组所需的完整字节数,可以查看nbytes。这只是itemsizesize属性的产物:

            In: b.nbytes 
            Out: 192 
            In: b.size * b.itemsize 
            Out: 192 
    
    
  • T属性的结果与transpose()函数相同,如下所示:

            In: b.resize(6,4) 
            In: b 
            Out: 
            array([[ 0,  1,  2,  3], 
                   [ 4,  5,  6,  7], 
                   [ 8,  9, 10, 11], 
                   [12, 13, 14, 15], 
                   [16, 17, 18, 19], 
                   [20, 21, 22, 23]]) 
            In: b.T 
            Out: 
            array([[ 0,  4,  8, 12, 16, 20], 
                   [ 1,  5,  9, 13, 17, 21], 
                   [ 2,  6, 10, 14, 18, 22], 
                   [ 3,  7, 11, 15, 19, 23]]) 
    
    
  • 如果数组的秩小于 2,我们将只获得数组的视图:

            In: b.ndim 
            Out: 1 
            In: b.T 
            Out: array([0, 1, 2, 3, 4]) 
    
    
  • NumPy 中的复数用j表示。例如,我们可以生成一个复数数组,如下所示:

            In: b = np.array([1.j + 1, 2.j + 3]) 
            In: b 
            Out: array([ 1.+1.j,  3.+2.j]) 
    
    
  • real属性向我们返回数组的实数部分,或者如果数组只包含实数,则返回数组本身:

            In: b.real 
            Out: array([ 1.,  3.]) 
    
    
  • imag属性保存数组的虚部:

            In: b.imag 
            Out: array([ 1.,  2.]) 
    
    
  • 如果数组包含复数,那么数据类型也会自动复杂:

            In: b.dtype 
            Out: dtype('complex128') 
            In: b.dtype.str 
            Out: '<c16' 
    
    
  • flat属性返回一个numpy.flatiter对象。这是获得一个flatiter对象的唯一手段;我们无法访问flatiter构造器。flat迭代器使我们能够像遍历平面数组一样遍历数组,如下面的代码片段所示:

            In: b = np.arange(4).reshape(2,2) 
            In: b 
            Out: 
            array([[0, 1], 
                   [2, 3]]) 
            In: f = b.flat 
            In: f 
            Out: <numpy.flatiter object at 0x103013e00> 
            In: for item in f: print(item) 
            Out:  
            0 
            1 
            2 
            3 
    
    
  • 可以直接获得具有flatiter对象的元素:

            In: b.flat[2] 
            Out: 2 
    
    
  • 另外,您可以获得多个元素,如下所示:

            In: b.flat[[1,3]] 
            Out: array([1, 3]) 
    
    
  • 可以设置flat属性。设置flat属性的值会覆盖整个数组的值:

            In: b.flat = 7 
            In: b 
            Out: 
            array([[7, 7], 
                   [7, 7]]) 
    
    
  • We can also obtain selected elements as follows:

            In: b.flat[[1,3]] = 1 
            In: b 
            Out: 
            array([[7, 1], 
                   [7, 1]]) 
    
    

    下图说明了ndarray的各种属性:

    NumPy array attributes

转换数组

我们可以用tolist()函数将 NumPy 数组转换成 Python 列表。以下是简要说明:

  • 转换为列表:

            In: b 
            Out: array([ 1.+1.j,  3.+2.j]) 
            In: b.tolist() 
            Out: [(1+1j), (3+2j)] 
    
    
  • astype()函数将数组转换为指定数据类型的数组:

            In: b 
            Out: array([ 1.+1.j,  3.+2.j]) 
            In: b.astype(int) 
            /usr/local/lib/python3.5/site-packages/ipykernel/__main__.py:1:  
            ComplexWarning: Casting complex values to real discards the 
            imaginary part
            ... 
            Out: array([1, 3]) 
            In: b.astype('complex') 
            Out: array([ 1.+1.j,  3.+2.j]) 
    
    

当从复数类型转换为整数类型时,我们去掉了虚部。astype()函数也将数据类型的名称作为字符串。

前面的代码这次不会显示警告,因为我们使用了正确的数据类型。

创建数组视图和副本

在关于ravel()的例子中,提出了一些观点。视图不应与数据库视图的构造混淆。NumPy 世界中的视图不是只读的,您没有保护底层信息的可能性。了解我们何时处理共享数组视图以及何时拥有数组数据的副本至关重要。例如,一个数组的切片将产生一个视图。这意味着,如果您将切片分配给一个变量,然后更改底层数组,该变量的值将会改变。我们将从 SciPy 包中的人脸图片创建一个数组,然后在最后阶段创建一个视图并修改它:

  1. 获取人脸图像:

            face = scipy.misc.face() 
    
    
  2. 创建面数组的副本:

            acopy = face.copy() 
    
    
  3. 创建数组视图:

            aview = face.view() 
    
    
  4. flat迭代器将视图中的所有值设置为0:

            aview.flat = 0 
    
    

最后的结果是只有一张图片描绘了这个模型。其他的全部被审查,如下图所示:

Creating array views and copies

请参考本教程的以下代码,其中显示了数组视图和副本的行为:

import scipy.misc 
import matplotlib.pyplot as plt 

face = scipy.misc.face() 
acopy = face.copy() 
aview = face.view() 
aview.flat = 0 
plt.subplot(221) 
plt.imshow(face) 
plt.subplot(222) 
plt.imshow(acopy) 
plt.subplot(223) 
plt.imshow(aview) 
plt.show() 

如您所见,通过改变程序末尾的视图,我们修改了原始的 Lena 数组。这导致了三张蓝色(或者黑色,如果你正在阅读这本书的印刷版)的图片。复制的数组没有改变。重要的是要记住视图不是只读的。

花式索引

花式索引是不涉及整数或切片的索引,是常规的索引。在本教程中,我们将练习花式索引,将 Lena 照片的对角线值设置为0。这将沿着对角线画黑线,穿过它们。

以下是该示例的代码:

import scipy.misc 
import matplotlib.pyplot as plt 

face = scipy.misc.face() 
xmax = face.shape[0] 
ymax = face.shape[1] 
face=face[:min(xmax,ymax),:min(xmax,ymax)] 
xmax = face.shape[0] 
ymax = face.shape[1] 
face[range(xmax), range(ymax)] = 0 
face[range(xmax-1,-1,-1), range(ymax)] = 0 
plt.imshow(face) 
plt.show() 

以下是前面代码的简要说明:

  1. Set the values of the first diagonal to 0.

    要将对角线值设置为0,我们需要为 xy 值(笛卡尔坐标系中的坐标)指定两个不同的范围:

            face[range(xmax), range(ymax)] = 0 
    
    
  2. Set the values of the other diagonal to 0.

    要设置另一条对角线的值,我们需要一组不同的范围,但规则保持不变:

              face[range(xmax-1,-1,-1), range(ymax)] = 0 
    
    

    在最后一个阶段,我们画出对角线被划掉的下图:

    Fancy indexing

我们为 x 值和 y 值指定了不同的范围。这些范围用于索引 Lena 数组。花式索引是基于内部 NumPy 迭代器对象完成的。执行以下三个步骤:

  1. 迭代器对象被创建。
  2. 迭代器对象绑定到数组。
  3. 数组元素通过迭代器访问。

用位置列表索引

让我们应用ix_()功能对 Lena 照片进行洗牌。下面是本例的代码,没有注释。食谱的最终代码可以在本书的代码包ix.py中找到:

import scipy.misc 
import matplotlib.pyplot as plt 
import numpy as np 

face = scipy.misc.face() 
xmax = face.shape[0] 
ymax = face.shape[1] 

def shuffle_indices(size): 
   arr = np.arange(size) 
   np.random.shuffle(arr) 

   return arr 

xindices = shuffle_indices(xmax) 
np.testing.assert_equal(len(xindices), xmax) 
yindices = shuffle_indices(ymax) 
np.testing.assert_equal(len(yindices), ymax) 
plt.imshow(face[np.ix_(xindices, yindices)]) 
plt.show() 

该函数从多个序列生成一个网格。我们将参数作为一维序列提交,函数返回一组 NumPy 数组,例如,如下所示:

In : ix_([0,1], [2,3]) 
Out: 
(array([[0],[1]]), array([[2, 3]])) 

要用位置列表索引 NumPy 数组,请执行以下步骤:

  1. Shuffle the array indices.

    numpy.random子包的shuffle()功能做一个随机索引号的数组。该函数就地修改数组:

              def shuffle_indices(size): 
                 arr = np.arange(size) 
                 np.random.shuffle(arr) 
    
              return arr 
    
    
  2. 绘制混合指数,如以下代码所示:

            plt.imshow(face[np.ix_(xindices, yindices)]) 
    
    

我们得到的是一个完全混乱的莉娜:

Indexing with a list of locations

用布尔值索引 NumPy 数组

布尔索引是基于布尔数组的索引,属于花式索引家族。因为布尔索引是一种花哨的索引,所以它的工作方式本质上是相同的。

以下是该段的代码(参见本书代码包中的boolean_indexing.py):

import scipy.misc 
import matplotlib.pyplot as plt 
import numpy as np 

face = scipy.misc.face() 
xmax = face.shape[0] 
ymax = face.shape[1] 
face=face[:min(xmax,ymax),:min(xmax,ymax)] 

def get_indices(size): 
   arr = np.arange(size) 
   return arr % 4 == 0 

face1 = face.copy()  
xindices = get_indices(face.shape[0]) 
yindices = get_indices(face.shape[1]) 
face1[xindices, yindices] = 0 
plt.subplot(211) 
plt.imshow(face1) 
face2 = face.copy()  
face2[(face > face.max()/4) & (face < 3 * face.max()/4)] = 0 
plt.subplot(212) 
plt.imshow(face2) 
plt.show() 

前面的代码意味着索引是在一个特殊的迭代器对象的帮助下进行的。

以下步骤将简要解释前面的代码:

  1. Image with dots on the diagonal.

    这在某种程度上类似于花式索引部分。这次我们选择图片对角线上的模 4 点:

              def get_indices(size):
               arr = np.arange(size)
               return arr % 4 == 0
    

    然后,我们只使用这个选择并绘制点:

              face1 = face.copy()  
              xindices = get_indices(face.shape[0]) 
              yindices = get_indices(face.shape[1]) 
              face1[xindices, yindices] = 0 
              plt.subplot(211) 
              plt.imshow(face1) 
    
    
  2. Set to 0 based on value.

    选择最大值四分之一到四分之三之间的数组值,并将其设置为0:

              face2[(face > face.max()/4) & (face < 3 * face.max()/4)] =  0 
    
    

    带有两张新图片的图表如下所示:

    Indexing NumPy arrays with Booleans

广播 NumPy 数组

NumPy 试图执行一个过程,即使操作数没有相同的形状。

在这个食谱中,我们将把一个数组和一个标量相乘。标量被扩展为数组操作数的形状,然后执行乘法。这里描述的过程叫做广播。以下是该配方的全部代码:

import scipy.io.wavfile as sw 
import matplotlib.pyplot as plt 
import urllib 
import numpy as np 

request = urllib.request.Request('http://www.thesoundarchive.com/austinpowers/smashingbaby.wav') 
response = urllib.request.urlopen(request) 
print(response.info()) 
WAV_FILE = 'smashingbaby.wav' 
filehandle = open(WAV_FILE, 'wb') 
filehandle.write(response.read()) 
filehandle.close() 
sample_rate, data = sw.read(WAV_FILE) 
print("Data type", data.dtype, "Shape", data.shape) 

plt.subplot(2, 1, 1) 
plt.title("Original") 
plt.plot(data) 

newdata = data * 0.2 
newdata = newdata.astype(np.uint8) 
print("Data type", newdata.dtype, "Shape", newdata.shape) 

sw.write("quiet.wav", sample_rate, newdata) 

plt.subplot(2, 1, 2) 
plt.title("Quiet") 
plt.plot(newdata)2 

plt.show() 

我们将下载一个声音文件,并创建一个更安静的新版本:

  1. Reading a WAV file.

    我们将使用标准的 Python 代码来下载一个声音文件,奥斯汀·鲍尔斯大声喊道:“粉碎,宝贝”SciPy 有一个wavfile子包,可以让你加载音频数据或者生成 WAV 文件。如果安装了 SciPy,那么我们应该已经有了这个子包。read()功能提供数据数组和采样率。在本练习中,我们只关注数据:

              sample_rate, data = scipy.io.wavfile.read(WAV_FILE) 
    
    
  2. Plot the original WAV data.

    用 Matplotlib 绘制原始 WAV 数据,给子图起标题Original:

              plt.subplot(2, 1, 1) 
              plt.title("Original") 
              plt.plot(data) 
    
    
  3. Create a new array.

    现在,我们将使用 NumPy 产生一个静音样本。只需要用一个常数乘以一个新数组,就可以得到一个更小的值。这就是广播的诀窍所在。最后,由于 WAV 格式,我们希望确保我们拥有与原始数组中相同的数据类型:

              newdata = data * 0.2 
              newdata = newdata.astype(np.uint8) 
    
    
  4. Write to a WAV file.

    这个新数组可以保存在一个新的 WAV 文件中,如下所示:

              scipy.io.wavfile.write("quiet.wav", 
                  sample_rate, newdata) 
    
    
  5. Plot the new WAV data.

    用 Matplotlib 绘制新的数据数组,如下所示:

              plt.subplot(2, 1, 2) 
              plt.title("Quiet") 
              plt.plot(newdata) 
              plt.show() 
    
    

    结果是原始 WAV 文件数据和具有较小值的新数组的图表,如下图所示:

    Broadcasting NumPy arrays

总结

在这一章中,我们发现了一堆关于 NumPy 的基础知识——数据类型和数组。数组有各种描述它们的属性。您了解到其中一个属性是数据类型,在 NumPy 中,它由一个完整的对象表示。

与标准 Python 列表相比,NumPy 数组可以以有效的方式进行切片和索引。NumPy 数组具有处理多维度的额外能力。

可以通过多种方式修改数组的形状,例如堆叠、调整大小、整形和拆分。本章介绍了大量用于形状操作的便利功能。

已经掌握了基础知识,现在是时候使用第 4 章统计和线性代数中的常用函数进行数据分析了。这包括使用主要的统计和数字函数。

鼓励读者阅读参考资料一节中提到的书籍,以便更详细、更深入地探索 NumPy。

参考文献

  • I. Idris, NumPy Cookbook -第二版, Packt 出版,2015。
  • I. Idris,学习 NumPy 数组, Packt 出版,2014。
  • I. Idris, Numpy:初学者指南-第三版, Packt 出版,2015。
  • 长度(梁辉)秦和 t .杜塔, NumPy Essentials, Packt 出版,2016。

三、Pandas 入门

Pandas 以面板数据(计量经济学术语)和 Python 数据分析命名,是一个流行的开源 Python 库。在本章中,我们将学习 Pandas 的基本功能、数据结构和操作。

官方 Pandas 文件坚持用小写字母命名项目“T1”Pandas“T2”。Pandas 项目坚持的另一个惯例是import pandas as pd进口声明。

我们将在本文中遵循这些约定。

在本章中,我们将安装和探索 Pandas。然后,我们将熟悉 Pandas 的两个核心数据结构——数据框架和系列。之后,您将学习如何对这些数据结构中包含的数据执行类似于 SQL 的操作。Pandas 有统计工具,包括时间序列例程,其中一些将被演示。我们将研究的主题如下:

  • 安装和探索 Pandas
  • Pandas 数据帧
  • Pandas 系列
  • Pandas 中的数据查询
  • Pandas 数据帧的统计
  • Pandas 数据帧的数据聚合
  • 连接和附加数据帧
  • 正在加入数据框
  • 处理缺失值
  • 处理日期
  • 数据透视表

安装和探索 Pandas

Pandas 的最小依赖集要求如下:

  • NumPy :这是我们安装的基本数值数组包,在前面的章节中已经详细介绍过了
  • python-dateutil :这是一个日期处理库
  • pytz :这处理时区定义

这份清单是最起码的;可选依赖项的更长列表可以位于http://pandas.pydata.org/pandas-docs/stable/install.html。我们可以使用二进制安装程序,借助我们的操作系统包管理器,或者通过检查代码从源代码,通过 PyPI 用pipeasy_install安装 Pandas。二进制安装程序可以从http://pandas.pydata.org/getpandas.html下载。

pip安装 Pandas 的命令如下:

$ pip3 install pandas rpy2

rpy2是 R 的接口,因为rpy被弃用,所以需要。如果您的用户帐户没有足够的权限,您可能需要在前面的命令前面加上sudo

正如我们在第 1 章Python 库入门中看到的,我们可以打印 Pandas 的版本和子包。该程序为 Pandas 打印了以下输出:

pandas version 0.19.0
pandas.api
pandas.compat DESCRIPTION compat  Cross-compatible functions for Python 2 and 3\. Key items to import for 2/3 compatible code: * iterators: range(), map(), 
pandas.computation
pandas.core
pandas.formats
pandas.indexes
pandas.io
pandas.msgpack DESCRIPTION # coding: utf-8 # flake8: noqa PACKAGE CONTENTS _packer _unpacker _version exceptions CLASSES ExtType(builtins.tuple) ExtType cl 
pandas.rpy DESCRIPTION # GH9602 # deprecate rpy to instead directly use rpy2 PACKAGE CONTENTS base common mass vars FILE /usr/local/lib/python3.5/site- 
pandas.sparse
pandas.stats
pandas.tests
pandas.tools
pandas.tseries
pandas.types
pandas.util

不幸的是,Pandas 子包的文档缺乏信息描述;然而,子包名称的描述性足以让我们了解它们的内容。

Pandas 数据帧

Pandas 数据框架是一种带标签的二维数据结构,在精神上类似于谷歌工作表或微软 Excel 中的工作表,或者关系数据库表。Pandas 数据框中的列可以是不同的类型。顺便说一下,类似的概念最初是在 R 编程语言中发明的。(更多信息请参考http://www.r-tutor.com/r-introduction/data-frame)。可以通过以下方式创建数据框:

  • 使用另一个数据帧。
  • 使用 NumPy 数组或具有二维形状的数组组合。
  • 同样,我们可以从另一个 Pandas 数据结构创建一个数据帧,称为系列。我们将在下一节中了解系列。
  • 数据帧也可以从文件中生成,例如 CSV 文件。
  • 来自一维结构的字典,如一维 NumPy 数组、列表、字典或 Pandas 系列。

例如,我们将使用可以从http://www.exploredata.net/Downloads/WHO-Data-Set检索的数据。原始数据文件比较大,列比较多,我们就用一个编辑好的文件代替,只包含前九列,叫做WHO_first9cols.csv;文件在这本书的代码包里。这是前两行,包括标题:

Country,CountryID,Continent,Adolescent fertility rate (%),Adult literacy rate (%),Gross national income per capita (PPP international $),Net primary school enrolment ratio female (%),Net primary school enrolment ratio male (%),Population (in thousands) total
Afghanistan,1,1,151,28,,,,26088

在接下来的步骤中,我们将了解 Pandas 数据帧及其属性:

  1. To kick off, load the data file into a DataFrame and print it on the screen:

              from pandas.io.parsers import read_csv 
    
              df = read_csv("WHO_first9cols.csv") 
              print("Dataframe", df) 
    
    

    打印输出是数据框的摘要。它太长,无法完全显示,所以我们将只抓取最后几行:

              199                          21732.0   
              200                          11696.0   
              201                          13228.0
    
              [202 rows x 9 columns]
    
  2. The DataFrame has an attribute that holds its shape as a tuple, similar to ndarray. Query the number of rows of a DataFrame as follows:

            print("Shape", df.shape) 
            print("Length", len(df)) 
    
    

    我们获得的值符合上一步的打印输出:

            Shape (202, 9) 
            Length 202 
    
    
  3. Check the column's header and data types with the other attributes:

            print("Column Headers", df.columns) 
            print("Data types", df.dtypes) 
    
    

    我们接收特殊数据结构中的列标题:

              Column Headers Index([u'Country', u'CountryID', u'Continent',  
              u'Adolescent fertility rate (%)', u'Adult literacy rate (%)', 
              u'Gross national income per capita (PPP international $)', 
              u'Net primary school enrolment ratio female (%)', 
              u'Net primary school enrolment ratio male (%)', 
              u'Population (in thousands) total'], dtype='object') 
    
    

    数据类型打印如下:

    The Pandas DataFrames

  4. The Pandas DataFrame has an index, which is like the primary key of relational database tables. We can either specify the index or have Pandas create it automatically. The index can be accessed with a corresponding property, as follows:

            Print("Index", df.index) 
    
    

    索引帮助我们快速搜索项目,就像本书中的索引一样。在我们的例子中,索引是从0开始的数组的包装,每行增加一个:

              Index Int64Index([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 
              13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 
              28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 
              43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 
              58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 
              73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 
              88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...], 
              dtype='int64')
    
  5. 有时,我们希望迭代数据框架的底层数据。如果我们使用 Pandas 迭代器,对列值进行迭代可能效率很低。提取底层的 NumPy 数组并使用它们要好得多。Pandas 数据框也有一个属性可以帮助你做到这一点:

            print("Values", df.values) 
    
    

请注意,有些值在输出中被指定为nan,表示“不是数字”。这些值来自输入数据文件中的空字段:

Values [['Afghanistan' 1 1 ..., nan nan 26088.0] 
 ['Albania' 2 2 ..., 93.0 94.0 3172.0] 
 ['Algeria' 3 3 ..., 94.0 96.0 33351.0]
 ..., 
 ['Yemen' 200 1 ..., 65.0 85.0 21732.0] 
 ['Zambia' 201 3 ..., 94.0 90.0 11696.0] 
 ['Zimbabwe' 202 3 ..., 88.0 87.0 13228.0]]

前面的代码可以在 Python 笔记本ch-03.ipynb中找到,可以在本书的代码包中找到。

Pandas 系列

PandasSeries数据结构是一个带有标签的一维异构数组。我们可以创建 PandasSeries数据结构如下:

  • 使用 Python 格言
  • 使用 NumPy 数组
  • 使用单个标量值

当创建一个系列时,我们可以给构造函数一个轴标签的列表,这通常被称为索引。索引是一个可选参数。默认情况下,如果我们使用 NumPy 数组作为输入数据,Pandas 将通过从 0 开始自动递增索引来索引值。如果传递给构造函数的数据是 Python 字典,排序后的字典键将成为索引。在标量值作为输入数据的情况下,我们需要提供索引。对于索引中的每个新值,将重复标量输入值。Pandas 系列和数据框架接口的特性和行为借鉴了 NumPy 数组和 Python 字典,例如切片、使用键的查找函数和向量化操作。对DataFrame列执行查找会返回Series。我们将通过返回上一节并再次加载 CSV 文件来演示Series的这一特性和其他特性:

  1. We will start by selecting the Country column, which happens to be the first column in the datafile. Then, show the type of the object currently in the local scope:

            country_col = df["Country"] 
            print("Type df", type(df)) 
            print("Type country col", type(country_col)) 
    
    

    我们现在可以确认,当我们选择数据框的一列时,我们得到了一个系列:

              Type df <class 'pandas.core.frame.DataFrame'>
              Type country col  <class 'pandas.core.series.Series'>
    

    如果需要,您可以打开 Python 或 IPython shell,导入 Pandas,并使用dir()函数查看上一次打印输出中找到的类的函数和属性列表。但是,请注意,在这两种情况下,您都会得到一长串函数。

  2. The Pandas Series data structure shares some of the attributes of DataFrame, and also has a name attribute. Explore these properties as follows:

            print("Series shape", country_col.shape) 
            print("Series index", country_col.index) 
            print("Series values", country_col.values) 
            print("Series name", country_col.name) 
    
    

    输出(为了节省空间而被截断)如下所示:

              Series shape (202,)
              Series index Int64Index([0, 1, 2, 3, 4, 5, 
              6, 7, 8, 9, 10, 11, 12, ...], dtype='int64')
              Series values ['Afghanistan' ... 'Vietnam' 'West Bank and          
              Gaza' 'Yemen' 'Zambia' 'Zimbabwe']
              Series name Country
    
  3. To demonstrate the slicing of a Series, select the last two countries of the Country Series and print the type:

            print("Last 2 countries", country_col[-2:]) 
            print("Last 2 countries type", type(country_col[-2:])) 
    
    

    切片产生另一个系列,如下所示:

              Last 2 countries
              200      Zambia
              201    Zimbabwe
              Name: Country, dtype: object
              Last 2 countries type <class 'pandas.core.series.Series'>
    
  4. NumPy functions can operate on Pandas DataFrame and Series. We can, for instance, apply the NumPy sign() function, which yields the sign of a number. 1 is returned for positive numbers, -1 for negative numbers, and 0 for zeros. Apply the function to the DataFrame's last column, which happens to be the population for each country in the dataset:

            last_col = df.columns[-1] 
            print("Last df column signs:\n", last_col, 
            np.sign(df[last_col]), "\n") 
    
    

    为了节省空间,输出在这里被截断,如下所示:

              Last df column signs Population (in thousands) total 0     1
              1     1
              [TRUNCATED]
              198   NaN
              199     1
              200     1
              201     1
              Name: Population (in thousands) total, Length: 202, dtype: 
              float64
    

请注意,指数 198 的人口值为NaN。匹配记录如下:West Bank and Gaza,199,1,,,,,,

我们可以在数据帧、序列和 NumPy 数组之间执行各种数值操作。如果我们得到 Pandas 系列的基本 NumPy 数组,并从该系列中减去该数组,我们可以合理地预期以下两个结果:

  • 用零和至少一个 NaN 填充的数组(我们在上一步中看到了一个 NaN)
  • 我们也可以期望只得到零

NumPy 函数的规则是为涉及 nan 的大多数操作生成 nan,如以下 IPython 会话所示:

In: np.sum([0, np.nan])
Out: nan

编写以下代码来执行减法:

print np.sum(df[last_col] - df[last_col].values) 

该代码片段产生第二个选项预测的结果:

0.0

请参考本书代码包中的ch-03.ipynb文件。

查询 Pandas 的数据

由于 Pandas 数据框的结构类似于关系数据库,因此我们可以将从数据框中读取数据的操作视为查询。在这个例子中,我们将从 Quandl 检索年太阳黑子数据。我们可以使用 Quandl API 或者从http://www . Quandl . com/SIDC/黑子 _ A-黑子-数字-年度手动下载数据作为 CSV 文件。如果您想安装应用编程接口,您可以通过从https://pypi.python.org/pypi/Quandl下载安装程序或运行以下命令来完成:

$ pip3 install Quandl

使用该应用编程接口是免费的,但仅限于每天 50 次应用编程接口调用。如果您需要更多的 API 调用,您将不得不请求一个身份验证密钥。本教程中的代码没有使用键。将代码更改为使用密钥或读取下载的 CSV 文件应该很简单。如果您有困难,请参考第 1 章 中的哪里可以找到帮助和参考资料部分,或者在https://docs.python.org/2/的 Python 文档中搜索。

没有进一步的前言,让我们来看看如何查询 Pandas 数据框中的数据:

  1. 作为第一步,我们显然必须下载数据。导入 Quandl API 后,得到如下数据:

            import quandl 
    
            # Data from 
            http://www.quandl.com/SIDC/SUNSPOTS_A-Sunspot-Numbers-Annual 
            # PyPi url https://pypi.python.org/pypi/Quandl 
            sunspots = quandl.get("SIDC/SUNSPOTS_A") 
    
    
  2. The head() and tail() methods have a purpose similar to that of the Unix commands with the same name. Select the first n and last n records of a DataFrame, where n is an integer parameter:

            print("Head 2", sunspots.head(2) ) 
            print("Tail 2", sunspots.tail(2)) 
    
    

    这给了我们太阳黑子数据的前两行和后两行(为了简洁起见,我们这里没有显示所有的列;您的输出将包含数据集的所有列):

              Head 2             Number
              Year              
              1700-12-31         5
              1701-12-31        11
    
              [2 rows x 1 columns]
              Tail 2             Number
              Year              
              2012-12-31    57.7
              2013-12-31    64.9
    
              [2 rows x 1 columns]
    

    请注意,我们只有一栏记录每年太阳黑子的数量。日期是数据框索引的一部分。

  3. The following is the query for the last value using the last date:

            last_date = sunspots.index[-1] 
            print("Last value", sunspots.loc[last_date]) 
    
    

    您可以使用上一步的结果检查以下输出:

              Last value Number    64.9
              Name: 2013-12-31 00:00:00, dtype: float64
    
  4. Query the date with date strings in the YYYYMMDD format as follows:

            print("Values slice by date:\n", sunspots["20020101": 
            "20131231"]) 
    
    

    这给出了从 2002 年到 2013 年的记录:

              Values slice by date             Number
              Year              
              2002-12-31   104.0
              [TRUNCATED]
              2013-12-31    64.9
    
              [12 rows x 1 columns]
    
  5. A list of indices can be used to query as well:

            print("Slice from a list of indices:\n", sunspots.iloc[[2, 4, 
            -4, -2]]) 
    
    

    前面的代码选择了下列行:

              Slice from a list of indices             Number
              Year              
              1702-12-31    16.0
              1704-12-31    36.0
              2010-12-31    16.0
              2012-12-31    57.7
    
              [4 rows x 1 columns]
    
  6. To select scalar values, we have two options. The second option given here should be faster. Two integers are required, the first for the row and the second for the column:

            print("Scalar with Iloc:", sunspots.iloc[0, 0]) 
            print("Scalar with iat", sunspots.iat[1, 0]) 
    
    

    这给了我们数据集的第一个和第二个标量值:

              Scalar with Iloc 5.0
              Scalar with iat 11.0
    
  7. Querying with Booleans works much like the Where clause of SQL. The following code queries for values larger than the arithmetic mean. Note that there is a difference between when we perform the query on the whole DataFrame and when we perform it on a single column:

            print("Boolean selection", sunspots[sunspots > 
            sunspots.mean()]) 
            print("Boolean selection with column label:\n",          
            sunspots[sunspots['Number of Observations'] > sunspots['Number 
            of Observations'].mean()]) 
    
    

    值得注意的区别是,第一个查询生成所有的行,有些行不符合 NaN 值的条件。第二个查询只返回值大于平均值的行:

                Boolean selection             Number
                Year                      
                1700-12-31     NaN
                [TRUNCATED]
                1759-12-31    54.0              
                               ...
    
               [314 rows x 1 columns]
               Boolean selection with column label             Number
               Year              
               1705-12-31    58.0
               [TRUNCATED]
               1870-12-31   139.1               
                              ...
    
               [127 rows x 1 columns]
    

前面的示例代码在本书代码包的ch_03.ipynb文件中。

Pandas 数据帧统计

Pandas 数据框架有十几种统计方法。下表列出了这些方法,以及每种方法的简短描述:

| **方法** | **描述** | | `describe` | 此方法返回一个带有描述性统计信息的小表。 | | `count` | 此方法返回非 NaN 项的数量。 | | `mad` | 该方法计算平均绝对偏差,这是一种类似于标准偏差的稳健度量。 | | `median` | 此方法返回中位数。这相当于第 50 个百分点的值。 | | `min` | 此方法返回最低值。 | | `max` | 此方法返回最高值。 | | `mode` | 此方法返回模式,这是最常出现的值。 | | `std` | 此方法返回标准偏差,用于测量离差。它是方差的平方根。 | | `var` | 此方法返回方差。 | | `skew` | 此方法返回偏斜度。偏斜度表示分布对称。 | | `kurt` | 此方法返回峰度。峰度表示分布形状。 |

使用与前面示例相同的数据,我们将演示这些统计方法。完整的脚本在本书的代码包ch-03.ipynb中:

import quandl 

# Data from http://www.quandl.com/SIDC/SUNSPOTS_A-Sunspot-Numbers-Annual 
# PyPi url https://pypi.python.org/pypi/Quandl 
sunspots = quandl.get("SIDC/SUNSPOTS_A") 
print("Describe", sunspots.describe(),"\n") 
print("Non NaN observations", sunspots.count(),"\n") 
print("MAD", sunspots.mad(),"\n") 
print("Median", sunspots.median(),"\n") 
print("Min", sunspots.min(),"\n") 
print("Max", sunspots.max(),"\n") 
print("Mode", sunspots.mode(),"\n") 
print("Standard Deviation", sunspots.std(),"\n") 
print("Variance", sunspots.var(),"\n") 
print("Skewness", sunspots.skew(),"\n") 
print("Kurtosis", sunspots.kurt(),"\n") 

以下是脚本的输出:

Statistics with Pandas DataFrames

Statistics with Pandas DataFrames

Pandas 数据帧的数据聚合

数据聚合是关系数据库领域中使用的一个术语。在数据库查询中,我们可以按一列或多列中的值对数据进行分组。然后,我们可以对每个组执行各种操作。Pandas 数据框也有类似的功能。我们将生成保存在 Python 字典中的数据,然后使用这些数据创建 Pandas 数据框架。然后,我们将练习 Pandas 聚合功能:

  1. Seed the NumPy random generator to make sure that the generated data will not differ between repeated program runs. The data will have four columns:

    • Weather(一串)
    • Food(也是一串)
    • Price(随机浮动)
    • Number(1 到 9 之间的随机整数)

    用例是,我们有某种消费者购买研究的结果,结合天气和市场定价,我们计算价格的平均值,并跟踪样本大小和参数:

                import pandas as pd
                from numpy.random import seed
                from numpy.random import rand
                from numpy.random import rand_int
                import numpy as np
    
                seed(42)
    
                df = pd.DataFrame({'Weather' : ['cold', 'hot', 'cold',   
                'hot', 'cold', 'hot', 'cold'],
                'Food' : ['soup', 'soup', 'icecream', 'chocolate',
                'icecream', 'icecream', 'soup'],
                'Price' : 10 * rand(7), 'Number' : rand_int(1, 9,)})
                 print(df)
    

    您应该会得到类似如下的输出:

                      Food  Number     Price Weather
               0       soup       8  3.745401    cold
               1       soup       5  9.507143     hot
               2   icecream       4  7.319939    cold
               3  chocolate       8  5.986585     hot
               4   icecream       8  1.560186    cold
               5   icecream       3  1.559945     hot
               6       soup       6  0.580836    cold
    
               [7 rows x 4 columns]
    

    请注意,列标签来自 Python 字典的词汇顺序键。词汇或词典顺序基于字符串中字符的字母顺序。

  2. Group the data by the Weather column and then iterate through the groups as follows:

            weather_group = df.groupby('Weather') 
    
            i = 0 
    
            for name, group in weather_group: 
               i = i + 1 
               print("Group", i, name) 
               print(group) 
    
    

    我们有两种天气,热的和冷的,所以我们有两组:

               Group 1 cold
                      Food  Number     Price Weather
               0      soup       8  3.745401    cold
               2  icecream       4  7.319939    cold
               4  icecream       8  1.560186    cold
               6      soup       6  0.580836    cold
    
               [4 rows x 4 columns]
               Group 2 hot
                       Food  Number     Price Weather
               1       soup       5  9.507143     hot
               3  chocolate       8  5.986585     hot
               5   icecream       3  1.559945     hot
    
               [3 rows x 4 columns]
    
  3. The weather_group variable is a special Pandas object that we get as a result of the groupby() method. This object has aggregation methods, which are demonstrated as follows:

            print("Weather group first\n", weather_group.first()) 
            print("Weather group last\n", weather_group.last()) 
            print("Weather group mean\n", weather_group.mean()) 
    
    

    前面的代码片段打印了每组的第一行、最后一行和平均值:

               Weather group first
                        Food  Number     Price
               Weather                        
               cold     soup       8  3.745401
               hot      soup       5  9.507143
    
               [2 rows x 3 columns]
               Weather group last
                        Food  Number     Price
               Weather                            
               cold         soup       6  0.580836
               hot      icecream       3  1.559945
    
               [2 rows x 3 columns]
               Weather group mean           Number     Price
               Weather                    
               cold     6.500000  3.301591
               hot      5.333333  5.684558
    
               [2 rows x 2 columns]
    
  4. Just as in a database query, we are allowed to group on multiple columns. The groups attribute will then tell us the groups that are formed, as well as the rows in each group:

            wf_group = df.groupby(['Weather', 'Food']) 
            print("WF Groups", wf_group.groups) 
    
    

    对于天气和食物价值的每种可能组合,都会创建一个新的组。每行的成员资格由其索引值表示,如下所示:

            WF Groups {('hot', 'chocolate'): [3], ('cold', 'icecream'):
            [2, 4], ('hot', 'icecream'): [5], ('hot', 'soup'): [1],  
            ('cold', 'soup'): [0, 6]} 
    
    
  5. Apply a list of NumPy functions on groups with the agg() method:

            print("WF Aggregated\n", wf_group.agg([np.mean, np.median])) 
    
    

    显然,我们可以应用更多的函数,但是它看起来比下面的输出更混乱:

    WF Aggregated                    Number             Price                                            
                                       mean  median      mean    median
    Weather Food
    cold    icecream                      6       6  4.440063  4.440063        
            soup                          7       7  2.163119  2.163119
    hot     chocolate                     8       8  5.986585  5.986585              
            icecream                      3       3  1.559945  1.559945           
            soup                          5       5  9.507143  9.507143
    
    [5 rows x 4 columns]
    

完整的数据聚合示例代码在ch-03.ipynb文件中,可以在本书的代码包中找到。

连接和追加数据帧

Pandas 数据框架允许类似于数据库表的内部和外部连接的操作。我们还可以追加和连接行。为了练习追加和连接行,我们将重用上一节中的数据帧。让我们选择前三行:

print("df :3\n", df[:3]) 

检查这些是否确实是前三行:

df :3
       Food  Number     Price Weather
0      soup       8  3.745401    cold
1      soup       5  9.507143     hot
2  icecream       4  7.319939    cold

concat()函数连接数据帧。例如,我们可以将由三行组成的数据框连接到其余的行,以便重新创建原始数据框:

print("Concat Back together\n", pd.concat([df[:3], df[3:]])) 

串联输出如下所示:

Concat Back together
        Food  Number     Price Weather
0       soup       8  3.745401    cold
1       soup       5  9.507143     hot
2   icecream       4  7.319939    cold
3  chocolate       8  5.986585     hot
4   icecream       8  1.560186    cold
5   icecream       3  1.559945     hot
6       soup       6  0.580836    cold

[7 rows x 4 columns]

要追加行,使用append()功能:

print("Appending rows\n", df[:3].append(df[5:])) 

结果是一个DataFrame,原始DataFrame的前三行和最后两行附加在它上面:

Appending rows
       Food  Number     Price Weather
0      soup       8  3.745401    cold
1      soup       5  9.507143     hot
2  icecream       4  7.319939    cold
5  icecream       3  1.559945     hot
6      soup       6  0.580836    cold

[5 rows x 4 columns]

连接数据帧

为了演示加入,我们将使用两个 CSV 文件- dest.csvtips.csv。背后的用例是我们在经营一家出租车公司。每次乘客在目的地下车时,我们都会在dest.csv文件中添加一行司机的员工编号和目的地:

EmpNr,Dest5,The Hague3,Amsterdam9,Rotterdam

有时候司机会得到提示,所以我们希望在tips.csv文件中注册(如果这看起来不现实,请随意想出自己的故事):

EmpNr,Amount5,109,57,2.5

Pandas 中类似数据库的连接可以通过merge()函数或join()数据框方法来完成。join()方法默认连接到指数,这可能不是你想要的。在关系数据库查询语言 SQL 中,我们有内部连接、左外部连接、右外部连接和完全外部连接。

对于联接条件中指定的列,当且仅当值匹配时,内部联接从两个表中选择行。外部联接不需要匹配,并且可能返回更多的行。更多关于连接的信息可以在http://en.wikipedia.org/wiki/Join_%28SQL%29找到。

Pandas 支持所有这些连接类型,但我们只看一下内部连接和完全外部连接:

  • A join on the employee number with the merge() function is performed as follows:

            print("Merge() on key\n", pd.merge(dests, tips, on='EmpNr')) 
    
    

    这就产生了一个内部连接:

              Merge() on key   
                 EmpNr       Dest  Amount
              0      5  The Hague      10
              1      9  Rotterdam       5
    
              [2 rows x 3 columns]
    
  • Joining with the join() method requires providing suffixes for the left and right operands:

            print("Dests join() tips\n", dests.join(tips, lsuffix='Dest',  
            rsuffix='Tips')) 
    
    

    此方法调用联接索引值,因此结果不同于 SQL 内部联接:

               Dests join() tips
                  EmpNrDest       Dest  EmpNrTips  Amount
               0          5  The Hague          5    10.0
               1          3  Amsterdam          9     5.0
               2          9  Rotterdam          7     2.5
    
               [3 rows x 4 columns]
    
  • An even more explicit way to execute an inner join with merge() is as follows:

            print("Inner join with merge()\n", pd.merge(dests, tips, 
            how='inner')) 
    
    

    输出如下:

               Inner join with merge()
                  EmpNr       Dest  Amount
               0      5  The Hague      10
               1      9  Rotterdam       5
    
               [2 rows x 3 columns]
    

    要使这成为完全的外部连接,只需要一个小的改变:

                print("Outer join\n", pd.merge(dests, tips, how='outer')) 
    
    

    外部连接添加具有NaN值的行:

                Outer join
                   EmpNr       Dest  Amount
                0      5  The Hague    10.0
                1      3  Amsterdam     NaN
                2      9  Rotterdam     5.0
                3      7        NaN     2.5
    
                [4 rows x 3 columns]
    

在关系数据库查询中,这些值会被设置为NULL。演示代码在本书代码包的ch-03.ipynb文件中。

处理缺失值

我们经常在数据记录中遇到空字段。我们最好接受这一点,并学习如何以稳健的方式处理这类问题。真实的数据不仅可能有缺口,还可能有错误的值,例如,由于测量设备故障。在 Pandas 中,缺少的数值将被指定为NaN,对象将被指定为None,而datetime64对象将被指定为NaT。带有NaN值的算术运算的结果也是NaN。描述性统计方法,如求和和平均,表现不同。正如我们在前面的例子中观察到的,在这种情况下,NaN值被视为零值。然而,如果在求和过程中所有的值都是NaN,返回的总和仍然是NaN。在聚合操作中,我们分组的列中的NaN值被忽略。我们将再次将WHO_first9cols.csv文件加载到数据框中。请记住,该文件包含空字段。我们只选择前三行,包括CountryNet primary school enrolment ratio male (%)列的标题如下:

df = df[['Country', df.columns[-2]]][:2] 
print("New df\n", df) 

我们得到一个具有两个 NaN 值的数据帧:

New df
       Country  Net primary school enrolment ratio male (%)
0  Afghanistan                                          NaN
1      Albania                                           94

[2 rows x 2 columns]

Pandasisnull()功能检查缺失值,如下所示:

print("Null Values\n", pd.isnull(df)) 

我们的数据帧输出如下:

Null Values
  Country Net primary school enrolment ratio male (%)
0   False                                        True
1   False                                       False

为了计算每一列的NaN值的数量,我们可以对isnull()返回的布尔值求和。这是因为在求和过程中,True值被视为 1,False值被视为 0:

Total Null Values
Country                                        0
Net primary school enrolment ratio male (%)    1
dtype: int64

同样,我们可以使用数据框notnull()方法检查任何存在的非缺失值:

print("Not Null Values\n", df.notnull()) 

notnull()方法的结果与isnull()功能相反:

Not Null Values
  Country Net primary school enrolment ratio male (%)
0    True                                       False
1    True                                        True

当我们将具有NaN值的数据帧中的值加倍时,乘积仍将包含NaN值,因为加倍是一种算术运算:

print("Last Column Doubled\n", 2 * df[df.columns[-1]]) 

我们将包含数值的最后一列加倍(将字符串值加倍会重复字符串):

Last Column Doubled
0    NaN
1    188
Name: Net primary school enrolment ratio male (%), dtype: float64

然而,如果我们添加一个NaN值,则NaN值获胜:

print("Last Column plus NaN\n", df[df.columns[-1]] + np.nan) 

如你所见,NaN值宣布完全胜利:

Last Column plus NaN
0   NaN1   NaN
Name: Net primary school enrolment ratio male (%), dtype: float64

用标量值替换缺少的值。例如,用fillna()方法替换0(我们不能总是用零替换丢失的值,但有时这已经足够好了):

print("Zero filled\n", df.fillna(0)) 

前一行的效果是将 NaN 值替换为 0:

Zero filled
       Country  Net primary school enrolment ratio male (%)
0  Afghanistan                                            0
1      Albania                                           94

本节的代码在本书代码包的ch-03.ipynb文件中:

处理日期

日期很复杂。想想千年虫,悬而未决的 2038 年问题,时区造成的混乱。一团糟。在处理时间序列数据时,我们自然会遇到日期。Pandas 可以创建日期范围,对时间序列数据重新采样,并执行日期算术运算。

创建从 1900 年 1 月 1 日开始并持续 42 天的日期范围,如下所示:

print("Date range", pd.date_range('1/1/1900', periods=42, freq='D')) 

一月不到 42 天,所以结束日期在二月,因为你可以自己检查:

Date range <class 'pandas.tseries.index.DatetimeIndex'>
[1900-01-01, ..., 1900-02-11]
Length: 42, Freq: D, Timezone: None

Pandas 官方文档中的下表描述了 Pandas 使用的频率:

|

分类代码

|

描述

|
| --- | --- |
| B | 工作日频率 |
| C | 自定义工作日频率(实验性) |
| D | 日历日频率 |
| W | 每周频率 |
| M | 月末频率 |
| BM | 营业月末频率 |
| MS | 月份开始频率 |
| BMS | 营业月开始频率 |
| Q | 四分之一结束频率 |
| BQ | 业务季度结束频率 |
| QS | 四分之一开始频率 |
| BQS | 业务季度开始频率 |
| A | 年终频率 |
| BA | 营业年度结束频率 |
| AS | 年度开始频率 |
| BAS | 业务年度开始频率 |
| H | 每小时频率 |
| T | 微小频率 |
| S | 其次是频率 |
| L | 毫秒 |
| U | 微秒 |

Pandas 的日期范围是有限制的。Pandas 中的时间戳(基于 NumPy datetime64数据类型)由一个 64 位整数表示,分辨率为纳秒(十亿分之一秒)。这将合法时间戳限制在大约介于 1677 年和 2262 年之间的日期范围内(并非这些年中的所有日期都有效)。这个范围的确切中点是 1970 年 1 月 1 日。例如,1677 年 1 月 1 日不能用 Pandas 时间戳定义,而 1677 年 9 月 30 日可以,如以下代码片段所示:

try: 
   print("Date range", pd.date_range('1/1/1677', periods=4, freq='D')) 
except: 
   etype, value, _ = sys.exc_info() 
   print("Error encountered", etype, value) 

该代码片段打印以下错误消息:

Date range Error encountered <class 'pandas.tslib.OutOfBoundsDatetime'> Out of bounds nanosecond timestamp: 1677-01-01 00:00:00

给定所有先前的信息,用 PandasDateOffset计算允许的日期范围如下:

offset = DateOffset(seconds=2 ** 33/10 ** 9) 
mid = pd.to_datetime('1/1/1970') 
print("Start valid range", mid - offset) 
print("End valid range", mid + offset') 

我们得到以下范围值:

Start valid range 1969-12-31 23:59:51.410065 
End valid range 1970-01-01 00:00:08.589935

我们可以把字符串列表转换成 Pandas 的日期。当然,不是所有的字符串都可以转换。如果 Pandas 无法转换字符串,通常会报告一个错误。有时,由于不同地区定义日期的方式不同,可能会出现歧义。在这种情况下,请使用格式字符串,如下所示:

print("With format", pd.to_datetime(['19021112', '19031230'], format='%Y%m%d')) 

字符串应该在不发生错误的情况下进行转换:

With format [datetime.datetime(1902, 11, 12, 0, 0) datetime.datetime(1903, 12, 30, 0, 0)]

如果我们尝试转换一个字符串(显然不是日期),默认情况下该字符串不会被转换:

print("Illegal date", pd.to_datetime(['1902-11-12', 'not a date'])) 

不应转换列表中的第二个字符串:

Illegal date ['1902-11-12' 'not a date']

要强制转换,请将coerce参数设置为True:

print("Illegal date coerced", pd.to_datetime(['1902-11-12', 'not a date'], errors='coerce')) 

显然,第二个字符串仍然不能转换为日期,所以我们能给它的唯一有效值是NaT(“不是时间”):

Illegal date coerced <class 'pandas.tseries.index.DatetimeIndex'>
[1902-11-12, NaT]Length: 2, Freq: None, Timezone: None

这个例子的代码在本书的代码包ch-03.ipynb中。

数据透视表

Excel 中使用的透视表汇总数据。到目前为止,我们在本章中看到的 CSV 文件中的数据都在平面文件中。数据透视表从平面文件中为某些列和行聚合数据。聚合操作可以是求和、求平均值、标准差等。我们将从ch-03.ipynb开始重用数据生成代码。Pandas 应用编程接口有一个顶级的pivot_table()函数和一个相应的数据框架方法。使用aggfunc参数,我们可以指定聚合函数,比如说,使用 NumPy sum()函数。cols参数告诉 Pandas 要聚合的列。在Food列上创建一个透视表,如下所示:

print(pd.pivot_table(df, cols=['Food'], aggfunc=np.sum)) 

我们得到的数据透视表包含每种食物的总数:

Food    chocolate   icecream      soup
Number   8.000000  15.000000  19.00000
Price    5.986585  10.440071  13.83338

[2 rows x 3 columns]

前面的代码可以在本书的代码包ch-03.ipynb中找到。

总结

在这一章中,我们重点介绍了 Pandas——一个 Python 数据分析库。这是一个关于 Pandas 的基本特征和数据结构的入门教程。我们看到了 Pandas 的大量功能是如何模仿关系数据库表的,允许我们高效地查询、聚合和操作数据。NumPy 和 Pandas 很好地合作,使得进行基本的统计分析成为可能。在这一点上,你可能会想 Pandas 是我们进行数据分析所需要的一切。然而,数据分析不仅仅是表面现象。

已经掌握了基础知识,现在是时候使用第 4 章统计和线性代数中的常用函数进行数据分析了。这包括使用主要的统计和数字函数。

鼓励读者阅读参考资料部分提到的书籍,以更详细和更深入地探索 Pandas。

参考文献

  1. 泰德·佩特罗Pandas 食谱帕克特出版**2017
  2. F .安东尼掌握 Pandas帕克特出版**2015
  3. M. Heydt掌握 Pandas 财经,T4】Packt 出版, 2015
  4. t . haukPandas 数据密集型应用-操作指南Packt Publishing2013
  5. m . Heydt学习 PandasPackt 出版**2015

四、统计学和线性代数

统计学和线性代数为探索性数据分析奠定了基础。两种主要的统计方法,描述性的和推断性的,都有助于从原始数据中获得见解和做出推断。例如,我们可以计算出一个变量的数据有一定的算术平均值和标准偏差。从这些数字中,我们可以推断出这个变量的范围和期望值。然后,我们可以运行统计测试来检查我们得出正确结论的可能性有多大。

线性代数关注的是线性方程组。使用linalg包,用 NumPy 和 SciPy 很容易解决这些问题。例如,线性代数对于将数据拟合到模型中是有用的。我们将在本章中介绍用于随机数生成和屏蔽数组的其他 NumPy 和 SciPy 包。

在本章中,我们将涵盖以下主题:

  • NumPy 的基本描述性统计
  • 带有 NumPy 的 Linera 代数
  • 用 NumPy 求特征值和特征向量
  • 随机数
  • 创建数字掩码数组

用 NumPy 进行基本描述性统计

在本书中,我们将尝试使用尽可能多的不同数据集。这取决于数据的可用性。不幸的是,这意味着数据的主题可能与你的兴趣不完全匹配。每个数据集都有自己的怪癖,但是你在本书中获得的一般技能应该转移到你自己的领域。在本章中,我们将把statsmodels库中的数据集加载到 NumPy 数组中,以便对数据进行分析。

莫纳罗亚公司 2 测量是我们将从statsmodels数据集包中使用的第一个数据集。以下代码加载数据集并打印基本的描述性统计值:

import numpy as np
from scipy.stats import scoreatpercentile
import pandas as pd

data = pd.read_csv("co2.csv", index_col=0, parse_dates=True) 
co2 = np.array(data.co2) 

print("The statistical values for amounts of co2 in atmosphere: \n") 
print("Max method", co2.max()) 
print("Max function", np.max(co2)) 

print("Min method", co2.min()) 
print("Min function", np.min(co2)) 

print("Mean method", co2.mean()) 
print("Mean function", np.mean(co2)) 

print("Std method", co2.std()) 
print("Std function", np.std(co2)) 

print("Median", np.median(co2)) 
print("Score at percentile 50", scoreatpercentile(co2, 50))

前面的代码计算 NumPy 数组的平均值、中值、最大值、最小值和标准偏差。

如果这些术语听起来不熟悉,请花些时间从维基百科或任何其他来源了解它们。如前言所述,我们将假设您熟悉基本的数学和统计概念。

数据来自statsmodels包,包含美国夏威夷莫纳罗亚天文台的大气co2

现在,让我们浏览一下代码:

  1. 首先,加载 Python 包模块的常用导入语句如下:

            import numpy as np 
            from scipy.stats import scoreatpercentile 
            import pandas as pd 
    
    
  2. Next, we will load the data with the following lines of code:

            data = pd.read_csv("co2.csv", index_col=0, parse_dates=True) 
            co2 = np.array(data.co2) 
    
    

    前面代码中的数据被复制到 NumPy 数组中,co2,包含大气中co2的值。

  3. The maximum of an array can be obtained via a method of the ndarray and NumPy functions. The same goes for the minimum, mean, and standard deviations. The following code snippet prints the various statistics:

            print("Max method", co2.max())
            print("Max function", np.max(co2))
    
            print("Min method", co2.min())
            print("Min function", np.min(co2))
    
            print("Mean method", co2.mean())
            print("Mean function", np.mean(co2))
    
            print("Std method", co2.std())
            print("Std function", np.std(co2))
    
    

    输出如下:

               Max method 366.84 
               Max function 366.84 
               Min method 313.18 
               Min function 313.18 
               Mean method 337.053525641 
               Mean function 337.053525641
               Std method 14.9502216262
               Std function 14.9502216262
    
  4. The median can be retrieved with a NumPy or SciPy function, which can estimate the 50th percentile of the data with the following lines:

            print("Median", np.median(co2))
            print("Score at percentile 50",scoreatpercentile(co2, 50)) 
    
    

    将打印以下内容:

               Median 335.17 
               Score at percentile 50 335.17
    

带 NumPy 的线性代数

线性代数是数学的一个重要分支。例如,我们可以使用线性代数来执行线性回归。numpy.linalg子包保存线性代数例程。有了这个子包,您可以反转矩阵、计算特征值、求解线性方程、寻找行列式等等。NumPy 中的矩阵由ndarray的子类表示。

用 NumPy 求矩阵的逆

线性代数中平方可逆矩阵A的逆是矩阵A<sup>-1</sup>,当与原矩阵相乘时,等于单位矩阵I。这可以写成下面的数学方程:

A A-1 = I 

numpy.linalg子包中的inv()功能可以为我们做到这一点。让我们反转一个示例矩阵。要反转矩阵,请执行以下步骤:

  1. Create the demonstration matrix with the mat() function:

            A = np.mat("2 4 6;4 2 6;10 -4 18")
             print("A\n", A)
    

    A矩阵打印如下:

              A[[ 2  4  6] [ 4  2  6] [10 -4 18]]]
    
  2. Invert the matrix.

    现在我们可以查看正在运行的inv()子程序:

              inverse = np.linalg.inv(A)
               print("inverse of A\n", inverse)
    

    逆矩阵显示如下:

              inverse of A[
              [-0.41666667 0.66666667 -0.08333333] 
              [ 0.08333333 0.16666667 -0.08333333] 
              [ 0.25 -0.33333333 0.08333333]]
    

    如果矩阵是奇异的,或者不是正方形的,则会出现一个LinAlgError。如果您愿意,您可以手动检查解决方案。这是留给你的一个练习,在这个练习之外。pinv() NumPy 函数执行伪求逆,可以应用于任何矩阵,包括非正方形的矩阵。

  3. Check by multiplication.

    让我们检查一下当我们将原始矩阵乘以inv()函数的结果时,我们得到了什么:

              print("Check\n", A * inverse) 
    
    

    结果是身份矩阵,正如预期的那样(忽略小的差异):

              Check
              [[  1.00000000e+00   0.00000000e+00  -5.55111512e-17] 
              [ -2.22044605e-16   1.00000000e+00  -5.55111512e-17] 
              [ -8.88178420e-16   8.88178420e-16   1.00000000e+00]]
    

    通过从前面的结果中减去 3×3 单位矩阵,我们得到反演过程的误差:

    print("Error\n", A * inverse - np.eye(3)) 
    
    

    一般来说,误差应该可以忽略不计,但在某些情况下,小误差可能会传播并带来不良副作用:

    [[ -1.11022302e-16   0.00000000e+00  -5.55111512e-17] 
     [ -2.22044605e-16   4.44089210e-16  -5.55111512e-17] 
     [ -8.88178420e-16   8.88178420e-16  -1.11022302e-16]]
    

    在这种情况下,更高精度的数据类型可能会有所帮助或切换到更好的算法。我们使用numpy.linalg子包的inv()例程计算矩阵的逆矩阵。我们用矩阵乘法确定,这是否真的是逆矩阵:

    import numpy as np 
    
    A = np.mat("2 4 6;4 2 6;10 -4 18") 
    print "A\n", A 
    
    inverse = np.linalg.inv(A) 
    print("inverse of A\n", inverse)
    
    print("Check\n", A * inverse)
    print("Error\n", A * inverse - np.eye(3))
    
    

用 NumPy 求解线性系统

矩阵以线性方式将一个向量转换成另一个向量。这个操作在数值上对应于一个线性方程组。numpy.linalgsolve()子程序求解形式为Ax = b的线性方程组;这里,A是矩阵,b可以是一维或二维数组,x是未知量。我们将见证dot()子程序的运行。这个函数计算两个浮点数数组的点积。

让我们解决一个线性系统的例子。要求解线性系统,请执行以下步骤:

  1. Create matrix A and array b.

    下面的代码将创建矩阵A和数组b:

              A = np.mat("1 -2 1;0 2 -8;-4 5 9")
               print("A\n", A)
               b = np.array([0, 8, -9])
               print("b\n", b)
    

    矩阵A和数组(向量)b定义如下:

    Solving linear systems with NumPy

  2. 调用solve()功能。

  3. Solve this linear system with the solve() function:

              x = np.linalg.solve(A, b)
               print("Solution", x)
    

    线性系统的解如下:

              Solution [ 29\.  16\.   3.]
    
  4. Check with the dot() function.

    使用dot()功能检查解决方案是否正确:

              print("Check\n", np.dot(A , x)) 
    
    

    结果是意料之中的:

              Check[[ 0\.  8\. -9.]]
    

我们通过应用 NumPy 的linalg子包中的solve()函数并使用dot()函数检查结果来求解线性系统:

import numpy as np 

A = np.mat("1 -2 1;0 2 -8;-4 5 9") 
print("A\n", A)

b = np.array([0, 8, -9]) 
print("b\n", b) 

x = np.linalg.solve(A, b) 
print("Solution", x)

print("Check\n", np.dot(A , x)) 

用 NumPy 求特征值和特征向量

eigenvalues是方程Ax = ax的标量解,其中A是二维矩阵,x是一维向量。eigenvectors是对应于eigenvalues的向量。

eigenvalueseigenvectors是数学中的基础,用于许多重要的算法,如主成分分析 ( 主成分分析)。主成分分析可用于简化大型数据集的分析。

numpy.linalg包中的eigvals()子程序计算eigenvalueseig()函数返回一个包含eigenvalueseigenvectors的元组。

我们将获得具有numpy.linalg子包的eigvals()eig()功能的矩阵的eigenvalueseigenvectors。我们将通过应用dot()功能来检查结果:

import numpy as np 

A = np.mat("3 -2;1 0") 
print("A\n", A) 

print("Eigenvalues", np.linalg.eigvals(A)) 

eigenvalues, eigenvectors = np.linalg.eig(A) 
print("First tuple of eig", eigenvalues) 
print("Second tuple of eig\n", eigenvectors) 

for i in range(len(eigenvalues)): 
   print("Left", np.dot(A, eigenvectors[:,i])) 
   print("Right", eigenvalues[i] * eigenvectors[:,i]) 

让我们计算一个矩阵的eigenvalues:

  1. Create the matrix.

    以下代码将创建一个矩阵:

              A = np.mat("3 -2;1 0")
               print("A\n", A)
    

    我们创建的矩阵如下所示:

              A[[ 3 -2] [ 1  0]]
    
  2. Calculate eigenvalues with the eig() function.

    应用eig()子程序:

              print("Eigenvalues", np.linalg.eigvals(A)) 
    
    

    矩阵的eigenvalues如下:

              Eigenvalues [ 2\.  1.]
    
  3. Get eigenvalues and eigenvectors with eig().

    使用eig()功能获取eigenvalueseigenvectors。该例程返回一个元组,其中第一个元素包含eigenvalues,第二个元素包含匹配的eigenvectors,按列设置:

              eigenvalues, eigenvectors = np.linalg.eig(A)
               print("First tuple of eig", eigenvalues)
               print("Second tuple of eig\n", eigenvectors)
    

    eigenvalueseigenvectors值如下:

              First tuple of eig [ 2\. 1.]
              Second tuple of eig
              [[ 0.89442719 0.70710678] 
              [ 0.4472136 0.70710678]]
    
  4. Check the result.

    通过计算eigenvalues方程Ax = ax的两边,用dot()函数检查答案:

              for i in range(len(eigenvalues)):
               print("Left", np.dot(A, eigenvectors[:,i]))
               print("Right", eigenvalues[i] * eigenvectors[:,i])
    

    输出如下:

              Left [[ 1.78885438] [ 0.89442719]]
              Right [[ 1.78885438] [ 0.89442719]]
              Left [[ 0.70710678] [ 0.70710678]]
              Right [[ 0.70710678] [ 0.70710678]]
    

NumPy 随机数

随机数用于蒙特卡罗方法、随机微积分等。实随机数很难产生,所以在实践中,我们使用伪随机数。伪随机数对于大多数意图和目的来说都是足够随机的,除了一些非常特殊的情况,例如非常精确的模拟。随机数关联例程可以位于 NumPy random子包中。

核心随机数发生器基于默森扭转算法(参考https://en.wikipedia.org/wiki/Mersenne_twister)。

随机数可以由离散或连续分布产生。分布函数有一个可选的size参数,它通知 NumPy 要创建多少个数字。您可以指定整数或元组作为大小。这将导致用随机数填充适当形状的数组。离散分布包括几何分布、超几何分布和二项式分布。连续分布包括正态分布和对数正态分布。

二项分布的赌博

二项式分布对一个实验的整数次独立运行的成功次数进行建模,其中每个实验的成功几率是一个设定值。

设想一个 17 世纪的赌场,在那里你可以赌掷 8 块钱。在一个受欢迎的游戏中,九枚硬币被抛了出来。如果少于五个硬币是头像,那么你就输了一块八;否则,你赚一个。让我们模拟一下,从我们拥有的一千枚硬币开始。为此,我们将使用random模块中的binomial()功能:

如果你想跟着代码走,看看本书代码包里的ch-04.ipynb

import numpy as np 
from matplotlib.pyplot import plot, show 

cash = np.zeros(10000) 
cash[0] = 1000 
outcome = np.random.binomial(9, 0.5, size=len(cash)) 

for i in range(1, len(cash)): 

   if outcome[i] < 5: 
      cash[i] = cash[i - 1] - 1 
   elif outcome[i] < 10: 
      cash[i] = cash[i - 1] + 1 
   else: 
      raise AssertionError("Unexpected outcome " + outcome) 

print(outcome.min(), outcome.max()) 

plot(np.arange(len(cash)), cash) 
show() 

为了理解binomial()功能,请看以下步骤:

  1. Calling the binomial() function.

    将充当现金余额的数组初始化为零。调用10000大小的binomial()函数。这代表我们赌场的 10,000 次抛硬币:

              cash = np.zeros(10000) 
              cash[0] = 1000 
              outcome = np.random.binomial(9, 0.5, size=len(cash)) 
    
    
  2. Updating the cash balance.

    查看投币结果,更新cash数组。显示outcome数组的最高和最低值,只是为了确保我们没有任何异常的异常值:

              for i in range(1, len(cash)):
               if outcome[i] < 5:
               cash[i] = cash[i - 1] - 1
               elif outcome[i] < 10:
               cash[i] = cash[i - 1] + 1
               else:
               raise AssertionError("Unexpected outcome " + outcome)
               print(outcome.min(), outcome.max())
    

    不出所料,数值介于09之间:

               0 9
    
  3. 接下来,用 matplotlib 绘制现金数组:

            plot(np.arange(len(cash)), cash) 
            show() 
    
    

在下面的图中,您可以确定我们的现金余额执行随机游走(不遵循模式的随机移动):

Gambling with the binomial distribution

当然,每次执行代码,我们都会有不同的随机游走。如果您希望总是收到相同的结果,您将希望从 NumPy random子包中向binomial()函数传递一个种子值。

正态分布采样

连续分布由概率密度函数建模。指定间隔的机会是通过对 PDF 的整合找到的。NumPy random模块有许多代表连续分布的功能,如betachisquareexponentialfgammagumbellaplacelognormallogisticmultivariate_normalnoncentral_chisquarenoncentral_fnormal等。

我们将通过应用random NumPy 子包中的normal()函数来可视化正态分布。我们将通过绘制钟形曲线和随机生成值的直方图来实现这一点:

import numpy as np 
import matplotlib.pyplot as plt 

N=10000 

normal_values = np.random.normal(size=N) 
dummy, bins, dummy = plt.hist(normal_values, np.sqrt(N), normed=True, lw=1) 
sigma = 1 
mu = 0 
plt.plot(bins, 1/(sigma * np.sqrt(2 * np.pi)) * np.exp( - (bins - mu)**2 / (2 * sigma**2) ),lw=2) 
plt.show() 

随机数可以由正态分布产生,它们的分布可以用直方图显示。要绘制正态分布,请执行以下步骤:

  1. Generate values.

    借助random NumPy 子包中的normal()功能,为特定样本量创建随机数:

              N=100.00 
              normal_values = np.random.normal(size=N) 
    
    
  2. Draw the histogram and theoretical PDF.

    绘制直方图和理论 PDF,中心值为0,标准偏差为1。我们将为此使用 matplotlib:

              dummy, bins, dummy = plt.hist(normal_values, np.sqrt(N),         
              normed=True, lw=1)
              sigma = 1
              mu = 0
              plt.plot(bins, 1/(sigma * np.sqrt(2 * np.pi)) * np.exp( - 
              (bins - mu)**2 / (2 * sigma**2) ),lw=2)
              plt.show()
    

    在下面的图中,我们看到了著名的钟形曲线:

    Sampling the normal distribution

用 SciPy 进行正态性测试

正态分布广泛应用于科学和统计中。根据中心极限定理,具有独立观测值的大的随机样本将向正态分布收敛。正态分布的性质是众所周知的,并且被认为使用方便。然而,需要满足许多要求,例如必须独立的足够多的数据点。检查数据是否符合正态分布是很好的做法。存在大量的正态性测试,其中一些已经在scipy.stats包中实现。我们将在本节中应用这些测试。作为样本数据,我们将使用来自https://www.google.org/flutrends/data.txt的流感趋势数据。原始文件已缩减为仅包含两列-阿根廷的日期和值。几行如下:

Date,Argentina
29/12/02,
05/01/03,
12/01/03,
19/01/03,
26/01/03,
02/02/03,136

数据可以在代码包的goog_flutrends.csv文件中找到。我们还将从正态分布中采样数据,就像我们在前面的教程中所做的那样。由此产生的数组将具有与流感趋势数组相同的大小,并将作为黄金标准,它应该以优异的成绩通过常态测试。以下代码参见代码包中的ch-04.ipynb:

import numpy as np 
from scipy.stats import shapiro 
from scipy.stats import anderson 
from scipy.stats import normaltest 

flutrends = np.loadtxt("goog_flutrends.csv", delimiter=',', usecols=(1,), skiprows=1, converters = {1: lambda s: float(s or 0)}, unpack=True) 
N = len(flutrends) 
normal_values = np.random.normal(size=N) 

print("Normal Values Shapiro", shapiro(normal_values)) 
print("Flu Shapiro", shapiro(flutrends)) 

print("Normal Values Anderson", anderson(normal_values)) 
print("Flu Anderson", anderson(flutrends)) 

print("Normal Values normaltest", normaltest(normal_values)) 
print("Flu normaltest", normaltest(flutrends)) 

作为一个反面例子,我们将使用一个与前面提到的两个用零填充的数组大小相同的数组。在现实生活中,如果我们正在处理一个罕见的事件(例如,疫情疫情),我们可以得到这些类型的价值。

在数据文件中,有些单元格是空的。当然,这类问题经常发生,因此我们必须习惯于清理数据。我们将假设正确的值应该是0。转换器可以为我们填写这些零值,如下所示:

flutrends = np.loadtxt("goog_flutrends.csv", delimiter=',', usecols=(1,), skiprows=1, converters = {1: lambda s: float(s or 0)}, unpack=True) 

夏皮罗-维尔克检验可以检验是否正常。对应的 SciPy 函数返回一个元组,其中第一个数字是测试统计量,第二个数字是 p 值。应该注意的是,用零填充的数组会引起警告。事实上,这个例子中使用的三个函数都有这个数组的问题,并给出了警告。我们得到以下结果:

Normal Values Shapiro (0.9967482686042786, 0.2774980068206787)
Flu Shapiro (0.9351990818977356, 2.2945883254311397e-15)

我们得到的 p 值类似于本例后面第三次测试的结果。分析基本一致。

安德森-达林检验可以检验正态性,也可以检验其他分布,如指数分布、逻辑分布和甘贝尔分布。SciPy 函数将测试统计与包含 15、10、5、2.5 和 1%显著性水平的临界值的数组相关联。如果统计量在显著性水平上大于临界值,我们可以拒绝正态性。我们得到以下值:

Normal Values Anderson (0.31201465602225653, array([ 0.572,  0.652,  0.782,  0.912,  1.085]), array([ 15\. ,  10\. ,   5\. ,   2.5,   1\. ]))
Flu Anderson (8.258614154768793, array([ 0.572,  0.652,  0.782,  0.912,  1.085]), array([ 15\. ,  10\. ,   5\. ,   2.5,   1\. ]))

正如我们所预料的那样,我们不能拒绝我们的黄金标准数组的常态。然而,为流感趋势数据返回的统计数据大于所有相应的临界值。因此,我们可以自信地拒绝常态。在三个测试函数中,这个似乎是最容易使用的。

达戈斯蒂诺-皮尔森测试也作为normaltest()函数在 SciPy 中实现。这个函数返回一个带有统计和 p 值的元组,就像shapiro()函数一样。p 值是双边卡方概率。卡方是另一个众所周知的分布。测试本身基于偏斜度和峰度测试的 z 分数。偏斜度衡量分布的对称性。正态分布是对称的,偏斜度为零。峰度告诉我们一些关于分布的形状(高峰,肥尾)。正态分布的峰度为 3(过度峰度为零)。通过测试获得以下值:

Normal Values normaltest (3.102791866779639, 0.21195189649335339)
Flu normaltest (99.643733363569538, 2.3048264115368721e-22)

因为我们处理的是 p 值的概率,所以我们希望这个概率尽可能高,并且接近 1。此外,如果 p 值至少为 0.5,我们可以接受正态性。对于黄金标准数组,我们得到一个较低的值,这意味着我们可能需要有更多的观察。留给你来证实这一点。

创建 NumPy 屏蔽数组

数据通常很混乱,包含我们不常处理的空白或字符。屏蔽数组可用于忽略缺失或无效的数据点。来自numpy.ma子包的屏蔽数组是带有屏蔽的ndarray的子类。在本节中,我们将使用face照片作为数据源,并表现得好像其中一些数据已损坏。以下是本书代码包中ch-04.ipynb文件中屏蔽数组示例的完整代码:

import numpy 
import scipy 
import matplotlib.pyplot as plt 

face = scipy.misc.face() 

random_mask = numpy.random.randint(0, 2, size=face.shape) 

plt.subplot(221) 
plt.title("Original") 
plt.imshow(face) 
plt.axis('off') 

masked_array = numpy.ma.array(face, mask=random_mask) 

plt.subplot(222) 
plt.title("Masked") 
plt.imshow(masked_array) 
plt.axis('off') 

plt.subplot(223) 
plt.title("Log") 
plt.imshow(numpy.ma.log(face).astype("float32")) 
plt.axis('off') 

plt.subplot(224) 
plt.title("Log Masked") 
plt.imshow(numpy.ma.log(masked_array).astype("float32")) 
plt.axis('off') 

plt.show() 

最后,我们将显示原始图片、原始图像的对数值、屏蔽数组及其对数值:

  1. Create a mask.

    为了产生屏蔽数组,我们必须规定一个屏蔽。让我们创建一个随机掩码。该掩码的值为01:

              random_mask = numpy.random.randint(0, 2, size=face.shape) 
    
    
  2. Create a masked array.

    通过应用前一步中的掩码,创建一个掩码数组:

              masked_array = numpy.ma.array(face, mask=random_mask) 
    
    

    结果图片展示如下:

    Creating a NumPy masked array

我们对 NumPy 数组应用了随机掩码。这导致忽略与掩码匹配的数据。在numpy.ma子包中可以发现整个范围的屏蔽数组过程。在本教程中,我们只介绍了如何生成屏蔽数组。

忽略负值和极值

当我们想要忽略负值时,屏蔽数组很有用;例如,当取数组值的对数时。屏蔽数组的第二个用例是拒绝异常值。这基于极值的上限和下限。在本教程中,我们将把这些技术应用到 MLB 球员的工资数据中。数据来源于http://www.exploredata.net/Downloads/Baseball-Data-Set。数据被编辑为包含两列-球员姓名和工资。这导致了MLB2008.csv,可以在代码包中找到。本教程的完整脚本在本书代码包的ch-04.ipynb文件中:

import numpy as np 
from datetime import date 
import sys 
import matplotlib.pyplot as plt 

salary = np.loadtxt("MLB2008.csv", delimiter=',', usecols=(1,), skiprows=1, unpack=True) 
triples = np.arange(0, len(salary), 3) 
print("Triples", triples[:10], "...") 

signs = np.ones(len(salary)) 
print("Signs", signs[:10], "...") 

signs[triples] = -1 
print("Signs", signs[:10], "...") 

ma_log = np.ma.log(salary * signs) 
print("Masked logs", ma_log[:10], "...") 

dev = salary.std() 
avg = salary.mean() 
inside = np.ma.masked_outside(salary, avg - dev, avg + dev) 
print("Inside", inside[:10], "...") 

plt.subplot(311) 
plt.title("Original") 
plt.plot(salary) 

plt.subplot(312) 
plt.title("Log Masked") 
plt.plot(np.exp(ma_log)) 

plt.subplot(313) 
plt.title("Not Extreme") 
plt.plot(inside) 

plt.subplots_adjust(hspace=.9) 

plt.show() 

以下是将帮助您执行上述命令的步骤:

  1. Taking the logarithm of negative numbers.

    我们将取包含负数的数组的对数。首先,让我们创建一个可以被 3 整除的数组:

              triples = numpy.arange(0, len(salary), 3)
               print("Triples", triples[:10], "...")
    

    接下来,我们将生成一个数组,其大小与薪资数据数组相同:

              signs = numpy.ones(len(salary)) 
              print("Signs", signs[:10], "...") 
    
    

    借助我们在第 2 章NumPy 数组中获得的索引技巧,我们将每个第三数组元素设置为负:

              signs[triples] = -1 
              print("Signs", signs[:10], "...") 
    
    

    总之,我们将取这个数组的对数:

              ma_log = numpy.ma.log(salary * signs) 
              print("Masked logs", ma_log[:10], "...") 
    
    

    这应该打印以下工资数据(注意-代表数据中的 NaN 值):

              Triples [ 0 3 6 9 12 15 18 21 24 27] ...
              Signs [ 1\. 1\. 1\. 1\. 1\. 1\. 1\. 1\. 1\. 1.] ...
              Signs [-1\. 1\. 1\. -1\. 1\. 1\. -1\. 1\. 1\. -1.] ...
              Masked logs [-- 14.970818190308929 15.830413578506539 --           
              13.458835614025542 15.319587954740548 -- 15.648092021712584 
              13.864300722133706 --] ...
    
  2. Ignoring extreme values.

    让我们将异常值指定为“比平均值低一个标准差”或“比平均值高一个标准差”(这不一定是这里给出的正确定义,但很容易计算)。该定义指导我们编写以下代码,这些代码将掩盖极端点:

              dev = salary.std()
              avg = salary.mean()
              inside = numpy.ma.masked_outside(salary, avg-dev, avg+dev)
              print("Inside", inside[:10], "...")
    

    以下代码显示了前十个元素的输出:

              Inside [3750000.0 3175000.0 7500000.0 3000000.0 700000.0 
              4500000.0 3000000.0 6250000.0 1050000.0 4600000.0] ...
    

    让我们绘制原始工资数据,再次取对数和指数后的数据,最后是应用基于标准差的掩码后的数据。

它看起来像这样:

Disregarding negative and extreme values

numpy.ma子包中的函数屏蔽数组元素,我们认为这是无效的。例如,log()sqrt()功能不允许负值。屏蔽值就像关系数据库和编程中的空值。具有屏蔽值的所有操作都会传递一个屏蔽值。

总结

在本章中,您学习了很多关于 NumPy 和 SciPy 子包的知识。我们研究了线性代数、统计学、连续和离散分布、屏蔽数组和随机数。

下一章第五章检索、处理和存储数据,将教会我们一些至关重要的技能,尽管有些人可能不认为它们是数据分析。我们将使用一个更广泛的定义,考虑任何可以想象的与数据分析相关的东西。通常,当我们分析数据时,我们没有一个完整的助手团队来帮助我们检索和存储数据。然而,由于这些任务对于平稳的数据分析流程很重要,我们将详细描述这些活动。

五、检索、处理和存储数据

数据随处可见,有各种形状和形式。我们可以通过网络、电子邮件和文件传输协议获得它,或者我们可以自己在实验室实验或市场调查中创建它。全面概述如何获取各种格式的数据将需要比我们现有的更多的页面。有时,我们需要在分析数据之前或完成分析之后存储数据。我们将在本章中讨论存储数据。第 8 章使用数据库,给出了关于各种数据库(关系数据库和 NoSQL 数据库)和相关应用编程接口的信息。以下是我们将在本章中讨论的主题列表:

  • 用 NumPy 和 Pandas 编写 CSV 文件
  • 二进制.npy和泡菜格式
  • 用 PyTables 存储数据
  • 向 HDF5 商店读写 Pandas 数据帧
  • 和 Pandas 一起读写 Excel
  • 使用 REST 网络服务和 JSON
  • 和 Pandas 一起读写 JSON
  • 解析 RSS 和 Atom 提要
  • 用美丽的汤解析 HTML

用 NumPy 和 Pandas 编写 CSV 文件

在前面的章节中,我们学习了阅读 CSV 文件。编写 CSV 文件同样简单,但是使用不同的功能和方法。让我们首先生成一些以 CSV 格式存储的数据。在下面的代码片段中植入随机生成器后,生成一个 3x4 NumPy 数组。

将其中一个数组值设置为nan:

np.random.seed(42) 

a = np.random.randn(3, 4) 
a[2][2] = np.nan 
print(a) 

该代码将按如下方式打印数组:

[[ 0.49671415 -0.1382643   0.64768854  1.52302986]
 [-0.23415337 -0.23413696  1.57921282  0.76743473]
 [-0.46947439  0.54256004         nan -0.46572975]]

NumPy savetxt()函数是 NumPy loadtxt()函数的对应物,可以以分隔文件格式保存数组,例如 CSV。使用以下函数调用保存我们创建的数组:

np.savetxt('np.csv', a, fmt='%.2f', delimiter=',', header=" #1,  #2,  #3,  #4") 

在前面的函数调用中,我们指定了要保存的文件名、数组、可选格式、分隔符(默认为空格)和可选标题。

格式参数记录在http://docs . python . org/2/library/string . html # format-specification-mini-language

查看我们使用cat命令(cat np.csv)或编辑器(如 Windows 中的记事本)创建的np.csv文件。文件的内容应显示如下:

    #  #1,  #2,  #3,  #4
    0.50,-0.14,0.65,1.52
    -0.23,-0.23,1.58,0.77
    -0.47,0.54,nan,-0.47

从随机值数组创建 Pandas 数据帧:

df = pd.DataFrame(a) 
print(df) 

如您所见,Pandas 会自动为我们的数据提供列名:

              0         1         2         3
    0  0.496714 -0.138264  0.647689  1.523030
    1 -0.234153 -0.234137  1.579213  0.767435
    2 -0.469474  0.542560NaN -0.465730

使用 Pandasto_csv()方法将数据帧写入 CSV 文件,如下所示:

df.to_csv('pd.csv', float_format='%.2f', na_rep="NAN!") 

我们给了这个方法文件名,一个类似于 NumPy savetxt()函数的格式参数的可选格式字符串,以及一个表示NaN的可选字符串。查看pd.csv文件可以看到以下内容:

    ,0,1,2,3
    0,0.50,-0.14,0.65,1.52
    1,-0.23,-0.23,1.58,0.77
    2,-0.47,0.54,NAN!,-0.47

看看本书代码包中ch-05.ipynb文件中的代码:

import numpy as np 
import pandas as pd 

np.random.seed(42) 

a = np.random.randn(3, 4) 
a[2][2] = np.nan 
print(a) 
np.savetxt('np.csv', a, fmt='%.2f', delimiter=',', header=" #1,  #2,  #3,  #4") 
df = pd.DataFrame(a) 
print(df) 
df.to_csv('pd.csv', float_format='%.2f', na_rep="NAN!") 

二进制。npy 和泡菜格式

大多数情况下,以 CSV 格式保存数据是可以的。交换 CSV 文件很容易,因为大多数编程语言和应用程序都可以处理这种格式。但是,效率不是很高;CSV 和其他明文格式占用大量空间。已经发明了许多提供高级压缩的文件格式,例如.zip.bzip.gzip

以下是本次存储对比练习的完整代码,也可以在本书的代码包ch-05.ipynb文件中找到:

import numpy as np 
import pandas as pd 
from tempfile import NamedTemporaryFile 
from os.path import getsize 

np.random.seed(42) 
a = np.random.randn(365, 4) 

tmpf = NamedTemporaryFile() 
np.savetxt(tmpf, a, delimiter=',') 
print("Size CSV file", getsize(tmpf.name)) 

tmpf = NamedTemporaryFile() 
np.save(tmpf, a) 
tmpf.seek(0) 
loaded = np.load(tmpf) 
print("Shape", loaded.shape) 
print("Size .npy file", getsize(tmpf.name)) 

df = pd.DataFrame(a) 
df.to_pickle(tmpf.name) 
print("Size pickled dataframe", getsize(tmpf.name)) 
print("DF from pickle\n", pd.read_pickle(tmpf.name)) 

NumPy 提供了一种特定于 NumPy 的格式.npy,可以用来存储 NumPy 数组。在演示这种格式之前,我们将生成一个充满随机值的 365x4 NumPy 数组。该数组模拟一年中四个变量的每日测量值(例如,带有测量温度、湿度、降水和大气压力的传感器的天气数据站)。我们将使用标准的 Python NamedTemporaryFile来存储数据。临时文件应该被自动删除。

将数组存储在 CSV 文件中,并按如下方式检查其大小:

tmpf = NamedTemporaryFile() 
np.savetxt(tmpf, a, delimiter=',') 
print("Size CSV file", getsize(tmpf.name)) 

CSV 文件大小打印如下:

 Size CSV file 36693

以 NumPy .npy格式保存数组,加载数组,检查其形状和.npy文件的大小:

tmpf = NamedTemporaryFile() 
np.save(tmpf, a) 
tmpf.seek(0) 
loaded = np.load(tmpf) 
print("Shape", loaded.shape) 
print("Size .npy file", getsize(tmpf.name)) 

需要调用seek()方法来模拟关闭和重新打开临时文件。形状应以文件大小打印:

Shape (365, 4)
Size .npy file 11760

.npy文件大致比 CSV 文件小三倍,果然不出所料。Python 允许我们存储实际上任意复杂的数据结构。我们也可以将 Pandas 数据框或系列存储为泡菜。

Python pickle 是一种用于将 Python 对象存储到磁盘或其他介质的格式。这叫腌制。我们可以从存储中重新创建 Python 对象。这个反向过程叫做拆线(参考http://docs.python.org/2/library/pickle.html)。腌制已经发展了多年,因此,各种腌制协议存在。不是所有 Python 对象都可以腌制;然而,也有替代的实现,比如 dill、,允许腌制更多类型的 Python 对象。如果可能,使用 cPickle(包含在标准 Python 发行版中),因为它是用 C 实现的,因此速度更快。

从生成的 NumPy 数组中创建一个 DataFrame,用to_pickle()方法将其写入 pickle,用read_pickle()函数从 pickle 中检索:

df = pd.DataFrame(a) 
df.to_pickle(tmpf.name) 
print("Size pickled dataframe", getsize(tmpf.name)) 
print("DF from pickle\n", pd.read_pickle(tmpf.name)) 

数据框的泡菜略大于.npy文件,如以下打印输出所示:

Size pickled dataframe 12244
DF from pickle
           0         1         2         3
0   0.496714 -0.138264  0.647689  1.523030
[TRUNCATED]
59 -2.025143  0.186454 -0.661786  0.852433
         ...       ...       ...       ...

[365 rows x 4 columns]

用 PyTables 存储数据

分层数据格式 ( HDF )是存储大数值数据的规范和技术。HDF 是在超级计算社区创建的,现在是一个开放标准。HDF 的最新版本是 HDF5 ,也是我们将要使用的版本。HDF5 以组和数据集的形式组织数据。数据集是多维齐次数组。组可以包含其他组或数据集。组就像分层文件系统中的目录。

两个主要的 HDF5 Python 库如下:

  • h5y
  • iptables(iptables)

在这个例子中,我们将使用 PyTables。PyTables 有许多依赖项:

  • 我们在第 1 章Python 库中安装的 NumPy 包
  • numexpr 包声称它计算多运算符数组表达式的速度比 NumPy 快很多倍
  • HDF5

HDF5 的并行版本也需要 MPI。可以通过从http://www.hdfgroup.org/HDF5/release/obtain5.html获得一个发行版并运行以下命令(可能需要几分钟)来安装 HDF5:

$ gunzip < hdf5-X.Y.Z.tar.gz | tar xf -
$ cd hdf5-X.Y.Z
$ make
$ make install

你最喜欢的包管理器很可能有一个 HDF5 的发行版。选择最新的稳定版本。在写这本书的时候,安装的版本是 1.8.12。

第二个依赖项 numexpr 声称能够比 NumPy 更快地执行某些操作。它支持多线程,并且有自己的用 c 实现的虚拟机,在 PyPi 上有 Numexpr 和 PyTables,所以我们可以用pip安装这些,如下所示:

$ pip3 install numexpr tables

同样,我们将生成随机值并用这些随机值填充 NumPy 数组。创建一个 HDF5 文件,并用以下代码将 NumPy 数组附加到根节点:

filename = "pytable_demo.h5" 
h5file = tables.openFile(filename, mode='w', ) 
root = h5file.root 
h5file.createArray(root, "array", a) 
h5file.close() 

读取 HDF5 文件并打印其文件大小:

h5file = tables.openFile(filename, "r") 
print(getsize(filename)) 

我们得到的文件大小值是13824。一旦我们读取了一个 HDF5 文件并获得了它的句柄,我们通常会遍历它来找到我们需要的数据。由于我们只有一个数据集,遍历非常简单。调用iterNodes()read()方法取回 NumPy 数组:

for node in h5file.iterNodes(h5file.root): 
   b = node.read() 
   print(type(b), b.shape) 

数据集的类型和形状符合我们的预期:

    <class 'numpy.ndarray'> (365, 4)

以下代码可以在本书代码包的ch-05.ipynb文件中找到:

import numpy as np 
import tables 
from os.path import getsize 

np.random.seed(42) 
a = np.random.randn(365, 4) 

filename = "pytable_demo.h5" 
h5file = tables.open_file( filename, mode='w', ) 
root = h5file.root 
h5file.create_array(root, "array", a) 
h5file.close() 

h5file = tables.open_file(filename, "r") 
print(getsize(filename)) 

for node in h5file.root: 
   b = node.read() 
   print(type(b), b.shape) 
 h5file.close() 

读写 Pandas 数据帧到 HDF5 商店

HDFStore类是负责处理 HDF5 数据的 Pandas 抽象。使用随机数据,我们将演示这个功能。

HDFStore构造函数一个演示文件的路径,并创建一个存储:

filename = "pytable_df_demo.h5"  
store = pd.io.pytables.HDFStore(filename) 
print(store) 

前面的代码片段将打印存储及其内容的文件路径,该路径目前为空:

    <class 'pandas.io.pytables.HDFStore'>
    File path: pytable_df_demo.h5
    Empty

HDFStore有一个类似字典的接口,这意味着我们可以存储值,比如 Pandas 数据帧和相应的查找键。将包含随机数据的数据帧存储在HDFStore中,如下所示:

store['df'] = df 
print(store) 

现在,该存储包含数据,如以下输出所示:

    <class 'pandas.io.pytables.HDFStore'>
    File path: pytable_df_demo.h5
                frame        (shape->[365,4])

我们可以通过三种方式访问数据框:使用get()方法、类似字典的查找或点状访问。让我们试试这个:

print("Get", store.get('df').shape) 
print("Lookup", store['df'].shape) 
print("Dotted", store.df.shape) 

数据框的形状对于所有三种访问方法都是相同的:

Get (365, 4)
Lookup (365, 4)
Dotted (365, 4)

我们可以通过调用remove()方法或使用del运算符来删除商店中的商品。显然,我们只能移除一个项目一次。从存储中删除数据帧:

del store['df'] 
print("After del\n", store) 

商店现在又空了:

After del
<class 'pandas.io.pytables.HDFStore'>
File path: pytable_df_demo.h5
Empty

is_open属性表示店铺是否开门。商店可以用close()方法关闭。关闭商店并检查其是否已关闭:

print("Before close", store.is_open) 
store.close() 
print("After close", store.is_open) 

一旦关闭,商店将不再营业,如下所述:

Before close True
After close False

Pandas 还提供了一个数据框架to_hdf()方法和一个顶层read_hdf()函数来读写 HDF 数据。调用to_hdf()方法并读取数据:

df.to_hdf(tmpf.name, 'data', format='table') 
print(pd.read_hdf(tmpf.name, 'data', where=['index>363'])) 

读写应用编程接口的参数是文件路径、存储中组的标识符和可选的格式字符串。格式可以是固定的,也可以是表格的。固定格式更快,但不能追加或搜索。表格格式对应于 PyTables Table结构,允许搜索和选择。我们在数据框上获得以下查询值:

                0         1         2         3
    364  0.753342  0.381158  1.289753  0.673181
    [1 rows x 4 columns]

本书代码包中的ch-05.ipynb文件包含以下代码:

import numpy as np 
import pandas as pd 

np.random.seed(42) 
a = np.random.randn(365, 4) 

filename = "pytable_df_demo.h5" 
store = pd.io.pytables.HDFStore(filename) 
print(store) 

df = pd.DataFrame(a) 
store['df'] = df 
print(store) 

print("Get", store.get('df').shape) 
print("Lookup", store['df'].shape) 
print( "Dotted", store.df.shape) 

del store['df'] 
print("After del\n", store) 

print("Before close", store.is_open) 
store.close() 
print("After close", store.is_open) 

df.to_hdf('test.h5', 'data', format='table') 
print(pd.read_hdf('test.h5', 'data', where=['index>363']))  

和 Pandas 一起读写 Excel

Excel 文件包含大量重要数据。当然,我们可以以其他更便携的格式导出这些数据,例如 CSV。但是用 Python 读写 Excel 文件更方便。正如在 Python 世界中常见的那样,目前有不止一个项目致力于提供 Excel 输入/输出功能的目标。我们将需要安装的模块,以获得 Excel 输入/输出工作与 Pandas 有点模糊的文件。原因是 Pandas 所依赖的项目是独立的,发展迅速。Pandas 包对它接受为 Excel 文件的文件很挑剔。这些文件必须有.xls.xlsx后缀,否则,我们会得到以下错误:

ValueError: No engine for filetype: '' 

这很容易解决。例如,如果我们创建一个临时文件,我们只需给它一个适当的后缀。如果您没有安装任何东西,您将收到以下错误消息:

ImportError: No module named openpyxl.workbook

以下命令通过安装openpyxl消除错误:

$ pip3 install openpyxl xlsxwriter xlrd

openpyxl模块是 PHPExcel 的一个端口,支持.xlsx文件的读写。

型式

如果由于某种原因pip install方法对您不起作用,您可以在http://pythonhosted.org/openpyxl/找到替代安装说明。

读取.xlsx文件也需要xlsxwriter模块。xlrd模块能够从.xls.xlsx文件中提取数据。

让我们生成随机值来填充 Pandas 数据框,从数据框创建一个 Excel 文件,从 Excel 文件重新创建数据框,并对其应用mean()方法。对于 Excel 文件的工作表,我们可以指定从零开始的索引或名称。

参考书籍代码包中的ch-05.ipynb文件,其中将包含以下代码:

import numpy as np 
import pandas as pd 

np.random.seed(42) 
a = np.random.randn(365, 4) 

filename="excel_demo.xlsx" 
df = pd.DataFrame(a) 
print(filename) 
df.to_excel(filename, sheet_name='Random Data') 
print("Means\n", pd.read_excel(filename, 'Random Data').mean())) 

使用to_excel()方法创建一个 Excel 文件:

df.to_excel(tmpf.name, sheet_name='Random Data') 

使用顶级read_excel()功能重新创建数据框:

print("Means\n", pd.read_excel(tmpf.name, 'Random Data').mean()) 

打印方式如下:

/var/folders/k_/xx_xz6xj0hx627654s3vld440000gn/T/tmpeBEfnO.xlsx
Means
0    0.037860
1    0.024483
2    0.059836
3    0.058417
dtype: float64

使用 REST web 服务和 JSON

具象 状态 转移 ( REST ) web 服务使用 REST 架构风格(更多信息请参考http://en . Wikipedia . org/wiki/具象 _state_transfer )。在 HTTP(S)协议的通常上下文中,我们有 GETPOSTPUTDELETE 方法。这些方法可以与数据上的常见操作保持一致,以创建、请求、更新或删除数据项。

在 RESTful API 中,数据项由 URIs 标识,如http://example.com/resourceshttp://example.com/resources/item42。REST 不是一个官方标准,但它如此广泛,我们需要了解它。Web 服务经常使用 JavaScript 对象符号 ( JSON )(更多信息参考http://en.wikipedia.org/wiki/JSON)来交换数据。在这种格式中,数据是使用 JavaScript 符号编写的。该符号类似于 Python 列表和字典的语法。在 JSON 中,我们可以定义由列表和字典的组合组成的任意复杂的数据。为了说明这一点,我们将使用一个非常简单的 JSON 字符串,它对应于一个字典,给出特定 IP 地址的地理信息:

{"country":"Netherlands","dma_code":"0","timezone":"Europe\/Amsterdam","area_code":"0","ip":"46.19.37.108","asn":"AS196752","continent_code":"EU","isp":"Tilaa V.O.F.","longitude":5.75, "latitude":52.5,"country_code":"NL","country_code3":"NLD"}

以下是来自ch-05.ipynb文件的代码:

import json 

json_str = '{"country":"Netherlands","dma_code":"0","timezone":"Europe\/Amsterdam","area_code":"0","ip":"46.19.37.108","asn":"AS196752","continent_code":"EU","isp":"Tilaa V.O.F.","longitude":5.75,"latitude":52.5,"country_code":"NL","country_code3":"NLD"}' 

data = json.loads(json_str) 
print("Country", data["country"]) 
data["country"] = "Brazil" 
print(json.dumps(data)) 

Python 有一个标准的 JSON API,非常容易使用。使用loads()函数解析 JSON 字符串:

data = json.loads(json_str) 

使用以下代码访问country值:

print "Country", data["country"] 

前一行应打印以下内容:

Country Netherlands

覆盖country值,并根据新的 JSON 数据创建一个字符串:

data["country"] = "Brazil" 
printjson.dumps(data) 

结果是带有新country值的 JSON。这种顺序不会像字典中通常出现的那样被保留:

{"longitude": 5.75, "ip": "46.19.37.108", "isp": "Tilaa V.O.F.", "area_code": "0", "dma_code": "0", "country_code3": "NLD", "continent_code": "EU", "country_code": "NL", "country": "Brazil", "latitude": 52.5, "timezone": "Europe/Amsterdam", "asn": "AS196752"}

与 Pandas 一起读写 JSON

我们可以很容易地从前面例子中的 JSON 字符串创建一个 PandasSeries。Pandasread_json()功能可以创建 Pandas 系列或 Pandas 数据框。

以下示例代码可以在本书的代码包ch-05.ipynb中找到:

import pandas as pd 

json_str = '{"country":"Netherlands","dma_code":"0","timezone":"Europe\/Amsterdam","area_code":"0","ip":"46.19.37.108","asn":"AS196752","continent_code":"EU","isp":"Tilaa V.O.F.","longitude":5.75,"latitude":52.5,"country_code":"NL","country_code3":"NLD"}' 

data = pd.read_json(json_str, typ='series') 
print("Series\n", data) 

data["country"] = "Brazil" 
print("New Series\n", data.to_json()) 

我们可以指定一个 JSON 字符串或者 JSON 文件的路径。调用read_json()函数,从前面例子中的 JSON 字符串创建 PandasSeries:

data = pd.read_json(json_str, typ='series') 
print("Series\n", data) 

在生成的Series中,按键按字母顺序排列:

    Series
    area_code                        0
    asn                       AS196752
    continent_code                  EU
    country                Netherlands
    country_code                    NL
    country_code3                  NLD
    dma_code                         0
    ip                    46.19.37.108
    ispTilaa V.O.F.
    latitude                      52.5
    longitude                     5.75
    timezone          Europe/Amsterdam
    dtype: object

再次更改country值,并使用to_json()方法将 PandasSeries转换为 JSON 字符串:

data["country"] = "Brazil" 
print("New Series\n", data.to_json()) 

在新的 JSON 字符串中,键顺序被保留,但是我们还有一个不同的country值:

 New Series
    {"area_code":"0","asn":"AS196752","continent_code":"EU","country":"Brazil","country_code":"NL","country_code3":"NLD","dma_code":"0","ip":"46.19.37.108","isp":"Tilaa V.O.F.","latitude":52.5,"longitude":5.75,"timezone":"Europe\/Amsterdam"}

解析 RSS 和 Atom 提要

真正简单的联合 ( RSS )和 Atom feeds(参考http://en.wikipedia.org/wiki/RSS)经常用于博客和新闻。这些类型的订阅源遵循发布/订阅模式。例如,帕克特出版公司有一个文章和书籍公告的 RSS 源。我们可以订阅订阅源以获得及时的更新。Python feedparser模块允许我们轻松解析 RSS 和 Atom 提要,而无需处理很多技术细节。feedparser模块可以安装pip如下:

$ pip3 install feedparser

解析完一个 RSS 文件后,我们可以使用虚线符号访问底层数据。解析打包发布 RSS 源并打印条目数:

import feedparser as fp 

rss = fp.parse("http://www.packtpub.com/rss.xml") 

print("# Entries", len(rss.entries)) 

打印条目的数量(数量可能因每次程序运行而异):

# Entries 10

如果条目包含带有以下代码的单词Python,则打印条目标题和摘要:

for i, entry in enumerate(rss.entries): 
   if "Python" in entry.summary: 
      print(i, entry.title) 
      print(entry.summary) 

在此特定运行中,打印了以下内容(如果您自己尝试,则可能会得到其他内容,如果过滤器限制太多,则可能什么也没有):

42 Create interactive plots with matplotlib using Pack't new book and eBook
About the author: Alexandre Devert is a scientist. He is an enthusiastic Python coder as well and never gets enough of it! He used to teach data mining, software engineering, and research in numerical optimization.
Matplotlib is part of the Scientific Python modules collection. It provides a large library of customizable plots and a comprehensive set of backends. It tries to make easy things easy and make hard things possible. It can help users generate plots, add dimensions to plots, and also make plots interactive with just a few lines of code. Also, matplotlib integrates well with all common GUI modules.

以下代码可以在本书代码包的ch-05.ipynb文件中找到:

import feedparser as fp 

rss = fp.parse("http://www.packtpub.com/rss.xml") 

print("# Entries", len(rss.entries)) 

for i, entry in enumerate(rss.entries): 
   if "Java" in entry.summary: 
      print(i, entry.title) 
      print(entry.summary) 

用靓汤解析 HTML

超文本标记语言 ( HTML )是用来创建网页的基础技术。HTML 是由 HTML 元素组成的,这些元素由所谓的标记组成,这些标记封装在倾斜的括号中(例如,<html>)。通常,标签与一个开始和结束标签成对出现在一个分层的树状结构中。伯纳斯-李于 1991 年首次发布了一个与 HTML 相关的规范草案。最初,只有 18 个 HTML 元素。正式的 HTML 定义由互联网工程任务组 ( IETF )于 1993 年发布。IETF 在 1995 年完成了 HTML 2.0 标准。大约在 2013 年,指定了最新的 HTML 版本 HTML5。与 XHTML 和 XML 相比,HTML 并不是一个非常严格的标准。

现代浏览器容忍大量违反标准的行为,使得网页成为一种非结构化数据。例如,我们可以将 HTML 视为一个大字符串,并用正则表达式对其执行字符串操作。这种方法只适用于简单的项目。

我曾在专业的环境中从事过网页抓取项目,所以从个人经验来看,我可以告诉你,我们需要更复杂的方法。在现实场景中,可能需要以编程方式提交 HTML 表单,例如,登录、浏览页面和健壮地管理 cookies。从网络上抓取数据的问题是,如果我们不能完全控制我们正在抓取的网页,我们可能不得不经常更改代码。网站所有者也可能主动阻止程序访问,甚至可能是非法的。出于这些原因,您应该总是先尝试使用其他替代方法,例如 REST API。

如果您必须通过抓取来检索数据,建议您使用 Python 美丽汤应用编程接口。这个应用编程接口可以从 HTML 和 XML 文件中提取数据。新项目应该用美人汤 4,因为美人汤 3 已经不开发了。我们可以用以下命令安装美人汤 4(类似easy_install):

$ pip3 install beautifulsoup4 lxml

如果这不起作用,您可以简单地将美丽的汤和您自己的代码打包在一起。为了演示解析 HTML,我用来自 http://loripsum.net/ T2 的生成器生成了本书代码包中的 T0 文件。然后,我稍微编辑了一下文件。该文件的内容是西塞罗用拉丁语创作的公元前一世纪的文本,这是创建网站模型的传统方式。请参考以下网页顶部的截图:

Parsing HTML with Beautiful Soup

在这个例子中,我们将使用美丽的汤 4 和标准的 Python 正则表达式库。

使用以下行导入这些库:

from bs4 import BeautifulSoup 
import re 

打开 HTML 文件,创建一个BeautifulSoup对象,如下行:

soup = BeautifulSoup(open('loremIpsum.html')) 

使用虚线符号,我们可以访问第一个<div>元素。<div> HTML 元素用于组织元素和设置元素的样式。访问第一个div元素,如下所示:

print("First div\n", soup.div) 

结果输出是一个 HTML 片段,带有第一个<div>标签和它包含的所有标签:

    First div
    <div class="tile">
    <h4>Development</h4>
         0.10.1 - July 2014<br/>
    </div>

这个特殊的div元素有一个值为tile的类属性。类属性属于应用于这个div元素的 CSS 样式。层叠样式表 ( CSS )是一种用于设计网页元素样式的语言。CSS 是一个广泛使用的规范,它通过 CSS 类来处理网页的外观和感觉。CSS 通过定义颜色、字体和元素布局来帮助分离内容和表示。这种分离导致更简单和更干净的设计。

标签的属性可以以类似字典的方式访问。如下打印<div>标签的类属性值:

print("First div class", soup.div['class']) 
First div class ['tile'] 

虚线符号允许我们访问任意深度的元素。例如,打印第一个<dfn>标签的文本如下:

print("First dfn text", soup.dl.dt.dfn.text) 

打印一行拉丁文文本(Solisten, I pray):

    First dfn text Quareattende, quaeso

有时候,我们只对 HTML 文档的超链接感兴趣。例如,我们可能只想知道哪个文档链接到哪个其他文档。在 HTML 中,链接用<a>标记指定。该标签的href属性保存链接指向的网址。BeautifulSoup班有个很好用的find_all()方法,我们会经常用到。使用find_all()方法定位所有超链接:

for link in soup.find_all('a'): 
      print("Link text", link.string, "URL", link.get('href')) 

文档中有三个链接具有相同的网址,但具有三种不同的文本:

Link text loripsum.net URL http://loripsum.net/ 
Link text Potera tautem inpune; URL http://loripsum.net/ 
Link text Is es profecto tu. URL http://loripsum.net/ 

我们可以省略find_all()方法作为捷径。访问所有<div>标签的内容,如下所示:

for i, div in enumerate(soup('div')): 
   print(i, div.contents) 

contents属性保存一个包含 HTML 元素的列表:

    0 [u'\n', <h4>Development</h4>, u'\n     0.10.1 - July 2014', <br/>, u'\n']
    1 [u'\n', <h4>Official Release</h4>, u'\n     0.10.0 June 2014', <br/>, u'\n']
    2 [u'\n', <h4>Previous Release</h4>, u'\n     0.09.1 June 2013', <br/>, u'\n']

带有唯一标识的标签很容易找到。选择official标识的<div>元素,打印第三个元素:

official_div = soup.find_all("div", id="official") 
print("Official Version", official_div[0].contents[2].strip()) 

许多网页是根据访问者的输入或外部数据动态创建的。网上购物网站的大部分内容都是这样提供的。如果我们在处理一个动态网站,我们必须记住,任何标签属性值都可能在一瞬间发生变化。通常,在大型网站中,会自动生成标识,从而产生长的字母数字字符串。最好不要寻找完全匹配,而是使用正则表达式。稍后我们将看到一个基于模式的匹配示例。前面的代码片段打印了一个版本号和月份,您可以在软件产品的网站上找到:

    Official Version 0.10.0 June 2014

众所周知,class是 Python 的一个关键词。要查询标签中的class属性,我们将其与class_进行匹配。获取已定义类别属性的<div>标签数量:

print("# elements with class",len(soup.find_all(class_=True))) 

正如预期的那样,我们发现了三个标签:

    # elements with class 3

用类别"tile"找到<div>标签的数量:

tile_class = soup.find_all("div", class_="tile") 
print("# Tile classes", len(tile_class)) 

有两个<div>类标签tile,一个<div>类标签notile:

  # Tile classes 2

定义一个匹配所有<div>标签的正则表达式:

print("# Divs with class containing tile", len(soup.find_all("div", class_=re.compile("tile")))) 

再次发现三种情况:

   # Divs with class containing tile 3

在 CSS 中,我们可以定义模式来匹配元素。这些模式被称为 CSS 选择器,并记录在http://www.w3.org/TR/selectors/中。我们也可以用 CSS 选择器从BeautifulSoup类中选择元素。使用select()方法将<div>元素与notile类匹配:

print("Using CSS selector\n", soup.select('div.notile')) 

屏幕上会显示以下内容:

Using CSS selector
[<div class="notile">
<h4>Previous Release</h4>
 0.09.1 June 2013<br/>
</div>]

一个 HTML 排序的列表看起来像一个编号的项目符号列表。有序列表由每个列表项的一个<ol>标签和几个<li>标签组成。select()方法的结果可以被切片为任何 Python 列表。请参考下面的订购列表截图:

Parsing HTML with Beautiful Soup

选择有序列表中的前两个列表项:

print("Selecting ordered list list items\n", soup.select("ol > li")[:2]) 

显示了以下两个列表项:

Selecting ordered list list items
[<li>Cur id non ita fit?</li>, <li>In qua si nihil est praeter rationem, sit in una virtute finis bonorum;</li>]

在 CSS 选择器迷你语言中,我们从1开始计数。选择第二个列表项,如下所示:

print("Second list item in ordered list", soup.select("ol>li:nth-of-type(2)")) 

第二个清单项可以翻译成英文其中,如果没有什么违背理智的事情,就让他成为美好事物终结的力量于一身:

Second list item in ordered list [<li>In qua si nihil est praeter rationem, sit in una virtute finis bonorum;</li>]

如果我们在浏览器中查看网页,我们可能会决定检索与某个正则表达式匹配的文本节点。查找包含带有text属性的字符串2014的所有文本节点:

print("Searching for text string", soup.find_all(text=re.compile("2014"))) 

这会打印以下文本节点:

Searching for text string [u'\n     0.10.1 - July 2014', u'\n     0.10.0 June 2014']

这只是BeautifulSoup课能为我们做什么的一个简单概述。美丽汤也可以用来修改 HTML 或 XML 文档。它有实用工具来排除故障,漂亮的打印,并处理不同的字符集。代码请参考ch-05.ipynb:

from bs4 import BeautifulSoup 
import re 

soup = BeautifulSoup(open('loremIpsum.html'),"lxml") 

print("First div\n", soup.div) 
print("First div class", soup.div['class']) 

print("First dfn text", soup.dl.dt.dfn.text) 

for link in soup.find_all('a'): 
   print("Link text", link.string, "URL", link.get('href')) 

# Omitting find_all 
for i, div in enumerate(soup('div')): 
   print(i, div.contents) 

#Div with id=official 
official_div = soup.find_all("div", id="official") 
print("Official Version", official_div[0].contents[2].strip()) 

print("# elements with class", len(soup.find_all(class_=True))) 

tile_class = soup.find_all("div", class_="tile") 
print("# Tile classes", len(tile_class)) 

print("# Divs with class containing tile", len(soup.find_all("div", class_=re.compile("tile")))) 

print("Using CSS selector\n", soup.select('div.notile')) 
print("Selecting ordered list list items\n", soup.select("ol > li")[:2]) 
print("Second list item in ordered list", soup.select("ol > li:nth-of-type(2)")) 

print("Searching for text string", soup.find_all(text=re.compile("2014"))) 

鼓励读者阅读参考部分提到的书籍,了解更多关于美丽汤功能的详细信息,例如搜索子节点的返回节点,获取返回节点的第 n 个父节点,获取返回节点的第 n 个同级节点,以及其他高级功能。

总结

在本章中,我们学习了以不同格式检索、处理和存储数据。这些是 CSV、NumPy .npy格式、Python pickle、JSON、RSS 和 HTML 格式。我们使用了 NumPy Pandas、JSON、feedparser 和美丽的汤库。

下一章第六章数据可视化,讲的是用 Python 可视化数据的重要话题。可视化是我们开始分析数据时经常做的事情。它有助于显示数据中变量之间的关系。通过可视化数据,我们还可以了解其统计特性。

参考

动词 (verb 的缩写)g .奈尔,美丽汤入门,帕克特出版,2014。

六、数据可视化

数据分析的第一步是可视化。即使是在查看数值表时,我们也可以在脑海中形成一个图表化数据的样子。数据可视化涉及信息的可视化表示的概念和分析,表示以某种形式模式抽象的数据,包括数据测量单位的属性或数量。数据可视化与科学可视化和统计图形密切相关。Python matplotlib库是一个著名的基于 NumPy 的绘图库,我们将在本章中使用它。它有一个面向对象的和类似于 Matlab 的程序性应用编程接口,可以并行使用。在http://matplotlib.org/gallery.html可以找到一个包含 matplotlib 示例的图库,这是一个基于云的数据可视化服务。我们将在本章末尾简要介绍这项服务。以下是本章将涵盖的主题列表:

  • matplotlib子包
  • 基本matplotlib地块
  • 对数图
  • 散点图
  • 图例和注释
  • 三维图
  • Pandas 中的阴谋
  • 滞后图
  • 自相关图
  • plot.ly

matplotlib 子包

如果我们更改ch-01.ipynb最后一节的代码来列出matplotlib子包,我们会得到以下结果:

matplotlib version 1.3.1
matplotlib.axes
matplotlib.backends
matplotlib.compat
matplotlib.delaunay DESCRIPTION :Author: Robert Kern   
    <robert.kern@gmail.com> :Copyright: Copyright 2005  
    Robert Kern. 
    :License: BSD-style license. See LICENSE.tx
matplotlib.projections
matplotlib.sphinxext
matplotlib.style
matplotlib.testing
matplotlib.tests
matplotlib.tri

子包的名称很容易解释。后端是指输出最终结果的方式。该输出可以是几种文件格式之一,也可以显示在图形用户界面的屏幕上。

基本 matplotlib 打印

我们在第 1 章Python 库中安装了 matplotlib 和 IPython。如果你需要刷新记忆,你可以回到那一章。许多人认为类似于 Matlab 的程序性 matplotlib 应用编程接口比面向对象的应用编程接口更容易使用,所以我们将首先演示这个程序性应用编程接口。为了在 matplotlib 中创建一个非常基本的图,我们需要调用matplotlib.pyplot子包中的plot()函数。该函数为具有已知xy 坐标的单个点列表或多个点列表生成二维图。

或者,我们可以传递一个格式参数,例如,指定一个虚线样式。plot()功能的格式选项和参数列表很长,但是使用以下命令很容易查找(在导入matplotlib.pyplot库之后):

In [1]: help(plot)

在本例中,我们将绘制两条线-一条是实线样式(默认),另一条是虚线样式。

以下演示代码位于本书代码包的ch-06.ipynb文件中:

import matplotlib.pyplot as plt 
import numpy as np 

x = np.linspace(0, 20) 

plt.plot(x,  .5 + x) 
plt.plot(x, 1 + 2 * x, '--') 
plt.show() 

请按照以下步骤绘制上述线条:

  1. 首先,我们用 NumPy linspace()函数指定x坐标。指定0的开始值和20的结束值:

            x = np.linspace(0, 20) 
    
    
  2. 如下绘制线条:

            plt.plot(x,  .5 + x) 
            plt.plot(x, 1 + 2 * x, '--') 
    
    
  3. At this juncture, we can either save the plot to a file with the savefig() function or show the plot on the screen with the show() function. Show the plot on the screen as follows:

            plt.show() 
    
    

    最终结果见下图:

    Basic matplotlib plots

对数图

对数图(或对数图)是使用对数标度的图。对数刻度显示变量的值,它使用与数量级匹配的间隔,而不是规则的线性刻度。对数图有两种类型。对数-对数图在两个轴上都采用对数标度,在 matplotlib 中由matplotlib.pyplot.loglog()函数表示。半对数图在一个轴上使用线性标度,在另一个轴上使用对数标度。这些图在 matplotlib 应用编程接口中由semilogx()semilogy()函数表示。在对数-对数图上,幂律显示为直线。在半对数图上,直线代表指数规律。

摩尔定律就是这样的定律。这不是物理上的,更多的是经验上的观察。戈登·摩尔发现了集成电路中晶体管数量每两年翻一番的趋势。在http://en . Wikipedia . org/wiki/Transistor _ count #微处理器上,你可以看到一张表格,上面有各种微处理器的晶体管计数和相应的推出年份。

从这个表中,我准备了一个 CSV 文件,transcount.csv,只包含晶体管计数和年份。我们仍然需要平均每年的晶体管数量。平均和加载可以用 Pandas 来完成。如有需要,可参考第三章Pandas 底火获取提示。一旦我们在表中有了每年的平均晶体管计数,我们就可以试着在计数与年份的对数上画一条直线。NumPy polyfit()函数允许我们将数据拟合为多项式。

以下代码请参考本书代码包中的ch-06.ipynb文件:

import matplotlib.pyplot as plt 
import numpy as np 
import pandas as pd 

df = pd.read_csv('transcount.csv') 
df = df.groupby('year').aggregate(np.mean) 
years = df.index.values 
counts = df['trans_count'].values 
poly = np.polyfit(years, np.log(counts), deg=1) 
print("Poly", poly) 
plt.semilogy(years, counts, 'o') 
plt.semilogy(years, np.exp(np.polyval(poly, years))) 
plt.show() 

以下步骤将解释前面的代码:

  1. 将数据拟合如下:

            poly = np.polyfit(years, np.log(counts), deg=1) 
            print("Poly", poly) 
    
    
  2. 拟合的结果是一个多项式对象(见[。这个对象的字符串表示给出了多项式系数,系数的阶数是递减的,所以最高的系数排在第一位。对于我们的数据,我们获得以下多项式系数:

            Poly [  3.61559210e-01  -7.05783195e+02]
    ```](http://docs.scipy.org/doc/numpy/reference/generated/numpy.polynomial.polynomial.Polynomial.html#numpy.polynomial.polynomial.Polynomial) 
    
  3. The NumPy polyval() function enables us to evaluate the polynomial we just obtained. Plot the data and fit it with the semilogy() function:

            plt.semilogy(years, counts, 'o') 
            plt.semilogy(years, np.exp(np.polyval(poly, years))) 
    
    

    趋势线绘制为实线,数据点绘制为实心圆。最终结果见下图:

    Logarithmic plots

散点图

一个散点图显示了笛卡尔坐标系中两个变量之间的关系。每个数据点的位置由这两个变量的值决定。散点图可以提示所研究的变量之间的任何相关性。上升趋势表明正相关。一张气泡图是散点图的延伸。在气泡图中,第三个变量的值由数据点周围气泡的相对大小来表示,因此得名。

http://en.wikipedia.org/wiki/Transistor_count#GPUs有一张表格,上面有图形处理器单元 ( 图形处理器)的晶体管计数。

图形处理器是用于高效显示图形的专用电路。由于现代显示硬件的工作方式,图形处理器可以用高度并行的操作来处理数据。图形处理器是计算领域的新发展。在本书代码包的gpu_transcount.csv文件中,你会注意到我们没有太多的数据点。处理缺失数据是一个反复出现的气泡图问题。我们将为缺失值定义默认气泡大小。同样,我们将每年加载和平均数据。然后,我们将把年指数上的中央处理器和图形处理器数据帧的晶体管计数与外部连接合并。NaN值将被设置为0(本例适用,但有时将NaN值设置为0可能不是一个好主意)。前文中描述的所有功能都包含在第 3 章、**Pandas 初级套装中,因此如果您需要,请参考本章。matplotlib 应用编程接口为散点图和气泡图提供scatter()功能。我们可以使用以下命令查看该函数的文档:

$ ipython3
ln [1]: import matplotlib as mpl
In [2]: help(mpl.scatter)

在本例中,我们将指定s参数,该参数与气泡的大小有关。c参数指定颜色。不幸的是,你在这本书里看不到颜色,所以你必须自己运行例子来看不同的颜色。alpha参数决定了图中气泡的透明度。该值在0(完全透明)和1(不透明)之间变化。按如下方式创建气泡图:

plt.scatter(years, cnt_log, c= 200 * years, s=20 + 200 * gpu_counts/gpu_counts.max(), alpha=0.5) 

本例的以下代码也可以在本书代码包的ch-06.ipynb文件中找到:

import matplotlib.pyplot as plt 
import numpy as np 
import pandas as pd 

df = pd.read_csv('transcount.csv') 
df = df.groupby('year').aggregate(np.mean) 

gpu = pd.read_csv('gpu_transcount.csv') 
gpu = gpu.groupby('year').aggregate(np.mean) 

df = pd.merge(df, gpu, how='outer', left_index=True, right_index=True) 
df = df.replace(np.nan, 0) 
print(df) 
years = df.index.values 
counts = df['trans_count'].values 
gpu_counts = df['gpu_trans_count'].values 
cnt_log = np.log(counts) 
plt.scatter(years, cnt_log, c= 200 * years, s=20 + 200 * gpu_counts/gpu_counts.max(), alpha=0.5) 
plt.show() 

最终结果见下图:

Scatter plots

传说和注释

图例和注释是一目了然地显示理解情节所需信息的有效工具。典型图将包含以下附加信息元素:

  • 描述剧情中各种数据系列的图例。这是通过调用 matplotlib legend()函数并为每个数据系列提供标签来实现的。
  • 情节中重要点的注释。matplotlib annotate()功能可用于此目的。matplotlib 注释由一个标签和一个箭头组成。这个函数有很多参数描述标签和箭头的样式和位置,所以可能需要调用help(annotate)进行详细描述。
  • 水平轴和垂直轴上的标签。这些标签可以通过xlabel()ylabel()功能绘制。我们需要给这些函数一个字符串形式的标签文本,以及可选的参数,比如标签的字体大小。
  • 具有 matplotlib title()功能的图形的描述性标题。通常,我们只给这个函数一个表示标题的字符串。
  • 有一个网格也很好,这样可以很容易地定位点。matplotlib grid()功能打开和关闭绘图网格。

我们将修改上一个示例中的气泡图代码,并添加本章第二个示例中的直线拟合。在此设置中,将标签添加到数据系列,如下所示:

plt.plot(years, np.polyval(poly, years), label='Fit') 
plt.scatter(years, cnt_log, c= 200 * years, s=20 + 200 * gpu_counts/gpu_counts.max(), alpha=0.5, label="Scatter Plot") 

让我们注释一下数据集中的第一个 GPU。为此,获取相关的点,定义注释的标签,指定箭头的样式(arrowprops参数),并确保注释悬停在有问题的点上方:

gpu_start = gpu.index.values.min() 
y_ann = np.log(df.at[gpu_start, 'trans_count']) 
ann_str = "First GPU\n %d" % gpu_start 
plt.annotate(ann_str, xy=(gpu_start, y_ann), arrowprops=dict(arrowstyle="->"), xytext=(-30, +70), textcoords='offset points') 

完整的代码示例在本书代码包的ch-06.ipynb文件中:

import matplotlib.pyplot as plt 
import numpy as np 
import pandas as pd 

df = pd.read_csv('transcount.csv') 
df = df.groupby('year').aggregate(np.mean) 
gpu = pd.read_csv('gpu_transcount.csv') 
gpu = gpu.groupby('year').aggregate(np.mean) 

df = pd.merge(df, gpu, how='outer', left_index=True, right_index=True) 
df = df.replace(np.nan, 0) 
years = df.index.values 
counts = df['trans_count'].values 
gpu_counts = df['gpu_trans_count'].values 

poly = np.polyfit(years, np.log(counts), deg=1) 
plt.plot(years, np.polyval(poly, years), label='Fit') 

gpu_start = gpu.index.values.min() 
y_ann = np.log(df.at[gpu_start, 'trans_count']) 
ann_str = "First GPU\n %d" % gpu_start 
plt.annotate(ann_str, xy=(gpu_start, y_ann), arrowprops=dict(arrowstyle="->"), xytext=(-30, +70), textcoords='offset points') 

cnt_log = np.log(counts) 
plt.scatter(years, cnt_log, c= 200 * years, s=20 + 200 * gpu_counts/gpu_counts.max(), alpha=0.5, label="Scatter Plot") 
plt.legend(loc='upper left') 
plt.grid() 
plt.xlabel("Year") 
plt.ylabel("Log Transistor Counts", fontsize=16) 
plt.title("Moore's Law & Transistor Counts") 
plt.show() 

最终结果见下图:

Legends and annotations

三维图

二维图是数据可视化的基础。然而,如果你想炫耀,没有什么比得上一个好的三维剧情。我曾经负责过一个可以画等高线图和三维图的软件包。该软件甚至可以绘制出当用特殊的眼镜观看时会突然出现在你面前的图表。

matplotlib API 具有用于三维绘图的Axes3D类。通过演示这个类是如何工作的,我们也将展示面向对象的 matplotlib API 是如何工作的。matplotlib Figure类是图表元素的顶级容器:

  1. 创建一个figure对象,如下所示:

            fig = plt.figure() 
    
    
  2. figure对象创建一个Axes3D对象:

            ax = Axes3D(fig) 
    
    
  3. 年份和 CPU 晶体管计数将是我们的XY轴。我们有必要根据年份和中央处理器晶体管计数数组创建坐标矩阵。使用meshgrid()功能

            X, Y = np.meshgrid(X, Y) 
    
    

    创建坐标矩阵

  4. Axes3D类的plot_surface()方法绘制数据:

            ax.plot_surface(X, Y, Z) 
    
    
  5. 面向对象的 API 方法的命名约定是以set_开始,以过程对应的函数名结束,如下面的代码片段所示:

            ax.set_xlabel('Year') 
            ax.set_ylabel('Log CPU transistor counts') 
            ax.set_zlabel('Log GPU transistor counts') 
            ax.set_title("Moore's Law & Transistor Counts") 
    
    

您还可以查看本书代码包中ch-06.ipynb文件中的以下代码:

from mpl_toolkits.mplot3d.axes3d import Axes3D 
import matplotlib.pyplot as plt 
import numpy as np 
import pandas as pd 

df = pd.read_csv('transcount.csv') 
df = df.groupby('year').aggregate(np.mean) 
gpu = pd.read_csv('gpu_transcount.csv') 
gpu = gpu.groupby('year').aggregate(np.mean) 

df = pd.merge(df, gpu, how='outer', left_index=True, right_index=True) 
df = df.replace(np.nan, 0) 

fig = plt.figure() 
ax = Axes3D(fig) 
X = df.index.values 
Y = np.where(df['trans_count'].values>0,np.ma.log(df['trans_count'].values), 0) 
X, Y = np.meshgrid(X, Y) 
Z = np.where(df['gpu_trans_count'].values>0,np.ma.log(df['gpu_trans_count'].values), 0) 
ax.plot_surface(X, Y, Z) 
ax.set_xlabel('Year') 
ax.set_ylabel('Log CPU transistor counts') 
ax.set_zlabel('Log GPU transistor counts') 
ax.set_title("Moore's Law & Transistor Counts") 
plt.show() 

最终结果见下图:

Three-dimensional plots

在 Pandas 中绘图

PandasSeriesDataFrame类中的plot()方法包装了相关的 matplotlib 函数。在最基本的形式中,没有任何参数,plot()方法显示了我们在本章中一直使用的数据集的以下图:

Plotting in Pandas

要创建半对数图,添加logy参数:

df.plot(logy=True) 

这导致我们的数据如下图所示:

Plotting in Pandas

要创建散点图,请将kind参数指定为scatter。我们还需要指定两列。将loglog参数设置为True以生成对数图(对于此代码,我们至少需要 Pandas v0.13.0):

df[df['gpu_trans_count'] > 0].plot(kind='scatter', x='trans_count', y='gpu_trans_count', loglog=True) 

最终结果见下图:

Plotting in Pandas

以下程序在本书代码包的ch-06.ipynb文件中:

import matplotlib.pyplot as plt 
import numpy as np 
import pandas as pd 

df = pd.read_csv('transcount.csv') 
df = df.groupby('year').aggregate(np.mean) 

gpu = pd.read_csv('gpu_transcount.csv') 
gpu = gpu.groupby('year').aggregate(np.mean) 

df = pd.merge(df, gpu, how='outer', left_index=True, right_index=True) 
df = df.replace(np.nan, 0) 
df.plot() 
df.plot(logy=True) 
df[df['gpu_trans_count'] > 0].plot(kind='scatter', x='trans_count', y='gpu_trans_count', loglog=True) 
plt.show() 

滞后图

一个滞后图是一个时间序列和相同数据滞后的散点图。例如,通过这样的图,我们可以检查今年的中央处理器晶体管计数和前一年的计数之间是否存在可能的相关性。pandas.tools.plotting中的lag_plot()Pandas 功能可以画出滞后图。用1的默认滞后绘制一个 CPU 晶体管计数的滞后图,如下所示:

lag_plot(np.log(df['trans_count'])) 

最终结果见下图:

Lag plots

滞后图示例的以下代码也可以在本书代码包的ch-06.ipynb文件中找到:

import matplotlib.pyplot as plt 
import numpy as np 
import pandas as pd 
from pandas.tools.plotting import lag_plot 

df = pd.read_csv('transcount.csv') 
df = df.groupby('year').aggregate(np.mean) 

gpu = pd.read_csv('gpu_transcount.csv') 
gpu = gpu.groupby('year').aggregate(np.mean) 

df = pd.merge(df, gpu, how='outer', left_index=True, right_index=True) 
df = df.replace(np.nan, 0) 
lag_plot(np.log(df['trans_count'])) 
plt.show() 

自相关图

自相关图绘制不同时滞的时间序列数据的自相关图。通俗地说,自相关就是时间 n 的值和时间 n 的值+l 的相关性,其中 l 是时滞。通常,这些图用于检查时间序列在其进展中是否具有随机性。在随机时间序列的情况下,自相关在所有时滞分离中都接近于零,在非随机时间序列的一些或所有时滞分离中具有非零的显著性值。我们在第 7 章信号处理和时间序列中进一步解释自相关。

pandas.tools.plotting中的autocorrelation_plot()Pandas 函数可以画出自相关图。以下是本书代码包中ch-06.ipynb文件的代码:

import matplotlib.pyplot as plt 
import numpy as np 
import pandas as pd 
from pandas.tools.plotting import autocorrelation_plot 

df = pd.read_csv('transcount.csv') 
df = df.groupby('year').aggregate(np.mean) 

gpu = pd.read_csv('gpu_transcount.csv') 
gpu = gpu.groupby('year').aggregate(np.mean) 

df = pd.merge(df, gpu, how='outer', left_index=True, right_index=True) 
df = df.replace(np.nan, 0) 
autocorrelation_plot(np.log(df['trans_count'])) 
plt.show() 

绘制中央处理器晶体管计数的自相关图,如下所示:

autocorrelation_plot(np.log(df['trans_count'])) 

最终结果见下图。正如我们在下图中看到的,较新的值(较小的滞后)与当前值的相关性比较旧的值(较大的滞后)更强,并且当滞后非常大时,相关性衰减到0:

Autocorrelation plots

Plot.ly

Plot.ly 是在线数据可视化工具的软件即服务 ( SaaS )云服务。Plot.ly 提供了一个相关的 python 库,可以在用户的机器上与 Python 一起使用。我们可以通过网络界面导入和分析数据,或者完全在本地环境中工作,并在 Plot.ly 网站上发布最终结果。情节可以很容易地在一个团队的网站上分享,允许合作,这确实是网站的第一点。在本节中,我们将给出一个如何用 Python API 绘制方框图的例子。

箱线图是使用四分位数可视化数据集的一种特殊方式。如果我们将排序后的数据集分成四个相等的部分,第一个四分位数将是具有最小数字的部分的最大值。第二个四分位数将是数据集中间的值,也称为中位数。第三个四分位数是中间值和最高值之间的值。箱线图的底部和顶部由第一个和第三个四分位数组成。穿过盒子的线是中线。盒子两端的胡须通常是数据集的最小值和最大值。在这一节的最后,我们将看到一个带注释的方框图,它将澄清问题。使用以下命令安装绘图应用编程接口:

$ sudo pip3 install plotly

安装完应用编程接口后,注册获取应用编程接口密钥。以下代码片段在提供有效密钥后让您登录:

# Change the user and api_key to your own username and api_key
py.sign_in('username', 'api_key')

使用绘图应用编程接口创建方框图,如下所示:

data = Data([Box(y=counts), Box(y=gpu_counts)])
plot_url = py.plot(data, filename='moore-law-scatter')

请参考本书代码包中ch-06.ipynb文件的以下代码:

import plotly.plotly as py
from plotly.graph_objs import *
import numpy as np
import pandas as pd

df = pd.read_csv('transcount.csv')
df = df.groupby('year').aggregate(np.mean)

gpu = pd.read_csv('gpu_transcount.csv')
gpu = gpu.groupby('year').aggregate(np.mean)
df = pd.merge(df, gpu, how='outer', left_index=True, right_index=True)
df = df.replace(np.nan, 0)

# Change the user and api_key to your own username and api_key
py.sign_in('username', 'api_key')

counts = np.log(df['trans_count'].values)
gpu_counts = np.log(df['gpu_trans_count'].values)

data = Data([Box(y=counts), Box(y=gpu_counts)])
plot_url = py.plot(data, filename='moore-law-scatter')
print(plot_url)

最终结果见下图:

Plot.ly

总结

在本章中,我们讨论了使用 Python 使用绘图来可视化数据。为此,我们使用了 matplotlib 和 Pandas。我们介绍了箱线图、散点图、气泡图、对数图、自相关图、滞后图、三维图、图例和注释。

对数图(或对数图)是使用对数刻度的图。半对数图在一个轴上使用线性标度,在另一个轴上使用对数标度。散点图将两个变量相对绘制。气泡图是一种特殊类型的散点图。在气泡图中,第三个变量的值相对由数据点周围气泡的大小来表示。自相关绘制不同滞后时间序列数据的自相关图。

我们了解了 plot.ly,一个基于云的在线数据可视化服务,并使用该服务构建了一个箱式图。箱线图基于数据的四分位数可视化数据。

下一章第七章信号处理与时间序列讲述的是一种特殊类型的数据——时间序列。时间序列由已打上时间戳的有序数据点组成。我们测量的很多物理世界数据都是以时间序列的形式出现的,可以认为是一种信号,比如声音、光或电信号。你将在下一章学习如何过滤信号和建模时间序列。*

七、信号处理和时间序列

信号处理是工程和应用数学的一个领域,包括分析随时间变化的变量,这种数据也被称为模拟和数字信号。信号处理技术的一个类别是时间序列分析。一个时间序列是一个有序的数据点列表,从最早的测量值开始。数据点通常是等距的,例如,每小时、每天、每周、每月或每年取样。在时间序列分析中,值的顺序很重要。在同一时间序列中,试图导出一个值与另一个数据点或数据点组合(过去的固定周期数)之间的关系是很常见的。

本章中的时间序列例子使用了年太阳黑子周期数据。该数据由statsmodels包(一个开源 Python 项目)提供。示例使用了 NumPy/SciPy、Pandas 以及statsmodels

我们将在本章中讨论以下主题:

  • statsmodels 模块
  • 移动平均线
  • 窗口功能
  • 定义协整
  • 自相关
  • 自回归模型
  • ARMA 模型
  • 产生周期性信号
  • 傅里叶分析
  • 光谱分析
  • 过滤

stats models 模块

要安装statsmodels,执行以下命令:

$ pip3 install statsmodels

在所附的ch-07.ipynb文件中,我们列出了statsmodels模块,得到如下结果:

statmodels version 0.6.1
statsmodels.base 
statsmodels.compat 
statsmodels.datasets 
statsmodels.discrete 
statsmodels.distributions 
statsmodels.duration 
statsmodels.emplike 
statsmodels.formula 
statsmodels.genmod 
statsmodels.graphics 
statsmodels.interface 
statsmodels.iolib 
statsmodels.miscmodels 
statsmodels.nonparametric 
DESCRIPTION For an overview of this module, see docs/source/nonparametric.rst PACKAGE CONTENTS _kernel_base _smoothers_lowess api bandwidths statsmodels.regression 
statsmodels.resampling 
statsmodels.robust 
statsmodels.sandbox 
statsmodels.stats 
statsmodels.tests 
statsmodels.tools 
statsmodels.tsa 

移动平均线

移动平均线常用于分析时间序列。移动平均线指定了以前看到的数据窗口,每次窗口向前滑动一个周期时,移动平均线就会取平均值:

Moving averages

不同类型的移动平均线在用于平均的权重上有本质的不同。例如,指数移动平均线的权重随时间呈指数递减:

Moving averages

这意味着旧值比新值的影响小,这有时是可取的。

本书代码包中ch-07.ipynb文件的以下代码绘制了 11 年和 22 年太阳黑子周期的简单移动平均线:

import matplotlib.pyplot as plt 
import statsmodels.api as sm 
from pandas.stats.moments import rolling_mean 

data_loader = sm.datasets.sunspots.load_pandas() 
df = data_loader.data 
year_range = df["YEAR"].values 
plt.plot(year_range, df["SUNACTIVITY"].values, label="Original") 
plt.plot(year_range, df.rolling(window=11).mean()["SUNACTIVITY"].values, label="SMA 11") 
plt.plot(year_range, df.rolling(window=22).mean()["SUNACTIVITY"].values, label="SMA 22") 
plt.legend() 
plt.show() 

我们可以表示指数移动平均线的指数递减权重策略,如下面的 NumPy 代码所示:

weights = np.exp(np.linspace(-1., 0., N)) 
weights /= weights.sum() 

简单的移动平均线使用相等的权重,在代码中看起来如下:

def sma(arr, n): 
   weights = np.ones(n) / n 

   return np.convolve(weights, arr)[n-1:-n+1]  

因为我们可以将数据加载到Pandas 数据框中,所以使用 PandasDataFrame.rolling().mean()功能更方便。使用statsmodels加载数据如下:

data_loader = sm.datasets.sunspots.load_pandas() 
df = data_loader.data 

最终结果见下图:

Moving averages

窗口功能

NumPy 有许多窗口例程,可以像我们在上一节中所做的那样,在滚动窗口中计算权重。

一个窗口函数是一个在一个区间(窗口)内定义的函数,或者是零值。我们可以使用窗口函数进行光谱分析和滤波器设计(更多背景信息,请参考http://en.wikipedia.org/wiki/Window_function)。棚车车窗为矩形车窗,公式如下:

w(n) = 1 

三角窗形状像三角形,公式如下:

Window functions

在上式中,L可以等于NN+1N-1。在最后一种情况下,窗口函数被称为巴特利特窗口布莱克曼窗为钟形,定义如下:

Window functions

Window functions

汉宁窗也是钟形的,定义如下:

Window functions

在 Pandas 应用编程接口中,DataFrame.rolling()函数提供相同的功能,不同的窗口函数对应不同的win_type字符串参数值。另一个参数是窗口的大小,对于太阳黑子数据的中间周期(根据研究,有 11 年、22 年和 100 年三个周期),窗口将被设置为22。代码很简单,在本书代码包的ch-07.ipynb文件中给出(这里的数据仅限于过去 150 年,以便在图中进行比较):

import matplotlib.pyplot as plt 
import statsmodels.api as sm 
from pandas.stats.moments import rolling_window 
import pandas as pd 

data_loader = sm.datasets.sunspots.load_pandas() 
df = data_loader.data.tail(150) 
df = pd.DataFrame({'SUNACTIVITY':df['SUNACTIVITY'].values}, index=df['YEAR']) 
ax = df.plot() 

def plot_window(wintype): 
    df2 = df.rolling(window=22,win_type=wintype, 
center=False,axis=0).mean() 
    df2.columns = [wintype] 
    df2.plot(ax=ax) 

plot_window('boxcar') 
plot_window('triang') 
plot_window('blackman') 
plot_window('hanning') 
plot_window('bartlett') 
plt.show() 

最终结果见下图:

Window functions

定义协整

协整类似于相关性,但被许多人视为定义两个时间序列相关性的优越指标。如果两个时间序列x(t)y(t)的线性组合是平稳的,则它们是协整的。在这种情况下,下面的等式应该是固定的:

y(t) - a x(t) 

想想一个醉汉和他的狗出去散步。相关性告诉我们它们是否在同一个方向。协整告诉我们一些关于人和他的狗之间的时间距离。我们将使用随机生成的时间序列和真实数据来展示协整性。增广 Dickey-Fuller ( ADF )测试(见http://en . Wikipedia . org/wiki/增广 _Dickey%E2%80%93Fuller_test )测试时间序列中的单位根,可用于确定时间序列的协整性。

对于下面的代码,请看一下本书代码包中的ch-07.ipynb文件:

import statsmodels.api as sm 
from pandas.stats.moments import rolling_window 
import pandas as pd 
import statsmodels.tsa.stattools as ts 
import numpy as np 

def calc_adf(x, y): 
    result = sm.OLS(x, y).fit()     
    return ts.adfuller(result.resid) 

data_loader = sm.datasets.sunspots.load_pandas() 
data = data_loader.data.values 
N = len(data) 

t = np.linspace(-2 * np.pi, 2 * np.pi, N) 
sine = np.sin(np.sin(t)) 
print("Self ADF", calc_adf(sine, sine)) 

noise = np.random.normal(0, .01, N) 
print("ADF sine with noise", calc_adf(sine, sine + noise)) 

cosine = 100 * np.cos(t) + 10 
print("ADF sine vs cosine with noise", calc_adf(sine, cosine + noise)) 

print("Sine vs sunspots", calc_adf(sine, data)) 

让我们从协整演示开始:

  1. 定义以下函数来计算 ADF 统计量:

            def calc_adf(x, y): 
                result = sm.OLS(x, y).fit()     
                return ts.adfuller(result.resid) 
    
    
  2. 将太阳黑子数据加载到 NumPy 数组中:

            data_loader = sm.datasets.sunspots.load_pandas() 
            data = data_loader.data.values 
            N = len(data) 
    
    
  3. 生成sine并计算sine与自身的协整:

            t = np.linspace(-2 * np.pi, 2 * np.pi, N) 
            sine = np.sin(np.sin(t)) 
            print("Self ADF", calc_adf(sine, sine)) 
    
    
  4. The code should print the following:

            Self ADF (-5.0383000037165746e-16, 0.95853208606005591, 0, 308,  
            {'5%': -2.8709700936076912, '1%': -3.4517611601803702, '10%':  
            -2.5717944160060719}, -21533.113655477719)
    
    

    打印输出中的第一个值是 ADF 指标,第二个值是 p 值。如你所见,p 值非常高。以下值是滞后和样本大小。最后的字典给出了这个精确样本量的 t 分布值。

  5. 现在,向sine添加噪声,演示噪声将如何影响信号:

            noise = np.random.normal(0, .01, N) 
            print("ADF sine with noise", calc_adf(sine, sine + noise)) 
    
    
  6. With the noise, we get the following results:

           ADF sine with noise (-7.4535502402193075, 5.5885761455106898e-
           11, 3, 305, {'5%': -2.8710633193086648, '1%': 
           -3.4519735736206991, '10%': -2.5718441306100512}, 
           -1855.0243977703672)
    
    

    p 值已经大幅下降。这里的 ADF 度量-7.45低于字典中所有的临界值。所有这些都是拒绝协整的有力论据。

  7. 让我们生成一个更大幅度和偏移的cosine。再次,让我们添加噪音:

        cosine = 100 * np.cos(t) + 10 
        print("ADF sine vs cosine with noise", calc_adf(sine, cosine + 
        noise)) 

将打印以下值:

ADF sine vs cosine with noise (-17.927224617871534, 2.8918612252729532e-30, 16, 292, {'5%': -2.8714895534256861, '1%': -3.4529449243622383, '10%': -2.5720714378870331}, -11017.837238220782)

同样,我们有充分的理由拒绝协整。检查sinesunspots之间的协整给出以下输出:

Sine vs sunspots (-6.7242691810701016, 3.4210811915549028e-09, 16, 292, {'5%': -2.8714895534256861, '1%': -3.4529449243622383, '10%': -2.5720714378870331}, -1102.5867415291168)

这里使用的对的置信水平大致相同,因为它们取决于数据点的数量,而数据点的数量变化不大。下表总结了结果:

| **配对** | **统计** | **p 值** | **5%** | **1%** | **10%** | **拒绝** | | 自正弦 | -5.03E-16 | Zero point nine five | -2.87 | -3.45 | -2.57 | 不 | | 正弦与正弦噪声 | -7.45 | 5.58E-11 | -2.87 | -3.45 | -2.57 | 是 | | 正弦和余弦与噪声的关系 | -17.92 | 2.89E-30 | -2.87 | -3.45 | -2.57 | 是 | | 正弦与太阳黑子 | -6.72 | 3.42E-09 | -2.87 | -3.45 | -2.57 | 是 |

自相关

自相关是数据集内的相关性,可以指示趋势。

对于给定的时间序列,在已知均值和标准差的情况下,我们可以使用期望值运算符定义时间st的自相关,如下所示:

Autocorrelation

本质上,这是应用于时间序列和滞后时间序列的相关公式。

例如,如果我们有一个周期的滞后,我们可以检查前一个值是否影响当前值。要做到这一点,自相关值必须相当高。

在前一章第 6 章数据可视化中,我们已经使用了绘制自相关的 Pandas 函数。在这个例子中,我们将使用 NumPy correlate()函数来计算太阳黑子周期的实际自相关值。最后,我们需要将我们收到的值规范化。按如下方式应用数字按钮correlate()功能:

y = data - np.mean(data) 
norm = np.sum(y ** 2) 
correlated = np.correlate(y, y, mode='full')/norm 

我们还对与最高相关性相对应的指数感兴趣。这些索引可以通过 NumPy argsort()函数找到,该函数返回对数组进行排序的索引:

print np.argsort(res)[-5:] 

这些是最大自相关的指数:

[ 9 11 10  1  0]  

根据定义,最大的自相关是零滞后,即信号与其自身的相关性。第二大值是滞后 1 年和 10 年。查看本书代码包中的ch-07.ipynb文件:

import numpy as np 
import pandas as pd 
import statsmodels.api as sm 
import matplotlib.pyplot as plt 
from pandas.tools.plotting import autocorrelation_plot 

data_loader = sm.datasets.sunspots.load_pandas() 
data = data_loader.data["SUNACTIVITY"].values 
y = data - np.mean(data) 
norm = np.sum(y ** 2) 
correlated = np.correlate(y, y, mode='full')/norm 
res = correlated[len(correlated)/2:] 

print(np.argsort(res)[-5:]) 
plt.plot(res) 
plt.grid(True) 
plt.xlabel("Lag") 
plt.ylabel("Autocorrelation") 
plt.show() 
autocorrelation_plot(data) 
plt.show() 

最终结果见下图:

Autocorrelation

将之前的剧情与 Pandas 制作的剧情进行对比:

Autocorrelation

自回归模型

一个自回归模型可以用来表示一个时间序列,目标是预测未来值。在这样的模型中,假设一个变量依赖于它以前的值。该关系也被假设为线性的,并且我们需要拟合数据以便找到数据的参数。自回归模型的数学公式如下:

Autoregressive models

在前面的公式中,c是常数,最后一项是随机分量,也称为白噪声。

这给我们带来了非常常见的线性回归问题。出于实际原因,保持模型简单并且只包含必要的滞后组件是很重要的。在机器学习术语中,这些被称为特征。对于回归问题,Python 机器学习 scikit-learn 库即使不是最好的选择,也是一个不错的选择。我们将在第 10 章、预测性分析和机器学习中使用该应用编程接口。

在回归设置中,我们经常会遇到过度拟合的问题——当我们对一个样本有完美的拟合时,这个问题就会出现,当我们引入新的数据点时,这个样本的性能会很差。标准的解决方案是应用交叉验证(或者使用避免过度拟合的算法)。在这种方法中,我们估计部分样本的模型参数。其余的数据用于测试和评估模型。这其实是一个简化的解释。还有更复杂的交叉验证方案,其中很多都得到 scikit-learn 的支持。为了评估模型,我们可以计算适当的评估指标。可以想象,有许多度量标准,由于实践者的不断调整,这些度量标准可以有不同的定义。我们可以在书上或维基百科上找到这些定义。重要的是要记住,对预测或拟合的评估不是一门精确的科学。有这么多指标的事实只能证实这一点。

我们将使用前一节中找到的前两个滞后分量,使用scipy.optimize.leastsq()函数建立模型。我们可以选择线性代数函数来代替。然而,leastsq()功能更加灵活,让我们可以指定几乎任何类型的模型。按照以下步骤设置模型:

def model(p, x1, x10): 
   p1, p10 = p 
   return p1 * x1 + p10 * x10 

def error(p, data, x1, x10): 
   return data - model(p, x1, x10) 

要拟合模型,初始化参数列表并将其传递给leastsq()函数,如下所示:

def fit(data): 
   p0 = [.5, 0.5] 
   params = leastsq(error, p0, args=(data[10:], data[9:-1], data[:-10]))[0] 
   return params 

根据部分数据训练模型:

cutoff = .9 * len(sunspots) 
params = fit(sunspots[:cutoff]) 
print "Params", params 

以下是我们得到的参数:

Params [ 0.67172672  0.33626295]

有了这些参数,我们将绘制预测值并计算各种指标。以下是我们获得的指标值:

Root mean square error 22.8148122613
Mean absolute error 17.6515446503
Mean absolute percentage error 60.7817800736
Symmetric Mean absolute percentage error 34.9843386176
Coefficient of determination 0.799940292779

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

Autoregressive models

似乎我们有许多几乎是准确无误的预测,但也有一堆相当遥远的预测。总的来说,我们没有完美的契合;然而,这并不是一场彻底的灾难。它在中间的某个地方。

以下代码在本书代码包的ch-07.ipynb文件中:

from scipy.optimize import leastsq 
import statsmodels.api as sm 
import matplotlib.pyplot as plt 
import numpy as np 

def model(p, x1, x10): 
   p1, p10 = p 
   return p1 * x1 + p10 * x10 

def error(p, data, x1, x10): 
   return data - model(p, x1, x10) 

def fit(data): 
   p0 = [.5, 0.5] 
   params = leastsq(error, p0, args=(data[10:], data[9:-1], data[:-10]))[0] 
   return params 

data_loader = sm.datasets.sunspots.load_pandas() 
sunspots = data_loader.data["SUNACTIVITY"].values 

cutoff = .9 * len(sunspots) 
params = fit(sunspots[:cutoff]) 
print("Params", params) 

pred = params[0] * sunspots[cutoff-1:-1] + params[1] * sunspots[cutoff-10:-10] 
actual = sunspots[cutoff:] 
print("Root mean square error", np.sqrt(np.mean((actual - pred) ** 2))) 
print("Mean absolute error", np.mean(np.abs(actual - pred))) 
print("Mean absolute percentage error", 100 * np.mean(np.abs(actual - pred)/actual)) 
mid = (actual + pred)/2 
print("Symmetric Mean absolute percentage error", 100 * np.mean(np.abs(actual - pred)/mid)) 
print("Coefficient of determination", 1 - ((actual - pred) ** 2).sum()/ ((actual - actual.mean()) ** 2).sum()) 
year_range = data_loader.data["YEAR"].values[cutoff:] 
plt.plot(year_range, actual, 'o', label="Sunspots") 
plt.plot(year_range, pred, 'x', label="Prediction") 
plt.grid(True) 
plt.xlabel("YEAR") 
plt.ylabel("SUNACTIVITY") 
plt.legend() 
plt.show() 

ARMA 模型

ARMA 模型常用于预测一个时间序列。这些模型结合了自回归和移动平均模型。在移动平均模型中,我们假设一个变量是时间序列平均值和噪声成分线性组合的和。

自回归和移动平均模型可以有不同的顺序。一般来说,我们可以用p自回归项和q移动平均项定义一个 ARMA 模型,如下所示:

ARMA models

在前面的公式中,就像在自回归模型公式中一样,我们有一个常数和一个白噪声分量;然而,我们也试图拟合滞后噪声分量。

幸运的是,可以使用statsmodelssm.tsa.ARMA()例程进行此分析。将数据拟合到ARMA(10,1)模型,如下所示:

model = sm.tsa.ARMA(df, (10,1)).fit() 

执行预测(statsmodels 大量使用字符串):

prediction = model.predict('1975', str(years[-1]), dynamic=True) 

最终结果见下图:

ARMA models

拟合度很差,因为坦率地说,我们过度拟合了数据。上一节中更简单的模型效果更好。示例代码可以在本书代码包的ch-07.ipynb文件中找到:

import pandas as pd 
import matplotlib.pyplot as plt 
import statsmodels.api as sm 
import datetime 

data_loader = sm.datasets.sunspots.load_pandas() 
df = data_loader.data 
years = df["YEAR"].values.astype(int) 
df.index = pd.Index(sm.tsa.datetools.dates_from_range(str(years[0]), str(years[-1]))) 
del df["YEAR"] 

model = sm.tsa.ARMA(df, (10,1)).fit() 
prediction = model.predict('1975', str(years[-1]), dynamic=True) 

df['1975':].plot() 
prediction.plot(style='--', label='Prediction') 
plt.legend() 
plt.show() 

产生周期信号

许多自然现象就像一个精确的时钟一样有规律,值得信赖。有些现象呈现出似乎有规律的模式。一组科学家通过希尔伯特-黄变换发现了太阳黑子活动的三个周期。周期的持续时间大约为 11 年、22 年和 100 年。通常,我们会使用三角函数(如正弦函数)来模拟周期信号。你可能还记得高中时的一些三角学。这就是我们这个例子所需要的。因为我们有三个周期,所以创建一个模型似乎是合理的,它是三个正弦函数的线性组合。这仅仅需要对自回归模型的代码进行微小的调整。以下代码请参考本书代码包中的periodic.py文件:

from scipy.optimize import leastsq 
import statsmodels.api as sm 
import matplotlib.pyplot as plt 
import numpy as np 
def model(p, t): 
   C, p1, f1, phi1 , p2, f2, phi2, p3, f3, phi3 = p 
   return C + p1 * np.sin(f1 * t + phi1) + p2 * np.sin(f2 * t + phi2) +p3 * np.sin(f3 * t + phi3) 

def error(p, y, t): 
   return y - model(p, t) 

def fit(y, t): 
   p0 = [y.mean(), 0, 2 * np.pi/11, 0, 0, 2 * np.pi/22, 0, 0, 2 * np.pi/100, 0] 
   params = leastsq(error, p0, args=(y, t))[0] 
   return params 

data_loader = sm.datasets.sunspots.load_pandas() 
sunspots = data_loader.data["SUNACTIVITY"].values 
years = data_loader.data["YEAR"].values 

cutoff = .9 * len(sunspots) 
params = fit(sunspots[:cutoff], years[:cutoff]) 
print("Params", params) 

pred = model(params, years[cutoff:]) 
actual = sunspots[cutoff:] 
print("Root mean square error", np.sqrt(np.mean((actual - pred) ** 2))) 
print("Mean absolute error", np.mean(np.abs(actual - pred))) 
print("Mean absolute percentage error", 100 * np.mean(np.abs(actual - pred)/actual)) 
mid = (actual + pred)/2 
print("Symmetric Mean absolute percentage error", 100 * np.mean(np.abs(actual - pred)/mid)) 
print("Coefficient of determination", 1 - ((actual - pred) ** 2).sum()/ ((actual - actual.mean()) ** 2).sum()) 
year_range = data_loader.data["YEAR"].values[cutoff:] 
plt.plot(year_range, actual, 'o', label="Sunspots") 
plt.plot(year_range, pred, 'x', label="Prediction") 
plt.grid(True) 
plt.xlabel("YEAR") 
plt.ylabel("SUNACTIVITY") 
plt.legend() 
plt.show() 

我们得到以下输出:

Params [ 47.18800285  28.89947419   0.56827284   6.51168446   4.55214999
0.29372077 -14.30926648 -18.16524041   0.06574835  -4.37789602]
Root mean square error 59.5619175499
Mean absolute error 44.5814573306
Mean absolute percentage error 65.1639657495
Symmetric Mean absolute percentage error 78.4477263927
Coefficient of determination -0.363525210982

第一行显示了我们尝试的模型的系数。我们的平均绝对误差为 44,这意味着我们平均在任一方向上偏离了这个量。我们还希望确定系数尽可能接近 1,以获得良好的拟合。相反,我们得到一个负值,这是不可取的。有关最终结果,请参考下图:

Generating periodic signals

傅里叶分析

傅里叶分析是基于以数学家约瑟夫·傅里叶命名的傅里叶级数而产生的。傅立叶级数是一种数学方法,用于将函数表示为正弦和余弦项的无穷级数。所讨论的函数可以是实数或复数:

Fourier analysis

傅里叶分析最有效的算法是快速傅里叶变换 ( 快速傅里叶变换)。该算法在 SciPy 和 NumPy 中实现。当应用于时间序列数据时,傅立叶分析将映射转换到频域,产生频谱。频谱将谐波显示为特定频率的明显尖峰。例如,音乐是由不同的频率组成的,音符 A 在 440 赫兹。音符 A 可以由音叉产生。我们可以用钢琴之类的乐器来演奏这个和其他的音符。白噪声是由许多频率组成的信号,这些频率被相等地表示。白光是光的所有可见频率的混合,也同样表现出来。

在下面的例子中,我们将导入两个函数(参考ch-07.ipynb):

from scipy.fftpack import rfft 
from scipy.fftpack import fftshift 

rfft()功能对实值数据进行快速傅立叶变换。我们也可以使用fft()函数,但是它在这个特定的数据集上给出了一个警告。fftshift()功能将零频率分量(数据的平均值)移动到频谱的中间,以便更好地可视化。我们还将看一下正弦波,因为这很容易理解。创建正弦波并对其应用快速傅立叶变换:

t = np.linspace(-2 * np.pi, 2 * np.pi, len(sunspots)) 
mid = np.ptp(sunspots)/2 
sine = mid + mid * np.sin(np.sin(t)) 

sine_fft = np.abs(fftshift(rfft(sine))) 
print "Index of max sine FFT", np.argsort(sine_fft)[-5:] 

以下输出显示了对应于最大振幅的指数:

Index of max sine FFT [160 157 166 158 154]

对太阳黑子数据进行快速傅立叶变换:

transformed = np.abs(fftshift(rfft(sunspots))) 
print "Indices of max sunspots FFT", np.argsort(transformed)[-5:] 

光谱中的五个最大峰值可以在以下指数中找到:

Indices of max sunspots FFT [205 212 215 209 154]

最大的山峰也位于 154。最终结果见下图:

Fourier analysis

完整的代码位于本书代码包的ch-07.ipynb文件中:

import numpy as np 
import statsmodels.api as sm 
import matplotlib.pyplot as plt 
from scipy.fftpack import rfft 
from scipy.fftpack import fftshift 

data_loader = sm.datasets.sunspots.load_pandas() 
sunspots = data_loader.data["SUNACTIVITY"].values 

t = np.linspace(-2 * np.pi, 2 * np.pi, len(sunspots)) 
mid = np.ptp(sunspots)/2 
sine = mid + mid * np.sin(np.sin(t)) 

sine_fft = np.abs(fftshift(rfft(sine))) 
print("Index of max sine FFT", np.argsort(sine_fft)[-5:]) 

transformed = np.abs(fftshift(rfft(sunspots))) 
print("Indices of max sunspots FFT", np.argsort(transformed)[-5:]) 

plt.subplot(311) 
plt.plot(sunspots, label="Sunspots") 
plt.plot(sine, lw=2, label="Sine") 
plt.grid(True) 
plt.legend() 
plt.subplot(312) 
plt.plot(transformed, label="Transformed Sunspots") 
plt.grid(True) 
plt.legend() 
plt.subplot(313) 
plt.plot(sine_fft, lw=2, label="Transformed Sine") 
plt.grid(True) 
plt.legend() 
plt.show() 

光谱分析

在前一节中,我们绘制了数据集的振幅谱。物理信号的功率谱可视化了信号的能量分布。我们可以很容易地修改代码来绘制功率谱,只需将值平方如下:

plt.plot(transformed ** 2, label="Power Spectrum") 

相位谱将相位(正弦函数的初始角度)可视化,可以绘制如下:

plt.plot(np.angle(transformed), label="Phase Spectrum") 

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

Spectral analysis

完整代码请参考本书代码包中的ch-07.ipynb文件。

过滤

滤波是一种信号处理,包括去除或抑制信号的一部分。在应用快速傅立叶变换后,我们可以过滤高频或低频,或者我们可以尝试去除白噪声。白噪声是具有恒定功率谱的随机信号,因此不包含任何有用的信息。scipy.signal包有许多用于过滤的实用程序。在本例中,我们将演示这些例程的一个小示例:

  • 中值滤波器计算滚动窗口中的中值(参见http://en.wikipedia.org/wiki/Median_filter)。它由medfilt()函数实现,该函数有一个可选的窗口大小参数。
  • 维纳滤波器使用统计数据去除噪声(参见http://en.wikipedia.org/wiki/Wiener_filter)。对于滤波器g(t)和信号s(t),通过卷积(g * [s + n])(t)计算输出。通过wiener()功能实现。该函数还有一个可选的窗口大小参数。
  • 去趋势过滤器去除趋势。这可以是线性或恒定趋势。通过detrend()功能实现。

以下代码请参考本书代码包中的ch-07.ipynb文件:

import statsmodels.api as sm 
import matplotlib.pyplot as plt 
from scipy.signal import medfilt 
from scipy.signal import wiener 
from scipy.signal import detrend 

data_loader = sm.datasets.sunspots.load_pandas() 
sunspots = data_loader.data["SUNACTIVITY"].values 
years = data_loader.data["YEAR"].values 

plt.plot(years, sunspots, label="SUNACTIVITY") 
plt.plot(years, medfilt(sunspots, 11), lw=2, label="Median") 
plt.plot(years, wiener(sunspots, 11), '--', lw=2, label="Wiener") 
plt.plot(years, detrend(sunspots), lw=3, label="Detrend") 
plt.xlabel("YEAR") 
plt.grid(True) 
plt.legend() 
plt.show() 

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

Filtering

总结

在本章中,时间序列示例使用了年太阳黑子周期数据。

您了解到,在过去的固定时间段内,在同一时间序列中,试图推导一个值与另一个数据点或数据点组合之间的关系是很常见的。

移动平均值指定了以前看到的数据的窗口,每次窗口向前滑动一个周期时,移动平均值就会被平均。在 Pandas 应用编程接口中,DataFrame.rolling()函数为窗口函数功能提供了与不同窗口函数对应的不同的win_type字符串参数值。

协整类似于相关性,是定义两个时间序列相关性的度量。在回归设置中,我们经常遇到过度拟合的问题。当我们完全适合一个样本时,这个问题就出现了,当我们引入新的数据点时,这个样本表现不佳。为了评估模型,我们可以计算适当的评估指标。

数据库是数据分析的重要工具。关系数据库从 20 世纪 70 年代就已经出现了。最近,NoSQL 数据库已经成为一个可行的选择。下一章,第 8 章使用数据库,包含关于各种数据库(关系数据库和 NoSQL 数据库)和相关应用编程接口的信息。

八、使用数据库

本章介绍各种数据库(关系数据库和 NoSQL 数据库)以及相关的应用编程接口。一个关系数据库是一个包含由数据项之间的关系组织的数据的表的集合的数据库。可以在表中的每一行和另一个表中的一行之间建立关系。关系也可以是表内的列之间的关系(显然,表内的列必须是相关的,例如,客户表中的名称列和地址列)以及其他表中的列之间的连接。

不仅是 SQL ( NoSQL )数据库在大数据和 web 应用中被频繁使用。NoSQL 系统可能允许使用类似 SQL 的查询语言。NoSQL 数据库允许以比关系模型更灵活的方式存储数据。这可能意味着没有数据库模式或灵活的数据库模式。当然,灵活性和速度可能是有代价的,例如对一致事务的有限支持。NoSQL 数据库可以使用字典样式或面向列的样式存储数据,或者将其存储为文档、对象、图形、元组或其组合。本章的主题如下:

  • 使用 sqlite3 实现轻量级访问
  • 从 Pandas 那里访问数据库
  • sqllcemy(SQL 语法)
  • 小马蛇
  • 面向懒人的数据集数据库
  • PyMongo 和 MongoDB
  • Redis 中的回滚数据
  • 将数据存储在 memcached 中
  • 阿帕奇·卡桑德拉

SQLite 3 轻量级访问

SQLite 是一个非常流行的关系数据库。它非常轻量级,被许多应用程序使用,例如像 Mozilla Firefox 这样的网络浏览器。安卓系统中的大多数应用程序都使用 SQLite 作为数据存储。

标准 Python 发行版中的sqlite3模块可用于处理 SQLite 数据库。借助sqlite3,我们可以将数据库存储在文件中,也可以将其保存在内存中。对于这个例子,我们将做后者。导入sqlite3如下:

import sqlite3

需要连接到数据库才能继续。如果我们想将数据库存储在一个文件中,我们可以提供一个文件名。相反,请执行以下操作:

with sqlite3.connect(":memory:") as con:

with语句是标准的 Python,依赖于特殊上下文管理器类中__exit__()方法的存在。使用此语句,我们不需要显式关闭连接。上下文管理器会自动关闭连接。连接到数据库后,我们需要一个游标,顺便说一下,这通常是它处理数据库的方式。数据库光标至少在概念上类似于文本编辑器中的光标。我们还需要关闭光标。按如下方式创建光标:

c = con.cursor() 

我们现在可以立即创建一个表。通常,您必须先创建一个数据库,或者让数据库专家为您创建。在本章中,您不仅需要了解 Python,还需要了解 SQL。 SQL 是数据库查询和操作的专用语言。本章没有足够的篇幅来完整描述 SQL,但是基本的 SQL 应该足够容易让你学会(更多信息,请访问http://www.w3schools.com/sql/)。为了创建一个表,我们将一个 SQL 字符串传递给游标,如下所示:

c.execute('''CREATE TABLE sensors(date text, city text,  
code text, sensor_id real, temperature real)''') 

这应该会创建一个包含几个名为sensors的列的表。在该字符串中,textreal是对应于字符串和数值的数据类型。我们可以相信表的创建工作正常。如果出了问题,我们会得到一个错误。在数据库中列出表是依赖于数据库的。通常有一个特殊的表或一组表包含关于用户表的元数据。按如下方式列出 SQLite 表:

for table in c.execute("SELECT name FROM sqlite_master  
WHERE type = 'table'"): 
print("Table", table[0]) 

不出所料,我们得到了以下输出:

Table sensors

让我们插入和查询一些随机数据如下:

c.execute("INSERT INTO sensors VALUES ('2016-11-05', 
'Utrecht','Red',42,15.14)") 
c.execute("SELECT * FROM sensors") 
print(c.fetchone()) 

我们插入的记录应打印如下:

(u'2016-11-05', u'Utrecht', u'Red', 42.0, 15.14)

当我们不再需要桌子时,我们可以把它放下。这很危险,所以你必须绝对确定你不需要这张桌子。一旦表被删除,除非对其进行备份,否则无法恢复。删除该表,并显示删除后的表数,如下所示:

con.execute("DROP TABLE sensors") 
print("# of tables", c.execute("SELECT COUNT(*)  
FROM sqlite_master WHERE type = 'table'").fetchone()[0]) 

我们得到以下输出:

# of tables 0

以下代码请参考本书代码包中的ch-08.ipynb文件:

import sqlite3 

with sqlite3.connect(":memory:") as con: 
    c = con.cursor() 
    c.execute('''CREATE TABLE sensors(date text, city text,  
code text, sensor_id real, temperature real)''') 

    for table in c.execute("SELECT name FROM sqlite_master  
WHERE type = 'table'"): 
        print("Table", table[0]) 

    c.execute("INSERT INTO sensors VALUES ('2016-11-05', 
'Utrecht','Red',42,15.14)") 
    c.execute("SELECT * FROM sensors") 
    print(c.fetchone()) 
    con.execute("DROP TABLE sensors") 

    print("# of tables", c.execute("SELECT COUNT(*)  
FROM sqlite_master WHERE type = 'table'").fetchone()[0]) 

    c.close() 

访问 Pandas 的数据库

我们可以给 Pandas 一个数据库连接,比如前面例子中的那个,或者一个 SQLAlchemy 连接。我们将在本章后面的部分讨论后者。我们将加载活动数据的 statsmodels,就像我们在上一章第 7 章信号处理和时间序列中所做的那样:

  1. Create a list of tuples to form the Pandas DataFrame:

            rows = [tuple(x) for x in df.values] 
    
    

    与前面的示例相反,创建表时不指定数据类型:

                con.execute("CREATE TABLE sunspots(year, sunactivity)") 
    
    
  2. The executemany() method executes multiple statements; in this case, we will be inserting records from a list of tuples. Insert all the rows into the table and show the row count as follows:

            con.executemany("INSERT INTO sunspots(year, sunactivity) VALUES 
            (?, ?)", rows) 
            c.execute("SELECT COUNT(*) FROM sunspots") 
            print(c.fetchone()) 
    
    

    表格中的行数打印如下:

                (309,)
    
    
  3. The rowcount attribute of the result of an execute() call gives the number of affected rows. This attribute is somewhat quirky and depends on your SQLite version. On the other hand, an SQL query, as shown in the previous code snippet, is unambiguous. Delete the records where the number of events is more than 20:

            print("Deleted", con.execute("DELETE FROM sunspots where 
            sunactivity > 20").rowcount, "rows") 
    
    

    应打印以下内容:

                Deleted 217 rows
    
    
  4. 如果我们有 Pandas 的数据库连接,我们可以执行查询并使用read_sql()函数返回 Pandas 数据帧。选择记录直到1732,如下所示:

       print(read_sql("SELECT * FROM sunspots where year < 1732", con)) 

最终结果是以下 Pandas 数据帧:

    year  sunactivity 
0   1700            5 
1   1701           11 
2   1702           16 
3   1707           20 
4   1708           10 
5   1709            8 
6   1710            3 
7   1711            0 
8   1712            0 
9   1713            2 
10  1714           11 
11  1723           11 

[12 rows x 2 columns] 

以下代码请参考本书代码包中的ch-08.ipynb文件:

import statsmodels.api as sm 
from pandas.io.sql import read_sql 
import sqlite3 

with sqlite3.connect(":memory:") as con: 
    c = con.cursor() 

    data_loader = sm.datasets.sunspots.load_pandas() 
    df = data_loader.data 
    rows = [tuple(x) for x in df.values] 

    con.execute("CREATE TABLE sunspots(year, sunactivity)") 
    con.executemany("INSERT INTO sunspots(year, sunactivity) VALUES (?, 
?)", rows) 
    c.execute("SELECT COUNT(*) FROM sunspots") 
    print(c.fetchone()) 
    print("Deleted", con.execute("DELETE FROM sunspots where sunactivity > 20").rowcount, "rows") 

    print(read_sql("SELECT * FROM sunspots where year < 1732", con)) 
    con.execute("DROP TABLE sunspots") 

    c.close() 

SQL 语法

SQLAlchemy 以其基于设计模式的对象关系映射 ( ORM )而闻名,其中 Python 类被映射到数据库表。实际上,这意味着增加了一个额外的抽象层,因此我们使用 SQLAlchemy API 与数据库对话,而不是发出 SQL 命令。SQLAlchemy 负责幕后的细节。缺点是必须学习 API,可能要付出很小的性能代价。在本节中,您将学习如何设置 SQLAlchemy,以及如何用 SQLAlchemy 填充和查询数据库。

安装和设置 SQLAlchemy

以下是安装 SQLAlchemy 的命令:

$ pip3 install sqlalchemy

在撰写本文时,SQLAlchemy 的最新版本是 1.1.4。SQLAlchemy 的下载页面在http://www.sqlalchemy.org/download.html提供,链接到安装程序和代码库。SQLAlchemy 在http://www.sqlalchemy.org/library.html也有大量可用的文档。

SQLAlchemy 要求我们定义一个超类,如下所示:

from sqlalchemy.ext.declarative import declarative_base 
Base = declarative_base() 

在这一节和下一节中,我们将使用一个包含两个表的小型数据库。第一个表定义了一个观察站。第二个表表示工作站中的传感器。每个工作站都有零个、一个或多个传感器。电台由一个整数标识来标识,该标识由数据库自动生成。电台也是由一个名字来标识的,这个名字是唯一的,也是强制性的。

传感器也有一个整数标识。我们跟踪传感器测量的最后一个值。这个值可以有一个相关的乘数。本节描述的设置在本书的代码包中的ch-08.ipynb文件中表达(您不必运行这个脚本,但本章后面的代码会用到它):

from sqlalchemy import Column, ForeignKey, Integer, String, Float 
from sqlalchemy.ext.declarative import declarative_base 
from sqlalchemy.orm import relationship 
from sqlalchemy import create_engine 
from sqlalchemy import UniqueConstraint 

Base = declarative_base() 
class Station(Base): 
    __tablename__ = 'station' 
    id = Column(Integer, primary_key=True) 
    name = Column(String(14), nullable=False, unique=True) 

    def __repr__(self): 
        return "Id=%d name=%s" %(self.id, self.name) 

class Sensor(Base): 
    __tablename__ = 'sensor' 
    id = Column(Integer, primary_key=True) 
    last = Column(Integer) 
    multiplier = Column(Float) 
    station_id = Column(Integer, ForeignKey('station.id')) 
    station = relationship(Station) 

    def __repr__(self): 
       return "Id={:d} last={:d} multiplier={:.1f} station_id=
       {:d}".format( 
       self.id,self.last, self.multiplier, self.station_id)  

if __name__ == "__main__": 
    print("This script is used by code further down in this notebook.") 

用 SQLAlchemy 填充数据库

创建表将推迟到下一节。在本节中,我们将准备一个脚本来填充数据库(您不必运行这个;它由后面一节中的脚本使用)。有了DBSession对象,我们可以将数据插入到表中。也需要一个引擎,但是创建引擎也将推迟到下一节:

  1. 按如下方式创建DBSession对象:

            Base.metadata.bind = engine 
    
            DBSession = sessionmaker(bind=engine) 
            session = DBSession() 
    
    
  2. Let's create two stations:

            de_bilt = Station(name='De Bilt') 
            session.add(de_bilt) 
            session.add(Station(name='Utrecht')) 
            session.commit() 
            print("Station", de_bilt) 
    
    

    在我们提交会话之前,不会插入行。为第一站打印以下内容:

                Station Id=1 name=De Bilt
    
    
  3. Similarly, insert a Sensor record as follows:

            temp_sensor = Sensor(last=20, multiplier=.1, station=de_bilt) 
            session.add(temp_sensor) 
            session.commit() 
            print("Sensor", temp_sensor) 
    
    

    传感器在第一站;因此,我们得到以下打印输出:

               Sensor Id=1 last=20 multiplier=0.1 station_id=1
    
    

数据库填充代码可以在本书的代码包中的ch-08.ipynb文件中找到(同样,您不需要运行该代码;它被另一个脚本使用):

from sqlalchemy import create_engine 
from sqlalchemy.orm import sessionmaker 

from alchemy_entities import Base, Sensor, Station 

def populate(engine): 
    Base.metadata.bind = engine 

    DBSession = sessionmaker(bind=engine) 
    session = DBSession() 

    de_bilt = Station(name='De Bilt') 
    session.add(de_bilt) 
    session.add(Station(name='Utrecht')) 
    session.commit() 
    print("Station", de_bilt) 

    temp_sensor = Sensor(last=20, multiplier=.1, station=de_bilt) 
    session.add(temp_sensor) 
    session.commit() 
    print("Sensor", temp_sensor) 

if __name__ == "__main__": 
    print("This script is used by code further down in this notebook") 

用 SQLAlchemy 查询数据库

发动机由 URI 发动机制造,如下所示:

engine = create_engine('sqlite:///demo.db') 

在这个 URI 中,我们指定使用 SQLite,数据存储在demo.db文件中。使用我们刚刚创建的引擎创建stationsensor表:

Base.metadata.create_all(engine) 

对于 SQLAlchemy 查询,我们再次需要一个DBSession对象,如前一节所示。

选择station表中的第一行:

station = session.query(Station).first() 

按如下方式选择所有电台:

print("Query 1", session.query(Station).all()) 

输出如下:

Query 1 [Id=1 name=De Bilt, Id=2 name=Utrecht]

如下选择所有传感器:

print("Query 2", session.query(Sensor).all()) 

输出如下:

Query 2 [Id=1 last=20 multiplier=0.1 station_id=1]

选择属于第一站的第一个传感器:

print("Query 3",session.query(Sensor).filter(Sensor.station == station).one()) 

输出如下:

Query 3 Id=1 last=20 multiplier=0.1 station_id=1

我们可以用 Pandasread_sql()方法再次查询:

print(read_sql("SELECT * FROM station", engine.raw_connection()) 

您将获得以下输出:

       id     name
    0   1  De Bilt
    1   2  Utrecht

    [2 rows x 2 columns]

检查本书代码包中的ch-08.ipynb文件:

from alchemy_entities import Base, Sensor, Station 
from populate_db import populate 
from sqlalchemy import create_engine 
from sqlalchemy.orm import sessionmaker 
import os 
from pandas.io.sql import read_sql 

engine = create_engine('sqlite:///demo.db') 
Base.metadata.create_all(engine) 
populate(engine) 
Base.metadata.bind = engine 
DBSession = sessionmaker() 
DBSession.bind = engine 
session = DBSession() 

station = session.query(Station).first() 

print("Query 1", session.query(Station).all()) 
print("Query 2", session.query(Sensor).all()) 
print("Query 3", session.query(Sensor).filter(Sensor.station == station).one()) 
print(read_sql("SELECT * FROM station", engine.raw_connection())) 

try: 
    os.remove('demo.db') 
    print("Deleted demo.db") 
except OSError: 
    pass 

Pony 蠕虫

Pony ORM 是另一个 Python ORM 包。Pony ORM 是用纯 Python 编写的,具有自动查询优化和 GUI 数据库模式编辑器。它还支持自动事务管理、自动缓存和复合键。Pony ORM 使用 Python 生成器表达式,这些表达式被翻译成 SQL。按照以下步骤安装:

$ pip3 install pony

导入我们在这个例子中需要的包。参考本书代码包中的pony_ride.py文件:

from pony.orm import Database, db_session  
import statsmodels.api as sm 

创建内存中的 SQLite 数据库:

db = Database('sqlite', ':memory:') 

加载sunspots数据并用 PandasDataFrame.to_sql功能写入数据库:

with db_session: 
    data_loader = sm.datasets.sunspots.load_pandas() 
    df = data_loader.data 
    df.to_sql("sunspots", db.get_connection()) 
    print(db.select("count(*) FROM sunspots")) 

sunspots表中的行数打印如下:

[309]

数据集——懒人数据库

数据集是一个 Python 库,基本上是 SQLAlchemy 的包装器。它号称好用到连懒人都喜欢。

按照以下步骤安装dataset:

$ pip3 install dataset

创建一个 SQLite 内存数据库并连接到它:

import dataset 
db = dataset.connect('sqlite:///:memory:') 

创建一个名为books的表格:

table = db["books"] 

实际上,数据库中的表还没有创建,因为我们没有指定任何列。我们只创建了一个相关对象。通过调用insert()方法自动创建表模式。给insert()方法字典加上书名:

table.insert(dict(, author='Ivan Idris')) 
table.insert(dict(,  
author='Ivan Idris')) 
table.insert(dict(,  
author='Ivan Idris')) 

按如下方式打印表格中的行:

for row in db['books']: 
   print(row) 

将打印以下内容:

<dataset.persistence.util.ResultIter object at 0x10d4bf550>  
OrderedDict([('id', 1), ('title', "NumPy Beginner's Guide"), ('author', 'Ivan Idris')])  
OrderedDict([('id', 2), ('title', 'NumPy Cookbook'), ('author', 'Ivan Idris')])  
OrderedDict([('id', 3), ('title', 'Learning NumPy'), ('author', 'Ivan Idris')]) 

我们可以很容易地用下面的行显示数据库中的表:

print("Tables", db.tables) 

以下是前面代码的输出:

Tables ['books']

以下是本书代码包中ch-08.ipynb文件的内容:

import dataset 

db = dataset.connect('sqlite:///:memory:') 
table = db["books"] 
table.insert(dict(, author='Ivan Idris')) 
table.insert(dict(, author='Ivan Idris')) 
table.insert(dict(, author='Ivan Idris')) 
for row in db['books']: 
   print(row) 

print("Tables", db.tables) 

PyMongo 和 MongoDB

是一个面向 NoSQL 文档的数据库。这些文档以类似 JSON 的 BSON 格式存储。您可以从http://www.mongodb.org/downloads下载 MongoDB 发行版。安装应该只是打开一个压缩的归档文件。撰写本文时的版本是 3.4.0。在发行版的bin目录中,我们会找到mongod文件,启动服务器。蒙古数据库希望找到一个/data/db目录。这是存储数据的目录。我们可以从命令行指定另一个目录,如下所示:

$ mkdir /tmp/db

从包含其二进制可执行文件的目录启动数据库:

./mongod --dbpath /tmp/db

我们需要保持这个进程运行,以便能够查询数据库。 PyMongo 是 MongoDB 的 Python 驱动。按照以下步骤安装 PyMongo:

$ pip3 install pymongo

连接到 MongoDB 测试数据库:

from pymongo import MongoClient 
client = MongoClient() 
db = client.test_database 

请记住,我们可以从 Pandas 数据帧创建 JSON。创建 JSON 并将其存储在 MongoDB 中:

data_loader = sm.datasets.sunspots.load_pandas() 
df = data_loader.data 
rows = json.loads(df.T.to_json()).values() 
db.sunspots.insert(rows) 

查询我们刚刚创建的文档:

cursor = db['sunspots'].find({}) 
df =  pd.DataFrame(list(cursor)) 
print(df) 

这将打印整个 Pandas 数据帧。参考本书代码包中的ch-08.ipynb文件:

from pymongo import MongoClient 
import statsmodels.api as sm 
import json 
import pandas as pd 

client = MongoClient() 
db = client.test_database 

data_loader = sm.datasets.sunspots.load_pandas() 
df = data_loader.data 
rows = json.loads(df.T.to_json()).values() 
db.sunspots.insert_many(rows) 

cursor = db['sunspots'].find({}) 
df =  pd.DataFrame(list(cursor)) 
print(df) 

db.drop_collection('sunspots') 

复读资料中的

远程词典服务器 ( Redis )是一个内存中的键值数据库,用 c 编写,在内存模式下,Redis 速度极快,读写速度几乎一样快。Redis 遵循发布/订阅模型,并使用 Lua 脚本作为存储过程。发布/订阅利用客户端可以订阅的频道来接收消息。在写这本书的时候,我已经安装了 Redis 3 . 2 . 6 版本。Redis 可以从http://redis.io/的 Redis 主页下载。安装 Redis 分发版后,发出以下命令运行服务器:

$ src/redis-server

现在让我们安装一个 Python 驱动程序:

$ pip3 install redis

当你意识到 Redis 是一个巨大的字典时,使用它是非常容易的。然而,Redis 确实有其局限性。有时,将复杂的对象存储为 JSON 字符串(或其他格式)会很方便。这就是我们要用 Pandas 数据框做的。按照以下方式连接到 Redis:

r = redis.StrictRedis()

使用 JSON 字符串创建键值对:

r.set('sunspots', data) 

用以下行检索数据:

blob = r.get('sunspots') 

代码很简单,在本书代码包的ch-08.ipynb文件中给出:

import redis 
import statsmodels.api as sm 
import pandas as pd 

r = redis.StrictRedis() 
data_loader = sm.datasets.sunspots.load_pandas() 
df = data_loader.data 
data = df.T.to_json() 
r.set('sunspots', data) 
blob = r.get('sunspots') 
print(pd.read_json(blob)) 

在 memcache 中存储数据

memcache 是一个内存中的键值数据库存储,就像 Redis 一样。安装并运行 memcached 服务器后,使用以下命令安装 memcache Python 客户端:

$ pip3 install python3-memcache

ch-08.ipynb文件中的代码创建一个 memcache 客户端,然后将数据帧存储到 memcache,自动过期值为600秒。该代码类似于 Redis 的代码:

import memcache 
import statsmodels.api as sm 
import pandas as pd 

client = memcache.Client([('127.0.0.1', 11211)]) 
data_loader = sm.datasets.sunspots.load_pandas() 
df = data_loader.data 
data = df.T.to_json() 
client.set('sunspots', data, time=600) 
print("Stored data to memcached, auto-expire after 600 seconds") 
blob = client.get('sunspots') 
print(pd.read_json(blob)) 

阿帕奇·卡珊德拉

Apache Cassandra 混合了键值和传统关系数据库的特性。在传统的关系数据库中,表的列是固定的。然而,在 Cassandra 中,同一表中的行可以有不同的列。因此,Cassandra 是面向列的,因为它允许每行都有一个灵活的模式。列被组织在所谓的列族中,这相当于关系数据库中的表。Cassandra 不支持连接和子查询。卡珊德拉可以从http://cassandra.apache.org/download/下载。在撰写本书时,我们使用了 Cassandra 版本。请参考http://wiki.apache.org/cassandra/GettingStarted开始:

  1. 从命令行运行服务器,如下所示:

    $ bin/cassandra-f
    
    
  2. 创建在conf/cassandra.yaml中列出的目录,或者如下调整它们:

    data_file_directories:
    /tmp/lib/cassandra/data
    commitlog_directory: /tmp/lib/cassandra/commitlog
    saved_caches_directory: /tmp/lib/cassandra/saved_caches
    
    
  3. 如果不想保留数据,以下命令很有意义:

    $ mkdir -p /tmp/lib/cassandra/data
    $ mkdir -p /tmp/lib/cassandra/commitlog
    $ mkdir -p /tmp/lib/cassandra/saved_caches
    
    
  4. 使用以下命令安装 Python 驱动程序:

    $ pip3 install cassandra-driver
    
    
  5. 现在是时候编码了。连接到集群并创建会话,如下所示:

            cluster = Cluster()
            session = cluster.connect()
    
    
  6. 卡珊德拉有钥匙空间的概念。键空间保存表。卡珊德拉有自己的查询语言卡珊德拉查询语言 ( CQL )。CQL 和 SQL 非常相似。创建键空间并设置会话以使用它:

            session.execute("CREATE KEYSPACE IF NOT EXISTS mykeyspace WITH   
            REPLICATION = { 'class' : 'SimpleStrategy', 
            'replication_factor' : 1 };")
            session.set_keyspace('mykeyspace')
    
    
  7. 现在,为sunspots数据创建一个表:

            session.execute("CREATE TABLE IF NOT EXISTS sunspots (year   
            decimal PRIMARY KEY, sunactivity decimal);")
    
    
  8. 创建一个我们将在循环中使用的语句,将数据行作为元组插入:

            query = SimpleStatement( 
                "INSERT INTO sunspots (year, sunactivity) VALUES (%s, %s)", 
                consistency_level=ConsistencyLevel.QUORUM) 
    
    
  9. 下一行插入数据:

            for row in rows: 
                session.execute(query, row) 
    
    
  10. Get the count of the rows in the table:

```py
        rows=session.execute("SELECT COUNT(*) FROM sunspots") 
        for row in rows: 
            print(row) 

```

这将按如下方式打印行数:

```py
        [Row(count=309)]

```
  1. Drop the keyspace and shut down the cluster:
```py
        session.execute('DROP KEYSPACE mykeyspace') 
        cluster.shutdown() 

```

参考本书代码包中的`ch-08.ipynb`文件:

```py
from cassandra import ConsistencyLevel 
from cassandra.cluster import Cluster 
from cassandra.query import SimpleStatement 
import statsmodels.api as sm 

cluster = Cluster() 
session = cluster.connect() 
session.execute("CREATE KEYSPACE IF NOT EXISTS mykeyspace WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 };") 
session.set_keyspace('mykeyspace') 
session.execute("CREATE TABLE IF NOT EXISTS sunspots (year decimal PRIMARY KEY, sunactivity decimal);") 

query = SimpleStatement( 
    "INSERT INTO sunspots (year, sunactivity) VALUES (%s, %s)", 
    consistency_level=ConsistencyLevel.QUORUM) 

data_loader = sm.datasets.sunspots.load_pandas() 
df = data_loader.data 
rows = [tuple(x) for x in df.values] 
for row in rows: 
    session.execute(query, row) 

rows=session.execute("SELECT COUNT(*) FROM sunspots") 
for row in rows: 
    print(row) 

session.execute('DROP KEYSPACE mykeyspace')  
cluster.shutdown() 

```

总结

我们将每年的太阳黑子周期数据存储在不同的关系数据库和 NoSQL 数据库中。

术语关系在这里不仅仅指表之间的关系;首先,它与表内各列之间的关系有关;其次,它涉及表之间的连接。

标准 Python 发行版中的sqlite3模块可用于处理 SQLite 数据库。我们可以给 Pandas 一个 SQLite 数据库连接或者 SQLAlchemy 连接。

SQLAlchemy 以其基于设计模式的 ORM 而闻名,其中 Python 类被映射到数据库表。ORM 模式是适用于其他面向对象编程语言的通用架构模式。SQLAlchemy 抽象出使用数据库的技术细节,包括编写 SQL。

MongoDB 是一个基于文档的存储,可以保存大量的数据。

在内存模式下,Redis 速度极快,写和读几乎一样快。Redis 是一个键值存储,其功能类似于 Python 字典。

Apache Cassandra 混合了键值和传统关系数据库的特性。它是面向列的,它的列被组织成系列,这相当于关系数据库中的表。Apache Cassandra 中的行不绑定到一组特定的列。

下一章第 9 章分析文本数据和社交媒体,介绍了明文数据的分析技术。许多组织和互联网上都有明文数据。一般来说,明文数据是非结构化的,需要一种不同于列表和清理数据的方法。对于分析,我们将使用 NLTK——一个开源的 Python 包。NLTK 非常全面,自带数据集。

九、分析文本数据和社交媒体

在前面的章节中,我们重点分析了结构化数据,主要是表格格式的数据。除了结构化数据,明文是当今另一种主要的数据形式。文本分析包括词频分布分析、模式识别、标记、链接和关联分析、情感分析和可视化。Python 中用于文本分析的主要库之一是自然语言工具包 ( NLTK )库。NLTK 附带了一个名为语料库的样本文本集合。scikit-learn 库还包含文本分析工具,我们将在本章中简要介绍。还将讲述一个网络分析的小例子。本章将讨论以下主题:

  • 安装 NLTK
  • 关于 NLTK
  • 筛选出停用词、名称和数字
  • 单词包模型
  • 分析词频
  • 朴素贝叶斯分类
  • 情感分析
  • 创建单词云
  • 社交网络分析

安装 NLTK

让我们使用以下命令安装本章所需的库:

$ pip3 install nltk  scikit-learn

关于 NLTK

NLTK 是一个 Python API,用于分析用自然语言(如英语)编写的文本。NLTK 创建于 2001 年,最初是作为一种教学工具。

虽然我们在前一节中安装了 NLTK,但是我们还没有完成;我们仍然需要下载 NLTK 语料库。下载量比较大(1.8 GB 左右);然而,我们只需要下载一次。除非你确切知道你需要哪些语料库,否则最好下载所有可用的语料库。从 Python 外壳下载语料库,如下所示:

$ python3
>>> import nltk 
>>> nltk.download()

应该会出现一个图形用户界面应用程序,您可以在其中指定目的地和要下载的文件。

About NLTK

如果您是 NLTK 的新手,选择默认选项并下载所有内容是最方便的。在这一章中,我们将需要单词、电影评论、名字和古腾堡语料库。鼓励读者遵循ch-09.ipynb文件中的章节。

过滤掉停止词、名称和数字

停用词是文本中信息价值非常低的常用词。去除停用词是文本分析中的一种常见做法。NLTK 拥有多种语言的 stopwords 语料库。加载英语单词语料库并打印一些单词:

sw = set(nltk.corpus.stopwords.words('english')) 
print("Stop words:", list(sw)[:7]) 

打印以下常用单词:

Stop words: ['between', 'who', 'such', 'ourselves', 'an', 'ain', 'ours'] 

请注意,这个语料库中的所有单词都是小写的。

NLTK 还有一个古腾堡语料库。古腾堡项目是一个数字图书图书馆,大部分是版权过期的图书,可以在互联网上免费获得(见http://www.gutenberg.org/)。

加载古腾堡语料库并打印它的一些文件名:

gb = nltk.corpus.gutenberg 
print("Gutenberg files:\n", gb.fileids()[-5:]) 

一些印刷的标题可能是你所熟悉的:

Gutenberg files:  ['milton-paradise.txt', 'shakespeare-caesar.txt', 'shakespeare-hamlet.txt', 'shakespeare-macbeth.txt', 'whitman-leaves.txt']

milton-paradise.txt文件中提取前几个句子,我们稍后会过滤:

text_sent = gb.sents("milton-paradise.txt")[:2] 
print("Unfiltered:", text_sent) 

打印以下句子:

 Unfiltered [['[', 'Paradise', 'Lost', 'by', 'John', 'Milton', 
 '1667',  ']'], ['Book', 'I']]

现在,按如下方式过滤掉停止词:

for sent in text_sent: 
    filtered = [w for w in sent if w.lower() not in sw] 
    print("Filtered:\n", filtered) 

对于第一句,我们得到以下输出:

Filtered ['[', 'Paradise', 'Lost', 'John', 'Milton', '1667', ']']

如果我们将此与前面的片段进行比较,我们注意到单词by已经被过滤掉了,因为它是在 stopwords 语料库中找到的。有时候,我们也想去掉数字和名字。我们可以根据词性 ( POS )标签删除单词。在该标记方案中,数字对应于基数 ( CD )标记。名称对应专有名词单数 ( NNP )标签。标记是一个基于启发式的不精确的过程。这是一个值得整本书讨论的大话题。用pos_tag()功能标记过滤后的文本:

tagged = nltk.pos_tag(filtered) 
print("Tagged:\n", tagged) 

对于我们的文本,我们得到以下标签:

Tagged [('[', 'NN'), ('Paradise', 'NNP'), ('Lost', 'NNP'), ('John', 'NNP'), ('Milton', 'NNP'), ('1667', 'CD'), (']', 'CD')]

pos_tag()函数返回元组列表,其中每个元组中的第二个元素是标签。如您所见,有些单词被标记为 NNP,尽管它们可能不应该被标记。这里的启发是,如果单词的第一个字符是大写的,则将单词标记为 NNP。如果我们把所有的单词都设为小写,我们会得到不同的结果。这是留给读者的练习。用 NNP 和 CD 标签很容易删除列表中的单词,如以下代码所述:

words= [] 
    for word in tagged: 
        if word[1] != 'NNP' and word[1] != 'CD': 
           words.append(word[0])  

    print(words) 

请看一下本书代码包中的ch-09.ipynb 文件:

import nltk

sw = set(nltk.corpus.stopwords.words('english'))
print(“Stop words:", list(sw)[:7])

gb = nltk.corpus.gutenberg
print(“Gutenberg files:\n", gb.fileids()[-5:])

text_sent = gb.sents("milton-paradise.txt")[:2]
print(“Unfiltered:", text_sent)

for sent in text_sent:
    filtered = [w for w in sent if w.lower() not in sw]
    print("Filtered:\n", filtered)
    tagged = nltk.pos_tag(filtered)
    print("Tagged:\n", tagged)

    words= []
    for word in tagged:
        if word[1] != 'NNP' and word[1] != 'CD':
           words.append(word[0]) 

    print(“Words:\n",words)

单词包模型

单词包模型中,我们从文档中创建一个包含文档中单词的包。在这个模型中,我们不关心词序。对于文档中的每个单词,我们都会计算出现的次数。通过这些字数统计,我们可以进行统计分析,例如,识别电子邮件中的垃圾邮件。

如果我们有一组文档,我们可以将语料库中每个唯一的单词视为一个特征;这里,特征表示参数或变量。使用所有的字数,我们可以为每个文档建立一个特征向量;向量在这里是数学意义上使用的。如果语料库中有一个词,但文档中没有,则该特征的值为0。令人惊讶的是,NLTK 目前没有一个方便的工具来创建特征向量。然而,机器学习 Python 库 scikit-learn 确实有一个我们可以使用的CountVectorizer类。在下一章第 10 章预测性分析和机器学习中,我们将用 scikit-learn 做更多的事情。

从 NLTK 古腾堡语料库中加载两个文本文档:

hamlet = gb.raw("shakespeare-hamlet.txt") 
macbeth = gb.raw("shakespeare-macbeth.txt") 

通过省略英文停止词来创建特征向量:

cv = sk.feature_extraction.text.CountVectorizer(stop_words='english') 
print("Feature vector:\n", cv.fit_transform([hamlet, macbeth]).toarray())  

这是两个文档的特征向量:

Feature vector:   
   [[ 1  0  1 ..., 14  0  1]   
    [ 0  1  0 ...,  1  1  0]] 

打印我们发现的一小部分功能(独特的词语):

print("Features:\n", cv.get_feature_names()[:5]) 

这些功能按字母顺序排列:

Features: 
  ['1599',  '1603',  'abhominably',  'abhorred',  'abide']

请看一下本书代码包中的ch-09.ipynb 文件:

import nltk
import sklearn as sk

hamlet = gb.raw("shakespeare-hamlet.txt")
macbeth = gb.raw("shakespeare-macbeth.txt")

cv = sk.feature_extraction.text.CountVectorizer(stop_words='english')

print(“Feature vector:\n”, cv.fit_transform([hamlet, macbeth]).toarray())
print("Features:\n", cv.get_feature_names()[:5])

分析词频

NLTK FreqDist类封装了一个单词字典,并对给定的单词列表进行计数。载入威廉·莎士比亚的《凯撒大帝》古腾堡文本。让我们过滤掉停用词和标点符号:

punctuation = set(string.punctuation) 
filtered = [w.lower() for w in words if w.lower() not in sw and w.lower() not in punctuation] 

创建一个FreqDist对象,并以最高频率打印相关的键和值:

fd = nltk.FreqDist(filtered) 
print("Words", fd.keys()[:5]) 
print("Counts", fd.values()[:5]) 

键和值打印如下:

Words ['d', 'caesar', 'brutus', 'bru', 'haue']
Counts [215, 190, 161, 153, 148]

当然,这个列表中的第一个单词不是英语单词,所以我们可能需要添加单词最少有两个字符的启发。NLTK FreqDist类允许类似字典的访问,但是它也有方便的方法。获取出现频率最高的单词和相关计数:

print("Max", fd.max()) 
print("Count", fd['d']) 

以下结果不应该令人惊讶:

Max d
Count 215

到目前为止,分析集中在单个单词上,但是我们可以将分析扩展到单词对和三元组。这些也被称为二元模型和三元模型。我们可以通过bigrams()trigrams()功能找到它们。重复分析,但这次是针对二元模型:

fd = nltk.FreqDist(nltk.bigrams(filtered)) 
print("Bigrams", fd.keys()[:5]) 
print("Counts", fd.values()[:5]) 
print("Bigram Max", fd.max()) 
print("Bigram count", fd[('let', 'vs')]) 

应打印以下输出:

Bigrams [('let', 'vs'), ('wee', 'l'), ('mark', 'antony'), ('marke', 'antony'), ('st', 'thou')]
    Counts [16, 15, 13, 12, 12]
    Bigram Max ('let', 'vs')
    Bigram count 16

查看本书代码包中的ch-09.ipynb文件:

import nltk 
import string 

gb = nltk.corpus.gutenberg 
words = gb.words("shakespeare-caesar.txt") 

sw = set(nltk.corpus.stopwords.words('english')) 
punctuation = set(string.punctuation) 
filtered = [w.lower() for w in words if w.lower() not in sw and w.lower() not in punctuation] 
fd = nltk.FreqDist(filtered) 
print("Words", fd.keys()[:5]) 
print("Counts", fd.values()[:5]) 
print("Max", fd.max()) 
print("Count", fd['d']) 

fd = nltk.FreqDist(nltk.bigrams(filtered)) 
print("Bigrams", fd.keys()[:5]) 
print("Counts", fd.values()[:5]) 
print("Bigram Max", fd.max()) 
print("Bigram count", fd[('let', 'vs')]) 

朴素贝叶斯分类

分类算法是一种机器学习算法,用于确定给定项目的类别(类别或类型)。例如,我们可以尝试根据一些特征来确定电影的类型。在这种情况下,流派就是要预测的类。在下一章第 10 章预测性分析和机器学习中,我们将继续概述机器学习。同时,我们将讨论一种流行的算法,称为朴素贝叶斯分类,它经常用于分析文本文档。

朴素贝叶斯分类是一种基于概率论和统计学中贝叶斯定理的概率算法。贝叶斯定理阐述了如何根据新的证据来降低事件的概率。例如,假设我们有一个袋子,里面装着巧克力块和其他我们看不见的东西。我们将绘制一块黑巧克力的概率称为 P(D)。我们将把画一块巧克力的概率表示为 P(C)。当然,总概率总是 1,所以 P(D)和 P(C)最多只能是 1。贝叶斯定理指出,后验概率与先验概率乘以似然性成正比:

P(D|C)在前面的符号中是指给定的C事件的概率D。当我们没有画任何项目时,P(D) = 0.5因为我们还没有任何信息。要实际应用这个公式,我们需要知道P(C|D)P(C),或者我们必须间接确定它们。

朴素贝叶斯分类被称为朴素,因为它对特征之间的独立性做了简化假设。实际上,结果通常都很好,所以这个假设在一定程度上是合理的。最近发现这个假设有理论上的原因。然而,由于机器学习是一个快速发展的领域,所以已经发明了具有(稍微)更好性能的算法。

让我们试着把单词分类为停用词或标点符号。作为一个特点,我们将使用单词长度,因为停止词和标点符号往往很短。

该设置引导我们定义以下功能:

def word_features(word): 
   return {'len': len(word)} 

def isStopword(word): 
   return word in sw or word in punctuation 

根据古腾堡语shakespeare-caesar.txt中的单词是否是停止词来标记它们:

labeled_words = ([(word.lower(), isStopword(word.lower())) for word in words]) 
random.seed(42) 
random.shuffle(labeled_words) 
print(labeled_words[:5]) 

5 个带标签的单词将显示如下:

    [('was', True), ('greeke', False), ('cause', False), ('but', True), ('house', False)]

对于每个单词,确定其长度:

featuresets = [(word_features(n), word) for (n, word) in labeled_words] 

在前面的章节中,我们提到了过拟合,并研究了如何通过拥有一个训练和一个测试数据集来避免交叉验证。我们将在 90%的单词上训练一个朴素贝叶斯分类器,并测试剩下的 10%。创建训练和测试集,并训练数据:

cutoff = int(.9 * len(featuresets)) 
train_set, test_set = featuresets[:cutoff], featuresets[cutoff:] 
classifier = nltk.NaiveBayesClassifier.train(train_set) 

我们现在可以检查分类器如何标记集合中的单词:

classifier = nltk.NaiveBayesClassifier.train(train_set) 
print("'behold' class", classifier.classify(word_features('behold'))) 
print("'the' class", classifier.classify(word_features('the'))) 

幸运的是,这些词被恰当地分类了:

'behold' class False
'the' class True

按照以下步骤确定测试集的分类器精度:

print("Accuracy", nltk.classify.accuracy(classifier, test_set)) 

这个分类器的准确率高达 85%左右。打印信息最丰富的功能概述:

print(classifier.show_most_informative_features(5)) 

概述显示了对分类过程最有用的单词长度:

Naive Bayes classification

代码在本书代码包的ch-09.ipynb文件中:

import nltk 
import string 
import random 

sw = set(nltk.corpus.stopwords.words('english')) 
punctuation = set(string.punctuation) 

def word_features(word): 
   return {'len': len(word)} 

def isStopword(word): 
    return word in sw or word in punctuation 
gb = nltk.corpus.gutenberg 
words = gb.words("shakespeare-caesar.txt") 

labeled_words = ([(word.lower(), isStopword(word.lower())) for word in words]) 
random.seed(42) 
random.shuffle(labeled_words) 
print(labeled_words[:5]) 

featuresets = [(word_features(n), word) for (n, word) in labeled_words] 
cutoff = int(.9 * len(featuresets)) 
train_set, test_set = featuresets[:cutoff], featuresets[cutoff:] 
classifier = nltk.NaiveBayesClassifier.train(train_set) 
print("'behold' class", classifier.classify(word_features('behold'))) 
print("'the' class", classifier.classify(word_features('the'))) 

print("Accuracy", nltk.classify.accuracy(classifier, test_set)) 
print(classifier.show_most_informative_features(5)) 

情绪分析

意见挖掘情绪分析是一个热门的新研究领域,致力于自动评估在社交媒体、产品评论网站或其他论坛上表达的意见。通常,我们想知道一个观点是积极的、中立的还是消极的。这当然是分类的一种形式,如前一节所见。因此,我们可以应用任意数量的分类算法。另一种方法是半自动地(通过一些手动编辑)编写一个带有相关数字情感评分的单词列表(单词“好”可以有 5 分,“坏”可以有-5 分)。如果我们有这样一个列表,我们可以查找文本文档中的所有单词,例如,总结所有找到的情感分数。班级的数量可以超过三个,就像五星评级计划一样。

我们将把朴素贝叶斯分类应用于 NLTK 电影评论语料库,目标是将电影评论分类为正面或负面。首先,我们将加载语料库并过滤掉停用词和标点符号。这些步骤将被省略,因为我们以前执行过它们。您可以考虑更复杂的过滤方案,但请记住,过度过滤可能会损害准确性。使用categories()方法标记电影评论文档:

labeled_docs = [(list(movie_reviews.words(fid)), cat) 
        for cat in movie_reviews.categories() 
        for fid in movie_reviews.fileids(cat)] 

完整的语料库有成千上万个我们可以用作特征的独特单词。然而,使用所有这些单词可能效率低下。选择最常用单词的前 5%:

words = FreqDist(filtered) 
N = int(.05 * len(words.keys())) 
word_features = words.keys()[:N] 

对于每个文档,我们可以使用多种方法提取特征,包括以下方法:

  • 检查给定的文档是否有单词
  • 确定给定文档中一个单词的出现次数
  • 标准化字数,使最大标准化字数小于或等于 1
  • 取计数的对数加 1(避免取零的对数)
  • 将所有前面的点合并成一个指标

俗话说条条大路通罗马。当然,有些路更安全,会让你更快到达罗马。定义以下函数,该函数使用原始字数作为度量:

def doc_features(doc): 
    doc_words = FreqDist(w for w in doc if not isStopWord(w)) 
    features = {} 
    for word in word_features: 
        features['count (%s)' % word] = (doc_words.get(word, 0)) 
    return features 

我们现在可以像前面的例子一样训练我们的分类器了。达到了 78%的准确率,这是相当不错的,接近情绪分析可能达到的水平。研究发现,即使是人类也不总是对给定文档的情感达成一致(参见http://mashable.com/2010/04/19/sentiment-analysis/),因此,使用情感分析软件,我们不可能拥有 100%的完美准确性。

信息最丰富的功能如下所示:

Sentiment analysis

如果我们仔细阅读这个列表,我们会发现明显的积极词汇,如“精彩”和“杰出”。“糟糕”、“愚蠢”和“无聊”是明显的负面词汇。分析剩下的特征会很有趣。这是留给读者的练习。参考本书代码包中的sentiment.py文件:

import random 
from nltk.corpus import movie_reviews 
from nltk.corpus import stopwords 
from nltk import FreqDist 
from nltk import NaiveBayesClassifier 
from nltk.classify import accuracy 
import string 

labeled_docs = [(list(movie_reviews.words(fid)), cat) 
        for cat in movie_reviews.categories() 
        for fid in movie_reviews.fileids(cat)] 
random.seed(42) 
random.shuffle(labeled_docs) 

review_words = movie_reviews.words() 
print("# Review Words", len(review_words)) 

sw = set(stopwords.words('english')) 
punctuation = set(string.punctuation) 

def isStopWord(word): 
    return word in sw or word in punctuation 

filtered = [w.lower() for w in review_words if not isStopWord(w.lower())] 
print("# After filter", len(filtered)) 
words = FreqDist(filtered) 
N = int(.05 * len(words.keys())) 
word_features = words.keys()[:N] 

def doc_features(doc): 
    doc_words = FreqDist(w for w in doc if not isStopWord(w)) 
    features = {} 
    for word in word_features: 
        features['count (%s)' % word] = (doc_words.get(word, 0)) 
    return features 

featuresets = [(doc_features(d), c) for (d,c) in labeled_docs] 
train_set, test_set = featuresets[200:], featuresets[:200] 
classifier = NaiveBayesClassifier.train(train_set) 
print("Accuracy", accuracy(classifier, test_set)) 

print(classifier.show_most_informative_features()) 

创造字云

你可能之前看过 Wordle 或者其他软件制作的字云。如果没有,你很快就会在本章中看到它们。几个 Python 库可以创建单词云;然而,这些库似乎还不能击败 Wordle 所产生的质量。我们可以通过位于http://www.wordle.net/advanced的 Wordle 网页创建一个单词云。Wordle 需要以下格式的单词和权重列表:

Word1 : weight 
Word2 : weight 

修改上例中的代码以打印单词列表。作为一个指标,我们将使用词频并选择最高的百分比。我们不需要任何新的东西。最终代码在本书代码包的ch-09.ipynb文件中:

from nltk.corpus import movie_reviews 
from nltk.corpus import stopwords 
from nltk import FreqDist 
import string 

sw = set(stopwords.words('english')) 
punctuation = set(string.punctuation) 

def isStopWord(word): 
    return word in sw or word in punctuation 
review_words = movie_reviews.words() 
filtered = [w.lower() for w in review_words if not isStopWord(w.lower())] 

words = FreqDist(filtered) 
N = int(.01 * len(words.keys())) 
tags = words.keys()[:N] 

for tag in tags: 
    print(tag, ':', words[tag]) 

将输出复制并粘贴到 Wordle 网页中,生成以下单词云:

Creating word clouds

如果我们分析云这个词,我们可能会发现结果远没有达到我们的目的,所以我们可能想尝试更好的东西。例如,我们可以尝试做以下事情:

  • 过滤更多:我们可以去掉包含数字字符和名字的单词。NLTK 有一个names语料库我们可以使用。此外,在整个语料库中只出现一次的单词很好忽略,因为它们可能没有增加足够的信息价值。
  • 使用更好的度量标准:短语术语频率-逆文档频率 ( tf-idf )似乎是一个不错的选择。

tf-idf 度量可以为我们的语料库中的单词提供排名权重。它的值与某个单词在特定文档中出现的次数(对应于术语频率)成正比。然而,它也与语料库中的文档数量成反比(对应于文档频率的倒数),即单词出现的位置。tf-idf 值是术语频率和反向文档频率的乘积。如果我们需要自己实现 tf-idf,我们还必须考虑对数缩放。幸运的是,我们不必关心实现细节,因为 scikit-learn 有一个高效实现的TfidfVectorizer类。这个类产生一个稀疏的 SciPy 矩阵。这是一个术语文档矩阵,其中包含可用单词和文档的每个组合的 tf-idf 值。因此,对于一个包含 2000 个文档和 25000 个独特单词的语料库,我们得到一个 2,000 x 25,000 的矩阵。很多矩阵值将为零,这就是稀疏性派上用场的地方。通过对每个单词的所有 tf-idf 值求和,可以找到最终的排名权重。

您可以使用isalpha()方法和names语料库来改进过滤:

all_names = set([name.lower() for name in names.words()]) 

def isStopWord(word): 
    return (word in sw or word in punctuation) or not word.isalpha() or word in all_names 

我们将再次创建一个 NLTK FreqDist,这样我们就可以忽略只出现一次的单词。TfidfVectorizer类需要一个代表语料库中每个文档的字符串列表。

按如下方式创建列表:

for fid in movie_reviews.fileids(): 
    texts.append(" ".join([w.lower() for w in movie_reviews.words(fid) if not isStopWord(w.lower()) and words[w.lower()] > 1])) 

创建vectorizer;为了安全起见,让它忽略 stopwords:

vectorizer = TfidfVectorizer(stop_words='english') 

创建稀疏术语文档矩阵:

matrix = vectorizer.fit_transform(texts) 

将每个单词的tf-idf值相加,并将其存储在 NumPy 数组中:

sums = np.array(matrix.sum(axis=0)).ravel() 

现在,用单词等级权重创建 Pandas 数据框并排序:

ranks = [] 

for word, val in itertools.izip(vectorizer.get_feature_names(), sums): 
    ranks.append((word, val)) 

    df = pd.DataFrame(ranks, columns=["term", "tfidf"]) 
    df = df.sort(['tfidf']) 
    print(df.head()) 

最低等级值打印如下,可考虑用于过滤:

    term    tfidf
    8742            greys  0.03035
    2793      cannibalize  0.03035
    2408          briefer  0.03035
    19977  superintendent  0.03035
    14022           ology  0.03035

现在的问题是打印排名靠前的单词并将其呈现给 Wordle,以便创建以下云:

Creating word clouds

不幸的是,您必须自己运行代码才能看到与前一个单词 cloud 的颜色差异。tf-idf 度量允许更多的变化,而不仅仅是词频,因此我们得到了更多变化的颜色。此外,云中的单词似乎更相关。参考本书代码包中的ch-09.ipynb文件:

from nltk.corpus import movie_reviews 
from nltk.corpus import stopwords 
from nltk.corpus import names 
from nltk import FreqDist 
from sklearn.feature_extraction.text import TfidfVectorizer 
import itertools 
import pandas as pd 
import numpy as np 
import string 

sw = set(stopwords.words('english')) 
punctuation = set(string.punctuation) 
all_names = set([name.lower() for name in names.words()]) 

def isStopWord(word): 
    return (word in sw or word in punctuation) or not word.isalpha() or word in all_names 

review_words = movie_reviews.words() 
filtered = [w.lower() for w in review_words if not isStopWord(w.lower())] 

words = FreqDist(filtered) 

texts = [] 

for fid in movie_reviews.fileids(): 
    texts.append(" ".join([w.lower() for w in movie_reviews.words(fid) if not isStopWord(w.lower()) and words[w.lower()] > 1])) 

vectorizer = TfidfVectorizer(stop_words='english') 
matrix = vectorizer.fit_transform(texts) 
sums = np.array(matrix.sum(axis=0)).ravel() 

ranks = [] 

for word, val in itertools.izip(vectorizer.get_feature_names(), sums): 
    ranks.append((word, val)) 

df = pd.DataFrame(ranks, columns=["term", "tfidf"]) 
df = df.sort(['tfidf']) 
print(df.head()) 

N = int(.01 * len(df)) 
df = df.tail(N) 

for term, tfidf in itertools.izip(df["term"].values, df["tfidf"].values): 
    print(term, ":", tfidf) 

社交网络分析

社交网络分析使用网络理论研究社会关系。节点代表网络中的参与者。节点之间的线表示关系。形式上,这被称为图形。由于本书的限制,我们将只快速查看流行的网络 Python 库附带的简单图表。matplotlib 将有助于图形的可视化。

使用以下命令安装网络:

$ pip3 install networkx

网络的导入惯例如下:

import networkx as nx 

NetworkX 提供了许多示例图,如下所示:

print([s for s in dir(nx) if s.endswith('graph')]) 

加载戴维斯南方女性图表,并绘制连接度直方图:

G = nx.davis_southern_women_graph() 
plt.figure(1) 
plt.hist(nx.degree(G).values()) 

生成的直方图如下所示:

Social network analysis

用节点标签绘制图表,如下所示:

plt.figure(2) 
pos = nx.spring_layout(G) 
nx.draw(G, node_size=9) 
nx.draw_networkx_labels(G, pos) 
plt.show() 

我们得到以下图表:

Social network analysis

这是一个简短的例子,但它应该足以让你尝试什么是可能的。我们可以使用网络来探索、可视化和分析社交媒体网络,如推特、脸书和领英。主题甚至不一定是社交网络——它可以是任何类似于网络 x 可以理解的图形的东西。

总结

这是关于文本分析的一章。我们了解到,在文本分析中,最好的做法是去掉停止词。

在单词包模型中,我们使用一个文档来创建一个包含在同一文档中找到的单词的包。我们学习了如何使用所有的字数为每个文档建立一个特征向量。

分类算法是一种机器学习算法,涉及确定给定项目的类别。朴素贝叶斯分类是一种基于概率论和统计学中贝叶斯定理的概率算法。贝叶斯定理指出,后验概率与先验概率乘以可能性成正比。

下一章将更详细地描述机器学习。机器学习是一个很有前途的研究领域。有一天,它甚至可能完全取代人类的劳动。我们将以天气数据为例,探索如何使用 Python 机器学习包 scikit-learn。

十、预测性分析和机器学习

预测性分析机器学习最近被许多行业接受进入主流数据科学和数据分析。它们现在与其他领域相比,毫无疑问,我们可以期待大量的快速增长。甚至有人预测,机器学习将加速发展,在短短几十年内,人类的劳动将被智能机器所取代(见http://en.wikipedia.org/wiki/Technological_singularity)。人工通用智能 ( AGI )目前的技术水平离那个乌托邦还很远,但机器学习已经取得了长足的进步,并被用于自动驾驶汽车、聊天机器人和基于人工智能的助手,如亚马逊的 Alexa、苹果的 Siri 和 Ok Google。即使是简单的决策,比如确定网络上的图片是包含狗还是猫,仍然需要大量的计算能力和数据。预测性分析使用各种技术,包括机器学习,来做出有用的预测。例如,它可以用来确定客户是否能够偿还贷款,或者识别怀孕的女性客户(见http://www . Forbes . com/sites/kashmirhill/2012/02/16/how-target-揣摩-一名少女在她父亲怀孕之前怀孕了/ )。

为了做出这些预测,从大量数据中提取特征。我们之前提到过特性。特征,也称为预测因子,是可以用来进行预测的输入变量。本质上,我们在数据中找到特征,并寻找将特征映射到目标的函数,目标可能是已知的,也可能是未知的。找到合适的函数可能很难。通常,不同的算法和模型被组合成所谓的集合。集合的输出可以是多数票,也可以是一组模型的平均值,但我们也可以使用更高级的算法来产生最终结果。我们不会在本章中使用合奏,但这是需要记住的。

在前一章中,我们尝到了机器学习算法的甜头——朴素贝叶斯分类算法。我们可以将机器学习算法和方法分为三大类:

  • 监督学习:监督学习是指对训练数据进行标注的方法,即给算法提供分类数据的例子。使用标记的训练数据,我们创建一个函数,将数据中的输入变量映射到结果变量。例如,如果我们想对垃圾邮件进行分类,我们需要提供垃圾邮件和普通电子邮件的例子。监督学习算法的例子有线性回归、逻辑回归、状态向量机、随机森林、k 近邻等等。
  • 无监督学习:无监督学习是指不标注训练数据的方法。这种类型的学习可以发现模式,例如大型数据集中的集群。无监督学习算法的例子是 k-means 和层次聚类。
  • 强化学习:强化学习是指计算机从反馈中学习的方法。例如,计算机可以通过与自己对弈来学习下棋。当然,其他游戏和技能也是可以学习的,如果你还记得 1983 年的电影战争游戏(见http://en.wikipedia.org/wiki/WarGames)想到井字游戏和热核战争。

我们将以天气预报为例。在本章中,我们将主要使用 Python scikit-learn 库。这个库有聚类、回归和分类算法。然而,scikit-learn 没有涵盖一些机器学习算法,因此对于这些算法,我们将使用其他 API。本章涵盖的主题如下:

  • 预处理
  • 逻辑回归分类
  • 支持向量机分类
  • 弹性回归
  • 支持向量回归
  • 具有相似性传播的聚类
  • 均值漂移
  • 遗传算法
  • 神经网络
  • 决策树

预处理

Library scikit-learn 有一个预处理模块,这是本节的主题。在上一章第九章分析文本数据和社交媒体中,我们安装了 scikit-learn,并通过过滤掉停止词来练习一种数据预处理形式。一些机器学习算法对于平均值为 0、方差为 1 的非高斯分布的数据有问题。sklearn.preprocessing模块处理这个问题。我们将在本节中演示它。我们将对来自荷兰 KNMI 研究所的气象数据进行预处理(德比尔特气象站原始数据来自http://www . KNMI . nl/气候学/daily _ data/data files 3/260/etmgeg _ 260 . zip)。数据只是原始数据文件的一列,包含每日降雨量值。它以第 5 章检索、处理和存储数据中讨论的.npy格式存储。我们可以将数据加载到 NumPy 数组中。这些值是整数,我们必须乘以 0.1,才能得到以毫米为单位的日降水量。

数据有一个有点奇怪的特点,0.05 毫米以下的数值被引用为-1。我们将这些值设置为 0.025 (0.05 除以 2)。原始数据中有几天缺少值。我们将完全忽略丢失的数据。我们可以这样做,因为我们有很多数据点。数据在本世纪初丢失了大约一年,在本世纪后期丢失了几天。preprocessing模块有一个Imputer类,它有处理缺失值的默认策略。然而,在这种情况下,这些策略似乎不合适。数据分析就是把数据当作一个窗口来看待——一个通向知识的窗口。数据清理和输入是可以让我们的窗口看起来更好的活动。然而,我们应该注意不要过分扭曲原始数据。

我们的机器学习示例的主要特征将是一系列一年中的某一天的值(1 到 366)。这应该有助于解释任何季节性影响。

安德森-达林检验的平均值、方差和输出(见第 4 章统计和线性代数)打印如下:

Rain mean 2.17919594267
Rain variance 18.803443919
Anderson rain (inf, array([ 0.576,  0.656,  0.787,  0.918,     
1.092]), array([ 15\. ,  10\. ,   5\. ,   2.5,   1\. ]))

我们可以有把握地得出结论,数据没有 0 的平均值和 1 的方差,也不符合正态分布。数据中有很大比例的 0 值对应于没有下雨的日子。大量降雨越来越少(这是好事)。然而,数据分布是完全不对称的,因此不是高斯分布。我们可以很容易地安排 0 的平均值和 1 的方差。使用scale()功能缩放数据:

scaled = preprocessing.scale(rain) 

我们现在得到了平均值和方差的要求值,但是数据分布仍然是不对称的:

Scaled mean 3.41301602808e-17
Scaled variance 1.0
Anderson scaled (inf, array([ 0.576,  0.656,  0.787,  0.918,  
1.092]), array([ 15\. ,  10\. ,   5\. ,   2.5,   1\. ]))

有时,我们希望将数字特征值转换为布尔值。这通常用于文本分析,以简化计算。使用binarize()功能进行转换:

binarized = preprocessing.binarize(rain) 
print(np.unique(binarized), binarized.sum()) 

默认情况下,会创建一个新数组;我们也可以选择就地进行手术。默认阈值为零,这意味着正值被 1 替换,负值被 0 替换:

[ 0\.  1.] 24594.0

LabelBinarizer类可以将整数标记为类(在分类的上下文中):

lb = preprocessing.LabelBinarizer() 
lb.fit(rain.astype(int)) 
print(lb.classes_) 

输出是从 0 到 62 的整数列表。参考本书代码包中的ch-10.ipynb文件:

import numpy as np 
from sklearn import preprocessing 
from scipy.stats import anderson 

rain = np.load('rain.npy') 
rain = .1 * rain 
rain[rain < 0] = .05/2 
print("Rain mean", rain.mean()) 
print("Rain variance", rain.var()) 
print("Anderson rain", anderson(rain)) 

scaled = preprocessing.scale(rain) 
print("Scaled mean", scaled.mean()) 
print("Scaled variance", scaled.var()) 
print("Anderson scaled", anderson(scaled)) 

binarized = preprocessing.binarize(rain) 
print(np.unique(binarized), binarized.sum()) 

lb = preprocessing.LabelBinarizer() 
lb.fit(rain.astype(int)) 
print(lb.classes_) 

用逻辑回归进行分类

逻辑回归是一种分类算法(见http://en.wikipedia.org/wiki/Logistic_regression)。这种算法可以用来预测一个类或事件发生的概率。具有多个类的分类问题可以简化为二元分类问题。在这种最简单的情况下,一个类的高概率意味着另一个类的低概率。逻辑回归基于逻辑函数,其值在 0 和 1 之间,概率也是如此。因此,逻辑函数可用于将任意值转换为概率。

我们可以定义一个用逻辑回归进行分类的函数。按如下方式创建分类器对象:

clf = LogisticRegression(random_state=12) 

random_state参数就像伪随机发生器的种子。在本书的前面,我们谈到了交叉验证作为一种避免过度拟合的技术的重要性。 k 折叠交叉验证是一种交叉验证形式,涉及 k (一个小整数)和称为折叠的随机数据分区。在 k 迭代中,每个折叠使用一次进行验证,其余数据用于训练。scikit-learn 中的类的默认 k 值为 3,但通常我们可能希望将其设置为更高的值,例如 5 或 10。迭代的结果可以在最后合并。scikit-learn 有一个用于 k 倍交叉验证的实用工具KFold类。用10折叠创建一个KFold对象,如下所示:

kf = KFold(len(y), n_folds=10) 

使用fit()方法训练数据,如下所示:

clf.fit(x[train], y[train]) 

score()方法测量分类精度:

scores.append(clf.score(x[test], y[test])) 

在本例中,我们将使用一年中的某一天和前一天的降雨量作为特征。构建一个带有特征的数组,如下所示:

x = np.vstack((dates[:-1], rain[:-1])) 

作为类,首先定义降雨量为 0 的无雨日;第二,我们的数据中对应于-1 的低降雨量;第三,雨天。这三个类可以链接到我们数据中的值的符号:

y = np.sign(rain[1:]) 

使用这种设置,我们获得了 57%的平均准确率。对于 scikit-learn 样本 iris 数据集,我们得到的平均准确率为 41%(参考本书代码包中的ch-10.ipynb文件):

from sklearn.linear_model import LogisticRegression 
from sklearn.cross_validation import KFold 
from sklearn import datasets 
import numpy as np 

def classify(x, y): 
    clf = LogisticRegression(random_state=12) 
    scores = [] 
    kf = KFold(len(y), n_folds=10) 
    for train,test in kf: 
      clf.fit(x[train], y[train]) 
      scores.append(clf.score(x[test], y[test])) 

    print("accuracy", np.mean(scores)) 

rain = np.load('rain.npy') 
dates = np.load('doy.npy') 

x = np.vstack((dates[:-1], rain[:-1])) 
y = np.sign(rain[1:]) 
classify(x.T, y) 

#iris example 
iris = datasets.load_iris() 
x = iris.data[:, :2] 
y = iris.target 
classify(x, y) 

支持向量机分类

支持向量机(【SVM】)可用于回归,即支持向量回归 ( SVR )和支持向量分类 ( SVC )。这个算法是弗拉基米尔·瓦普尼克在 1993 年发明的(见http://en.wikipedia.org/wiki/Support_vector_machine)。SVM 将数据点映射到多维空间中的点。该映射由所谓的内核函数执行。核函数可以是线性的或非线性的。然后将分类问题简化为寻找一个或多个超平面,该超平面最好地将点分成类。很难用超平面进行分离,这就导致了软边界概念的出现。软裕度衡量误分类的容限,由通常用 c 表示的常数控制。另一个重要参数是核函数的类型,它可以是以下之一:

  • 线性函数
  • 多项式函数
  • 径向基函数
  • sigmoid 函数

一个网格搜索可以为一个问题找到合适的参数。这是一种尝试所有可能的参数组合的系统方法。我们将使用 scikit-learn GridSearchCV类执行网格搜索。我们给这个类一个带有字典的分类器或回归器类型对象。字典的键是我们想要调整的参数。字典的值是要尝试的参数值的对应列表。scikit-learn API 有许多类,它们向对应的类添加了交叉验证功能。默认情况下,交叉验证处于关闭状态。如下创建一个GridSearchCV对象:

clf = GridSearchCV(SVC(random_state=42, max_iter=100), {'kernel': ['linear', 'poly', 'rbf'], 'C':[1, 10]}) 

在这一行中,我们指定了最大迭代次数,以避免过度考验我们的耐心。交叉验证也被关闭,以加快过程。此外,我们改变了核的类型和软边界参数。

前面的代码片段为可能的参数变化创建了一个 2 乘 3 的网格。如果我们有更多的时间,我们可以用更多可能的值创建一个更大的网格。我们还会将GridSearchCVcv参数设置为我们想要的折叠数,例如 5 或 10。最大迭代次数也应该设置为更高的值。不同的内核在适应所需的时间上可能有很大的差异。我们可以打印更多信息,例如每个参数值组合的执行时间,详细参数设置为非零整数值。通常,我们希望将软裕度参数改变几个数量级,例如,从 1 到 10,000。我们可以通过数字 T2 功能来实现这一点。

应用这个分类器,我们获得了 56%的天气数据准确率和 82%的虹膜样本数据集准确率。GridSearchCVgrid_scores_字段包含网格搜索得到的分数。对于天气数据,得分如下:

[mean: 0.42879, std: 0.11308, params: {'kernel': 'linear', 'C': 1},
 mean: 0.55570, std: 0.00559, params: {'kernel': 'poly', 'C': 1},
 mean: 0.36939, std: 0.00169, params: {'kernel': 'rbf', 'C': 1},
 mean: 0.30658, std: 0.03034, params: {'kernel': 'linear', 'C':10},
 mean: 0.41673, std: 0.20214, params: {'kernel': 'poly', 'C': 10},
 mean: 0.49195, std: 0.08911, params: {'kernel': 'rbf', 'C': 10}]

对于虹膜样本数据,我们得到以下分数:

[mean: 0.80000, std: 0.03949, params: {'kernel': 'linear', 'C': 1},
 mean: 0.58667, std: 0.12603, params: {'kernel': 'poly', 'C': 1},
 mean: 0.80000, std: 0.03254, params: {'kernel': 'rbf', 'C': 1},
 mean: 0.74667, std: 0.07391, params: {'kernel': 'linear', 'C':10},
 mean: 0.56667, std: 0.13132, params: {'kernel': 'poly', 'C': 10},
 mean: 0.79333, std: 0.03467, params: {'kernel': 'rbf', 'C': 10}]

参考本书代码包中的ch-10.ipynb文件:

from sklearn.svm import SVC 
from sklearn.grid_search import GridSearchCV 
from sklearn import datasets 
import numpy as np 
from pprint import PrettyPrinter 

def classify(x, y): 
    clf = GridSearchCV(SVC(random_state=42, max_iter=100), {'kernel': ['linear', 'poly', 'rbf'], 'C':[1, 10]}) 

    clf.fit(x, y) 
    print("Score", clf.score(x, y)) 
    PrettyPrinter().pprint(clf.grid_scores_) 

rain = np.load('rain.npy') 
dates = np.load('doy.npy') 

x = np.vstack((dates[:-1], rain[:-1])) 
y = np.sign(rain[1:]) 
classify(x.T, y) 

#iris example 
iris = datasets.load_iris() 
x = iris.data[:, :2] 
y = iris.target 
classify(x, y) 

用弹性系数回归

弹性网正则化是一种减少回归情况下过拟合危险的方法(参见http://en.wikipedia.org/wiki/Elastic_net_regularization)。弹性网正则化线性组合了最小绝对收缩和选择算子 ( 拉索)和方法。LASSO 限制了所谓的 L1 范数,或曼哈顿距离。这个范数度量一对点的绝对坐标之差的和。岭方法使用一个惩罚,即 L1 范数的平方。对于回归问题,拟合优度通常使用确定系数 也称为 R 平方(见http://en.wikipedia.org/wiki/Coefficient_of_determination)。不幸的是,R 平方有几种定义。此外,这个名字有点误导,因为负值是可能的。完美拟合的决定系数为 1。由于这些定义允许广泛的可接受值,我们应该将目标定在尽可能接近 1 的分数。

让我们使用 10 倍交叉验证。如下定义ElasticNetCV对象:

clf = ElasticNetCV(max_iter=200, cv=10, l1_ratio = [.1, .5, .7, .9, .95, .99, 1]) 

ElasticNetCV类有一个l1_ratio参数,其值在01之间。如果值为0,我们有一个岭回归;如果是1,我们有一个 LASSO 回归。否则,我们有混合物。我们可以指定一个数字或一个数字列表供选择。对于雨水数据,我们得到以下分数:

Score 0.0527838760942

这个分数表明我们对数据的拟合不足。出现这种情况有几个原因,例如,也许我们没有使用足够的功能,或者也许模型是错误的。对于波士顿房价数据,根据目前的所有特征,我们得出以下结论:

Score 0.683143903455

predict()方法给出新数据的预测。我们将使用散点图来可视化预测的质量。对于降雨数据,我们得到如下图:

Regression with ElasticNetCV

上图中的图证实了我们有一个不好的匹配(装配不足)。穿过原点的一条直的对角线表示完美的配合。这几乎是我们从波士顿房价数据中得到的:

Regression with ElasticNetCV

参考本书代码包中的ch-10.ipynb文件:

from sklearn.linear_model import ElasticNetCV 
import numpy as np 
from sklearn import datasets 
import matplotlib.pyplot as plt 

def regress(x, y, title): 
    clf = ElasticNetCV(max_iter=200, cv=10, l1_ratio = [.1, .5, .7, .9, .95, .99, 1]) 

    clf.fit(x, y) 
    print("Score", clf.score(x, y)) 

    pred = clf.predict(x) 
    plt.title("Scatter plot of prediction and " + title) 
    plt.xlabel("Prediction") 
    plt.ylabel("Target") 
    plt.scatter(y, pred) 
    # Show perfect fit line 
    if "Boston" in title: 
        plt.plot(y, y, label="Perfect Fit") 
        plt.legend() 

    plt.grid(True) 
    plt.show() 

rain = .1 * np.load('rain.npy') 
rain[rain < 0] = .05/2 
dates = np.load('doy.npy') 

x = np.vstack((dates[:-1], rain[:-1])) 
y = rain[1:] 
regress(x.T, y, "rain data") 

boston = datasets.load_boston() 
x = boston.data 
y = boston.target 
regress(x, y, "Boston house prices") 

支持向量回归

如前所述,支持向量机可以用于回归。在回归的情况下,我们使用超平面不是为了分离点,而是为了拟合。一条学习曲线是一种可视化学习算法行为的方式。它是一系列训练数据大小的训练和测试分数图。创建一条学习曲线迫使我们多次训练估计器,因此总体而言速度很慢。我们可以通过创建多个并发估算器作业来弥补这一点。支持向量回归是可能需要缩放的算法之一。如果我们这样做,那么我们会得到以下最高分:

Max test score Rain 0.0161004084576
Max test score Boston 0.662188537037

这类似于ElasticNetCV类获得的结果。为此,许多 scikit 学习类都有一个n_jobs参数。根据经验,我们创造的就业机会往往与我们系统中的中央处理器数量一样多。作业是使用标准的 Python 多处理应用编程接口创建的。调用learning_curve()功能进行培训和测试:

train_sizes, train_scores, test_scores = learning_curve(clf, X, Y, n_jobs=ncpus) 

通过取平均值绘制分数:

plt.plot(train_sizes, train_scores.mean(axis=1), label="Train score") 
plt.plot(train_sizes, test_scores.mean(axis=1), '--', label="Test score") 

rain 数据学习曲线如下所示:

Support vector regression

学习曲线是我们在日常生活中熟悉的东西。我们的经验越多,我们应该学到的就越多。在数据分析方面,如果我们增加更多的数据,应该会有更好的分数。如果我们训练成绩很好,但是考试成绩很差,这意味着我们过度适应了。我们的模型只对训练数据起作用。波士顿房价数据学习曲线看起来要好得多:

Support vector regression

代码在本书代码包的sv_regress.py文件中:

import numpy as np 
from sklearn import datasets 
from sklearn.learning_curve import learning_curve 
from sklearn.svm import SVR 
from sklearn import preprocessing 
import multiprocessing 
import matplotlib.pyplot as plt 

def regress(x, y, ncpus, title): 
    X = preprocessing.scale(x) 
    Y = preprocessing.scale(y) 
    clf = SVR(max_iter=ncpus * 200) 

    train_sizes, train_scores, test_scores = learning_curve(clf, X, Y, n_jobs=ncpus)  

    plt.figure() 
    plt.title(title) 
    plt.plot(train_sizes, train_scores.mean(axis=1), label="Train score") 
    plt.plot(train_sizes, test_scores.mean(axis=1), '--', label="Test score") 
    print("Max test score " + title, test_scores.max()) 
    plt.grid(True) 
    plt.legend(loc='best') 
    plt.show() 

rain = .1 * np.load('rain.npy') 
rain[rain < 0] = .05/2 
dates = np.load('doy.npy') 

x = np.vstack((dates[:-1], rain[:-1])) 
y = rain[1:] 
ncpus = multiprocessing.cpu_count() 
regress(x.T, y, ncpus, "Rain") 

boston = datasets.load_boston() 
x = boston.data 
y = boston.target 
regress(x, y, ncpus, "Boston") 

利用亲和传播进行聚类

聚类旨在将数据划分为称为聚类的组。聚类通常是无监督的,因为没有给出例子。一些聚类算法需要猜测聚类的数量,而其他算法则不需要。亲缘关系传播属于后一类。数据集中的每个项目都可以使用特征值映射到欧氏空间。相似性传播依赖于包含数据点之间欧几里得距离的矩阵。由于矩阵可以很快变得相当大,我们应该注意不要占用太多的内存。scikit-learn 库有生成结构化数据的实用程序。创建三个数据块,如下所示:

x, _ = datasets.make_blobs(n_samples=100, centers=3, n_features=2, random_state=10) 

调用euclidean_distances()函数创建上述矩阵:

S = euclidean_distances(x) 

使用矩阵进行聚类,以便用相应的聚类标记数据:

aff_pro = cluster.AffinityPropagation().fit(S) 
labels = aff_pro.labels_ 

如果我们绘制集群,我们会得到下图:

Clustering with affinity propagation

参考本书代码包中的ch-10.ipynb.py文件:

from sklearn import datasets 
from sklearn import cluster 
import numpy as np 
import matplotlib.pyplot as plt 
from sklearn.metrics import euclidean_distances 

x, _ = datasets.make_blobs(n_samples=100, centers=3, n_features=2, random_state=10) 
S = euclidean_distances(x) 

aff_pro = cluster.AffinityPropagation().fit(S) 
labels = aff_pro.labels_ 

styles = ['o', 'x', '^'] 

for style, label in zip(styles, np.unique(labels)): 
   print(label) 
   plt.plot(x[labels == label], style, label=label) 
plt.title("Clustering Blobs") 
plt.grid(True) 
plt.legend(loc='best') 
plt.show() 

均值漂移

均值漂移是另一种不需要估计聚类数量的聚类算法。它已成功地应用于图像处理。该算法试图迭代地找到密度函数的最大值。在演示均值漂移之前,我们将使用 PandasDataFrame对一年中每天的降雨数据进行平均。创建DataFrame并将其数据平均如下:

df = pd.DataFrame.from_records(x.T, columns=['dates', 'rain']) 
df = df.groupby('dates').mean() 

df.plot() 

结果如下图所示:

Mean shift

用均值漂移算法对数据进行聚类,如下所示:

x = np.vstack((np.arange(1, len(df) + 1) , df.as_matrix().ravel())) 
x = x.T 
ms = cluster.MeanShift() 
ms.fit(x) 
labels = ms.predict(x) 

如果我们用不同的线宽和阴影来可视化三个结果聚类的数据,会得到下图:

Mean shift

如你所见,根据一年中某一天的平均降雨量(1-366 毫米),我们有三个聚类。完整的代码在本书代码包的ch-10.ipynb文件中:

import numpy as np 
from sklearn import cluster 
import matplotlib.pyplot as plt 
import pandas as pd 

rain = .1 * np.load('rain.npy') 
rain[rain < 0] = .05/2 
dates = np.load('doy.npy') 
x = np.vstack((dates, rain)) 
df = pd.DataFrame.from_records(x.T, columns=['dates', 'rain']) 
df = df.groupby('dates').mean() 
df.plot() 
x = np.vstack((np.arange(1, len(df) + 1) , df.as_matrix().ravel())) 
x = x.T 
ms = cluster.MeanShift() 
ms.fit(x) 
labels = ms.predict(x) 

plt.figure() 
grays = ['0', '0.5', '0.75'] 

for gray, label in zip(grays, np.unique(labels)): 
    match = labels == label 
    x0 = x[:, 0] 
    x1 = x[:, 1] 
    plt.plot(x0[match], x1[match], lw=label+1, label=label) 
    plt.fill_between(x0, x1, where=match, color=gray) 

plt.grid(True) 
plt.legend() 
plt.show() 

遗传算法

这是本书迄今为止最有争议的部分。遗传算法基于生物进化理论(见http://en.wikipedia.org/wiki/Evolutionary_algorithm)。这种类型的算法对于搜索和优化很有用。例如,我们可以用它来寻找回归或分类问题的最佳参数。

人类和地球上的其他生命形式在染色体中携带遗传信息。染色体经常被模拟成字符串。遗传算法中也使用了类似的表示。第一步是用随机个体和遗传信息的相关表示初始化种群。我们也可以用已知的候选解决方案来初始化这个问题。之后,我们要经历很多次迭代,称为。在每一代中,个体都是根据预定义的适应度函数来选择交配的。适应度函数评估个体与期望解的接近程度。

两个遗传算子生成新的遗传信息:

  • 杂交:这是通过交配发生的,会产生新的孩子。单点杂交过程从一个父母那里获得一条遗传信息,从另一个父母那里获得一条互补信息。例如,如果信息由 100 个列表元素表示,交叉可以从第一个父代中获取前 80 个元素,从另一个父代中获取最后 20 个元素。在遗传算法中,有可能从两个以上的父母那里产生孩子。这是一个正在研究的领域(参考 Eiben,A. E. et al. 多亲重组遗传算法国际进化计算会议论文集- PPSN 三世)。第三届自然并行问题解决会议:78-87。ISBN 3-540-58484-6,1994 年)。
  • 突变:这是由固定的突变率控制的。这一概念已经在流行文化中得到探索,包括几部好莱坞电影。突变是罕见的,通常是有害的,甚至是致命的。然而,有时突变体可以获得理想的性状。在某些情况下,这种特征可以遗传给后代。

最终,新的个体取代了旧的群体,我们可以开始新的迭代。在这个例子中,我们将使用 Python DEAP 库。按照以下步骤安装 DEAP:

$ pip3 install deap

首先定义一个最大化适合度的Fitness子类:

creator.create("FitnessMax", base.Fitness, weights=(1.0,)) 

然后,为群体中的每个个体定义一个模板:

creator.create("Individual", array.array, typecode='d', fitness=creator.FitnessMax) 

DEAP 有工具箱的概念,这是一个必要功能的登记册。创建一个工具箱并注册初始化函数,如下所示:

toolbox = base.Toolbox() 
toolbox.register("attr_float", random.random) 
toolbox.register("individual", tools.initRepeat, creator.Individual, toolbox.attr_float, 200) 
toolbox.register("populate", tools.initRepeat, list, toolbox.individual) 

第一个函数生成 0 到 1 之间的浮点数。第二个函数创建一个包含 200 个浮点数的个体。第三个函数创建一个个人列表。该列表表示搜索或优化问题的可能解决方案的总体。

在一个社会里,我们要的是“正常”的个体,也要像爱因斯坦这样的人。在第 4 章统计学和线性代数中,我们被介绍到shapiro()函数,该函数执行正态性测试。要使一个人正常,他或她的列表的正态性检验 p 值需要尽可能高。以下代码定义了健身功能:

def eval(individual): 
    return shapiro(individual)[1], 

让我们定义遗传算子:

toolbox.register("evaluate", eval) 
toolbox.register("mate", tools.cxTwoPoint) 
toolbox.register("mutate", tools.mutFlipBit, indpb=0.1) 
toolbox.register("select", tools.selTournament, tournsize=4) 

下面的列表将向您解释前面的遗传操作符:

  • evaluate:这个操作员测量每个人的体能。在这个例子中,正态性测试的 p 值被用作适合度得分。
  • mate:此运算符产生子代。在这个例子中,它使用两点交叉。
  • mutate:这个操作者随意改变一个个体。对于布尔值列表,这意味着一些值从True翻转到False,反之亦然。
  • select:该操作员选择允许交配的个体。

在前面的代码片段中,我们指定使用两点交叉和属性翻转的概率。生成 400 个个体作为初始群体:

pop = toolbox.populate(n=400) 

现在开始进化过程,如下所示:

hof = tools.HallOfFame(1) 
stats = tools.Statistics(key=lambda ind: ind.fitness.values) 
stats.register("max", np.max) 

algorithms.eaSimple(pop, toolbox, cxpb=0.5, mutpb=0.2, ngen=80, stats=stats, halloffame=hof) 

该计划报告统计数据,包括每一代人的最大体能。我们指定了交叉概率、变异率和停止后的世代数。以下是显示的统计报告的摘录:

    gen        nevals        max
    0          400           0.000484774
    1          245           0.000776807
    2          248           0.00135569
    ...
    79         250           0.99826
    80         248           0.99826

如您所见,我们从远离正常的分布开始,但最终我们得到一个具有以下直方图的个体:

Genetic algorithms

参考本书代码包中的ch-10.ipynb文件:

import array 
import random 
import numpy as np 
from deap import algorithms 
from deap import base 
from deap import creator 
from deap import tools 
from scipy.stats import shapiro 
import matplotlib.pyplot as plt 

creator.create("FitnessMax", base.Fitness, weights=(1.0,)) 
creator.create("Individual", array.array, typecode='d', fitness=creator.FitnessMax) 

toolbox = base.Toolbox() 
toolbox.register("attr_float", random.random) 
toolbox.register("individual", tools.initRepeat, creator.Individual, toolbox.attr_float, 200) 
toolbox.register("populate", tools.initRepeat, list, toolbox.individual) 

def eval(individual): 
    return shapiro(individual)[1], 

toolbox.register("evaluate", eval) 
toolbox.register("mate", tools.cxTwoPoint) 
toolbox.register("mutate", tools.mutFlipBit, indpb=0.1) 
toolbox.register("select", tools.selTournament, tournsize=4) 

random.seed(42) 

pop = toolbox.populate(n=400) 
hof = tools.HallOfFame(1) 
stats = tools.Statistics(key=lambda ind: ind.fitness.values) 
stats.register("max", np.max) 

algorithms.eaSimple(pop, toolbox, cxpb=0.5, mutpb=0.2, ngen=80, stats=stats, halloffame=hof) 

print(shapiro(hof[0])[1]) 
plt.hist(hof[0]) 
plt.grid(True) 
plt.show() 

神经网络

人工神经网络 ( ANN )是受动物大脑(高度进化的动物)启发的模型。神经网络是由神经元组成的网络,神经元是具有输入和输出的单元。例如,一个神经元的输入可以是与图像像素相关的值,一个神经元的输出可以传递给另一个神经元,然后传递给另一个神经元,以此类推,从而创建多层网络。神经网络包含自适应元素,使其适合处理非线性模型和模式识别问题。我们将再次尝试根据一年中的某一天和前一天的数值来预测是否会下雨。让我们使用 Python 库,它可以如下安装:

$ pip3 install theanets nose_parameterized

其中一名技术审查人员遇到错误,通过更新 NumPy 和 SciPy 解决了该错误。我们首先创建一个对应于神经网络的Experiment,然后训练网络。创建一个具有两个输入神经元和一个输出神经元的网络:

net = theanets.Regressor(layers=[2,3,1]) 

该网络有一个带有三个神经元的隐藏层,并使用标准的 Python 多处理应用编程接口来加快计算速度。使用训练和验证数据集进行训练:

train = [x[:N], y[:N]] 
valid = [x[N:], y[N:]] 
net.train(train,valid,learning_rate=0.1,momentum=0.5) 

获取验证数据的预测,如下所示:

pred = net.predict(x[N:]).ravel() 

scikit-learn 库具有计算分类器精度的实用函数。计算精度如下:

print("Pred Min", pred.min(), "Max", pred.max()) 
print("Y Min", y.min(), "Max", y.max()) 
print("Accuracy", accuracy_score(y[N:], pred >= .5)) 

由于神经网络的性质,输出值可能会有所不同。输出可能如下所示:

Pred Min 0.615606596762 Max 0.615606596762 
Y Min 0.0 Max 1.0 
Accuracy 0.634133878385

参考本书代码包中的ch-10.ipynb文件:

import numpy as np 
import theanets 
import multiprocessing 
from sklearn import datasets 
from sklearn.metrics import accuracy_score 

rain = .1 * np.load('rain.npy') 
rain[rain < 0] = .05/2 
dates = np.load('doy.npy') 
x = np.vstack((dates[:-1], np.sign(rain[:-1]))) 
x = x.T 

y = np.vstack(np.sign(rain[1:]),) 
N = int(.9 * len(x)) 

train = [x[:N], y[:N]] 
valid = [x[N:], y[N:]] 

net = theanets.Regressor(layers=[2,3,1]) 

net.train(train,valid,learning_rate=0.1,momentum=0.5) 

pred = net.predict(x[N:]).ravel() 
print("Pred Min", pred.min(), "Max", pred.max()) 
print("Y Min", y.min(), "Max", y.max()) 
print("Accuracy", accuracy_score(y[N:], pred >= .5)) 

决策树

if a: else b语句是 Python 编程中最常见的语句之一。通过嵌套和组合这样的语句,我们可以构建一个所谓的决策树。这类似于老式的流程图,尽管流程图也允许循环。决策树在机器学习中的应用被称为决策树学习。决策树学习中的树的末端节点,也称为叶子,包含一个分类问题的类标签。每个非叶节点都与一个包含特征值的布尔条件相关联。scikit-learn 实现使用基尼杂质和熵作为信息度量。这些指标衡量物品被错误分类的概率(见http://en.wikipedia.org/wiki/Decision_tree_learning)。决策树易于理解、使用、可视化和验证。为了可视化树,我们将使用 Graphviz,它可以从http://graphviz.org/下载。我们还需要安装 pydot2,如下所示:

$ pip3 install pydot2

使用 scikit-learn train_test_split()功能,将 rain 数据拆分为如下训练和测试集:

x_train, x_test, y_train, y_test = train_test_split(x, y, random_state=37) 

如下创建DecisionTreeClassifier:

clf = tree.DecisionTreeClassifier(random_state=37) 

我们将使用 scikit-learn RandomSearchCV类来尝试一系列参数。按如下方式使用该类:

params = {"max_depth": [2, None], 
              "min_samples_leaf": sp_randint(1, 5), 
              "criterion": ["gini", "entropy"]} 
rscv = RandomizedSearchCV(clf, params) 
rscv.fit(x_train,y_train) 

我们从搜索中获得以下最佳分数和参数:

    Best Train Score 0.703164923517
    Test Score 0.705058763413
    Best params {'criterion': 'gini', 'max_depth': 2, 'min_samples_leaf': 2}

可视化决策树很好,即使它只是为了验证我们的假设。使用以下代码创建决策树图:

sio = io.StringIO() 
tree.export_graphviz(rscv.best_estimator_, out_file=sio, feature_names=['day-of-year','yest']) 
dec_tree = pydot.graph_from_dot_data(sio.getvalue()) 

print("Best Train Score", rscv.best_score_) 
print("Test Score", rscv.score(x_test, y_test)) 
print("Best params", rscv.best_params_) 

from IPython.display import Image 
Image(dec_tree.create_png()) 

最终结果见下图:

Decision trees

在非叶节点中,我们将条件打印为顶行。如果条件是真的,我们去左边的孩子;否则,我们向右走。当我们到达一个叶节点时,具有最高值的类获胜,如底线所示。检查本书代码包中的ch-10.ipynb文件:

from sklearn.model_selection import train_test_split 
from sklearn import tree 
from sklearn.model_selection import RandomizedSearchCV 
from scipy.stats import randint as sp_randint 
import pydotplus as pydot 
import io 
import numpy as np 

rain = .1 * np.load('rain.npy') 
rain[rain < 0] = .05/2 
dates = np.load('doy.npy').astype(int) 
x = np.vstack((dates[:-1], np.sign(rain[:-1]))) 
x = x.T 

y = np.sign(rain[1:]) 

x_train, x_test, y_train, y_test = train_test_split(x, y,  
random_state=37) 

clf = tree.DecisionTreeClassifier(random_state=37) 
params = {"max_depth": [2, None], 
              "min_samples_leaf": sp_randint(1, 5), 
              "criterion": ["gini", "entropy"]} 
rscv = RandomizedSearchCV(clf, params) 
rscv.fit(x_train,y_train) 

sio = io.StringIO() 
tree.export_graphviz(rscv.best_estimator_, out_file=sio,  
feature_names=['day-of-year','yest']) 
dec_tree = pydot.graph_from_dot_data(sio.getvalue()) 

print("Best Train Score", rscv.best_score_) 
print("Test Score", rscv.score(x_test, y_test)) 
print("Best params", rscv.best_params_) 

from IPython.display import Image 
Image(dec_tree.create_png()) 

总结

这一章致力于预测建模和机器学习。这些都是很大的领域,要在一章中涵盖,所以你可能想看看帕克特出版社的一些书(见http://www.packtpub.com)。预测性分析使用各种技术,包括机器学习,来做出有用的预测——例如,确定明天是否会下雨。

SVM 将数据点映射到多维空间中的点。然后将分类问题简化为寻找一个或多个超平面,该超平面最好地将点分成类。

弹性网络正则化线性结合了 LASSO 和岭方法。对于回归问题,拟合优度通常由决定系数决定,也称为 R 平方。一些聚类算法需要估计聚类的数量,而其他算法不需要。

遗传算法的第一步是用随机个体和遗传信息的相关表示初始化种群。在每一代中,个体被选择用于基于预定义的适应度函数进行交配。决策树在机器学习中的应用称为决策树学习。

下一章第 11 章Python 生态系统和云计算之外的环境描述了互操作性和云的可能性。

十一、Python 生态系统和云计算之外的环境

在 Python 生态系统之外,诸如 R、C、Java 和 Fortran 等编程语言相当流行。在本章中,我们将深入研究与这些环境交换信息的细节。

云计算旨在通过互联网提供计算能力。这意味着我们不需要在本地拥有很多强大的硬件。取而代之的是,我们根据当前的需求,按需付费。我们还将讨论如何在云中获取 Python 代码。在快节奏的世界里,这是一个快速发展的行业。我们在这里有很多选择。亚马逊网络服务 ( AWS )故意不在本书中讨论,因为其他书籍,如用 Python 构建机器学习系统威利·里歇特和路易斯·佩德罗·科埃略帕克特出版都非常详细地涵盖了这个话题。

http://datasciencetoolbox.org/提供的数据科学工具箱,是一个基于 Linux 的数据分析虚拟环境,可以在本地运行,也可以在 AWS 上运行。数据科学工具箱网站上给出的说明非常清楚,应该可以帮助您建立一个预装了大量 Python 包的环境。

本章将涉及的主题如下:

  • 用 Matlab/Octave 交换信息
  • 安装rpy2包装
  • 与 R 接口
  • 将 NumPy 数组发送到 Java
  • 集成 SWIG 和 NumPy
  • 集成 Boost 和 Python
  • 通过f2py使用 Fortran 代码
  • PythonAnywhere 云

用 Matlab/Octave 交换信息

Matlab 及其开源替代软件 Octave 是流行的数值程序和编程语言。Octave 和 Matlab 的语法与 Python 非常相似。事实上,你可以找到比较它们语法的网站(例如,参见http://wiki.scipy.org/NumPy_for_Matlab_Users)。

http://www.gnu.org/software/octave/download.html下载 Octave。

撰写本文时使用的 Octave 版本是 4.2.0。scipy.io.savemat()函数将数组保存在符合 Octave 和 Matlab 格式的文件中。该函数接受文件名和带有数组名称的字典,并将值作为参数。参考本书代码包中的ch-11.ipynb文件:

import statsmodels.api as sm 
from scipy.io import savemat 

data_loader = sm.datasets.sunspots.load_pandas() 
df = data_loader.data 
savemat("sunspots", {"sunspots": df.values}) 

前面的代码将太阳黑子数据存储在一个名为sunspots.mat的文件中。扩展名会自动添加。启动八度图形用户界面 ( 图形用户界面)或命令行界面。加载我们创建的文件并查看数据,如下所示:

    octave:1> load sunspots.mat
    octave:2> sunspots
    sunspots =
       1.7000e+03   5.0000e+00
       1.7010e+03   1.1000e+01
       1.7020e+03   1.6000e+01

安装 rpy2 包

R 编程语言在统计学家中很受欢迎。它是用 C 和 Fortran 编写的,可以在 GNU 通用公共许可证 ( GPL 下获得。r 支持建模、统计测试、时间序列分析、分类、可视化和聚类。综合 R 档案网 ( CRAN )等资源库网站为各种任务提供数千个 R 包。

http://www.r-project.org/下载 R。

rpy2包方便了与 Python 中的 R 接口。用pip按照以下步骤安装rpy2:

$ pip3 install rpy2

如果您已经安装了rpy2,请按照http://rpy.sourceforge.net/rpy2/doc-dev/html/overview.html上的说明进行操作,因为升级不是一个简单的过程。

与 R 接口

r 提供了一个包含样本数据集的datasets包。morley数据集有 1879 年光速测量的数据。光速是一个基本的物理常数,它的值目前已知非常精确。数据描述见。光速值可以在scipy.constants模块中找到。R 数据存储在具有三列的 R 数据帧中:

  • 实验编号,从一到五
  • 运行次数,每个实验运行 20 次,使测量总数达到 100
  • 以千米每秒为单位的光速,减去 299,000

rpy2.robjects.r()函数在 Python 环境中执行 R 代码。按照以下方式加载数据:

pandas2ri.activate() 
r.data('morley') 

Pandas 库通过pandas.rpy.common模块的 R 接口被否决,因此建议读者使用rpy2对象模块。将数据加载到 Pandas 数据框中,如下所示:

df = r['morley'] 

让我们用以下代码通过实验对数据进行分组,这将创建一个 5 乘 2 的 NumPy 数组:

samples = dict(list(df.groupby('Expt'))) 
samples = np.array([samples[i]['Speed'].values for i in samples.keys()]) 

当我们有不同实验的数据时,知道这些实验的数据点是否来自同一个分布是很有趣的。 Kruskal-Wallis 单向方差分析(参考http://en . Wikipedia . org/wiki/Kruskal % E2 % 80% 93 Wallis _ 单向方差分析 _of_variance )是一种统计方法,分析样本时不需要对其分布做假设。这个测试的零假设是所有样本的中位数都相等。测试在scipy.stats.kruskal()功能中实现。按照以下步骤进行测试:

print("Kruskal", kruskal(samples[0], samples[1], samples[2], samples[3], samples[4])) 

测试statisticpvalue打印在下面一行:

Kruskal KruskalResult(statistic=15.022124661246552,    pvalue=0.0046555484175328015)

我们可以拒绝零假设,但这并不能告诉我们哪个或哪些实验有偏离的中位数。进一步的分析留给读者去做。如果我们绘制每个实验的最小值、最大值和平均值,我们会得到下图:

Interfacing with R

查看本书代码包中的ch-11.ipynb文件:

import rpy2.robjects as ro 
from rpy2.robjects import pandas2ri 
from rpy2.robjects import r 

from scipy.stats import kruskal 
import matplotlib.pyplot as plt 
import numpy as np 
from scipy.constants import c 

pandas2ri.activate() 
r.data('morley') 

df = r['morley'] 

df['Speed'] = df['Speed'] + 299000 

samples = dict(list(df.groupby('Expt'))) 
samples = np.array([samples[i]['Speed'].values for i in  
samples.keys()]) 
print("Kruskal", kruskal(samples[0], samples[1], samples[2], samples[3], samples[4])) 

plt.title('Speed of light') 
plt.plot(samples.min(axis=1), 'x', label='min') 
plt.plot(samples.mean(axis=1), 'o', label='mean') 
plt.plot(np.ones(5) * samples.mean(), '--', label='All mean') 
plt.plot(np.ones(5) * c/1000, lw=2, label='Actual') 
plt.plot(samples.max(axis=1), 'v', label='max') 
plt.grid(True) 
plt.legend() 
plt.show() 

向 Java 发送 NumPy 数组

和 Python 一样,Java 也是一种非常流行的编程语言。我们在第 8 章中安装了 Java,作为使用 Cassandra 的先决条件。要运行 Java 代码,我们需要 Java 运行时环境 ( JRE )。开发需要 Java 开发工具包 ( JDK )。

Jython 是用 Java 编写的 Python 的实现。Jython 代码可以使用任何 Java 类。但是,用 C 语言编写的 Python 模块不能在 Jython 中导入。这是一个问题,因为许多数值和数据分析 Python 库都有用 c 语言编写的模块。JPype1包提供了一个解决方案,可以从http://pypi.python.org/pypi/JPype1http://github.com/originell/jpype下载。您可以使用以下命令安装JPype1:

$ pip3 install JPype1 

用以下代码启动 Java 虚拟机 ( JVM ):

jpype.startJVM(jpype.getDefaultJVMPath()) 

用一些随机值创建一个 JPype 数组JArray:

values = np.random.randn(7) 
java_array = jpype.JArray(jpype.JDouble, 1)(values.tolist()) 

按如下方式打印每个数组元素:

for item in java_array: 
   jpype.java.lang.System.out.println(item) 

最后,我们应该关闭 JVM,如下所示:

jpype.shutdownJVM() 

以下是本书代码包中ch-11.ipynb文件的代码列表:

import jpype 
import numpy as np 
from numpy import random 
jpype.startJVM(jpype.getDefaultJVMPath()) 

random.seed(44) 
values = np.random.randn(7) 
java_array = jpype.JArray(jpype.JDouble, 1)(values.tolist()) 

for item in java_array: 
   jpype.java.lang.System.out.println(item) 

jpype.shutdownJVM() 

当您在 Jupyter 笔记本中执行上述代码时,您会在 Jupyter 控制台中获得以下输出:

[W 18:23:45.918 NotebookApp] 404 GET /nbextensions/widgets/notebook/js/extension.js?v=20170305114358 (::1) 4.30ms referer=http://localhost:8888/notebooks/ch-12.ipynb
-0.7506147172558728
1.3163573247118194
1.2461400286434303
-1.6049157412585944
-1.468143678979905
-1.7150704579733684
1.8587836915125544
JVM activity report     :
classes loaded       : 32
JVM has been shutdown

集成 SWIG 和 NumPy

c 是 1970 年左右开发的一种广泛使用的编程语言。C 语言存在多种方言,C 语言影响了其他编程语言。c 不是面向对象的。这导致了 C++的产生,这是一种具有 C 特性的面向对象语言,因为 C 是 C++的子集。C 和 C++是编译语言。我们需要编译源代码来创建所谓的目标文件。之后,我们必须链接对象文件来创建动态共享库。

集成 C 和 Python 的好处是我们有很多选择。第一个选项是简化包装器和接口生成器 ( SWIG )。SWIG 在开发过程中增加了一个额外的步骤,那就是在 Python 和 C(或 C++)之间生成胶水代码。从http://www.swig.org/download.html下载 SWIG。在撰写本文时,最新的 SWIG 版本是 3.0.12。安装 SWIG 的前提是安装 Perl 兼容正则表达式 ( PCRE )。PCRE 是一个 C 正则表达式库。从http://www.pcre.org/下载 PCRE。撰写本文时的 PCRE 版本为 8.39。打开 PCRE 后,运行以下命令:

$ ./configure
$ make
$ make install

前面片段中的最后一个命令需要rootsudo访问。我们可以用同样的命令安装 SWIG。我们从编写包含函数定义的头文件开始。编写一个头文件,定义以下函数:

double sum_rain(int* rain, int len); 

我们将使用前面的函数对我们在上一章中分析的rain金额值求和。请参考本书代码包中的sum_rain.h文件。该功能在本书的代码包中的sum_rain.cpp文件中实现:

double sum_rain(int* rain, int len) { 

  double sum = 0.; 

  for (int i = 0; i < len; i++){ 
    if(rain[i] == -1) { 
       sum += 0.025; 
    } else { 
      sum += 0.1 * rain[i]; 
    } 
  } 

  return sum; 
} 

定义以下 SWIG 接口文件(参考本书代码包中的sum_rain.i文件):

%module sum_rain 

%{ 
  #define SWIG_FILE_WITH_INIT 
  #include "sum_rain.h" 
%} 

%include "/tmp/numpy.i" 

%init %{ 
  import_array(); 
%} 

%apply (int* IN_ARRAY1, int DIM1) {(int* rain, int len)}; 

%include "sum_rain.h" 

前面的代码依赖于numpy.i界面文件,可以在https://github . com/numpy/numpy/blob/master/tools/swig/numpy . I找到。在这个例子中,文件被放在/tmp目录中,但是我们几乎可以把这个文件放在任何地方。使用以下命令生成 SWIG 粘合代码:

$ swig -c++ -python sum_rain.i

上一步创建了一个sum_rain_wrap.cxx文件。如下编译sum_rain.cpp文件:

$ g++ -O2 -fPIC -c sum_rain.cpp -I<Python headers dir>

在前面的命令中,我们需要指定实际的 Python C 头文件目录。我们可以通过以下命令找到它:

$ python3-config --includes

我们也可以使用以下命令进行编译:

$ g++ -O2 -fPIC -c sum_rain.cpp $(python3-config --includes)

该目录的位置将根据 Python 版本和操作系统而有所不同(类似于/usr/include/python3.6)。如下编译生成的 SWIG 包装文件:

$ g++ -O2 -fPIC -c sum_rain_wrap.cxx $(python3-config --includes)  -I<numpy-dir>/core/include/

前面的命令取决于安装的 NumPy 的位置。从 Python 外壳中找到它,如下所示:

$ python3
>>> import numpy as np
>>> np.__file__

屏幕上打印的字符串应该包含 Python 版本,site-packages,以__init__.pyc结尾。如果去掉最后一部分,我们应该有 NumPy 目录。或者,我们可以使用以下代码:

>>> from imp import find_module
>>> find_module('numpy')

在我的计算机上,发出以下命令:

$ g++ -O2 -fPIC -c sum_rain_wrap.cxx $(python3-config --includes) -I/usr/local/lib/python3.6/site-packages/numpy/core/include/

最后一步是链接通过编译创建的目标文件:

$ g++ -lpython3.6 -dynamiclib sum_rain.o sum_rain_wrap.o -o _sum_rain.so -L /usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/lib

除非我们使用 Cygwin,否则前面的步骤在其他操作系统(如窗口)上的工作方式会有所不同。如果需要,建议您在 SWIG 用户邮件列表(http://www.swig.org/mail.html)或 StackOverflow 上寻求帮助。

用本书代码包中的swig_demo.py文件测试创建的库:

from _sum_rain import * 
import numpy as np 

rain = np.load('rain.npy') 
print("Swig", sum_rain(rain)) 
rain = .1 * rain 
rain[rain < 0] = .025 
print("Numpy", rain.sum()) 

使用以下命令执行此文件:

$ python3 swig_demo.py

如果一切顺利,并且我们没有混淆 Python 安装,将打印以下行:

Swig 85291.554999999328
Numpy 85291.55

集成 Boost 和 Python

Boost 是一个可以和 Python 接口的 C++库。从http://www.boost.org/users/download/下载。撰写本文时的 Boost 版本是 1.63.0。最简单但也是最慢的安装方法包括以下命令:

$ ./bootstrap.sh --prefix=/path/to/boost
$ ./b2 install

prefix参数指定安装目录。在本例中,我们将假设 Boost 安装在名为 Boost 的目录(如~/Boost)中用户的home目录下。在该目录中,将创建一个libinclude目录。对于 Unix 和 Linux,您应该运行以下命令:

export LD_LIBRARY_PATH=$HOME/Boost/lib:${LD_LIBRARY_PATH}

在 Mac OS X 上,设置以下环境变量:

export DYLD_LIBRARY_PATH=$HOME/Boost/lib 

在我们的例子中,我们将这个变量设置如下:

export DYLD_LIBRARY_PATH=/usr/local/Cellar/boost/1.63.0/lib

重新定义本书代码包中boost_rain.cpp文件中给出的 rain 求和函数:

#include <boost/python.hpp> 

double sum_rain(boost::python::list rain, int len) { 

  double sum = 0.; 

  for (int i = 0; i < len; i++){ 
    int val = boost::python::extract<int>(rain[i]); 
    if(val == -1) { 
       sum += 0.025; 
    } else { 
      sum += 0.1 * val; 
    } 
  } 

  return sum; 
} 

BOOST_PYTHON_MODULE(librain) { 
    using namespace boost::python; 

    def("sum_rain", sum_rain); 
} 

该函数接受 Python 列表和列表的大小。从 Python 中调用该函数,如本书代码包中的rain_demo.py文件所示:

import numpy as np 
import librain 

rain_data = np.load('rain.npy') 
print("Boost", librain.sum_rain(rain_data.astype(int).tolist(), len(rain_data))) 
rain_data = .1 * rain_data 
rain_data[rain_data < 0] = .025 
print("Numpy", rain_data.sum()) 

我们将使用本书代码包中的Makefile文件来自动化开发过程:

CC = g++ 
PYLIBPATH = $(shell python3-config --exec-prefix)/lib 
LIB = -L$(PYLIBPATH) $(shell python3-config --libs) -L/usr/local/Cellar/boost/1.63.0/lib -L/usr/local/Cellar/boost-python/1.63.0/lib -lboost_python3 
OPTS = $(shell python3-config --include) -O2 -I/usr/local/Cellar/boost/1.63.0/include 

default: librain.so 
  @python3 ./rain_demo.py 

librain.so: rain.o 
  $(CC) $(LIB)  -Wl,-rpath,$(PYLIBPATH) -shared $< -o $@ 

rain.o: boost_rain.cpp Makefile 
  $(CC) $(OPTS) -c $< -o $@ 

clean: 
  rm -rf *.so *.o 

.PHONY: default clean 

从命令行运行以下命令:

$ make clean;make

结果如预期的一样:

Boost 85291.54999999328
Numpy 85291.55 

通过 f2py 使用 Fortran 代码

Fortran (源自公式翻译)是一种成熟的编程语言,主要用于科学计算。它是在 20 世纪 50 年代开发的,出现了更新的版本,如 Fortran 77、Fortran 90、Fortran 95、Fortran 2003 和 Fortran 2008(参考http://en.wikipedia.org/wiki/Fortran)。每个版本都增加了特性和新的编程范例。这个例子我们需要一个 Fortran 编译器。gfortran编译器是 GNU Fortran 编译器,可以从http://gcc.gnu.org/wiki/GFortranBinaries下载。

NumPy f2py模块作为 Fortran 和 Python 之间的接口。如果有一个 Fortran 编译器,我们可以使用这个模块从 Fortran 代码创建一个共享库。我们将编写一个 Fortran 子程序,用于对前面例子中给出的降雨量值进行求和。定义子例程,并将其存储在 Python 字符串中。之后,我们可以调用f2py.compile()函数,从 Fortran 代码中产生一个共享库。最终产品在本书代码包的fort_src.py文件中:

from numpy import f2py 
fsource = ''' 
       subroutine sumarray(A, N) 
       REAL, DIMENSION(N) :: A 
       INTEGER :: N 
       RES = 0.1 * SUM(A, MASK = A .GT. 0) 
       RES2 = -0.025 * SUM(A, MASK = A .LT. 0) 
       print*, RES + RES2 
       end  
 ''' 
f2py.compile(fsource,modulename='fort_sum',verbose=0) 

使用以下命令执行文件以生成模块:

$ python3 fort_src.py

调用本书代码包中fort_demo.py文件给出的子程序:

import fort_sum 
import numpy as np 
rain = np.load('rain.npy') 
fort_sum.sumarray(rain, len(rain)) 
rain = .1 * rain 
rain[rain < 0] = .025 
print("Numpy", rain.sum()) 

使用以下命令执行文件以生成输出:

$ python3 fort_demo.py

Fortran 和 NumPy 的结果符合预期(我们可以忽略 Fortran 子程序打印的最后两位数字):

85291.5547
Numpy 85291.55

PythonAnywhere 云

Python 这里是 Python 开发的云服务。该界面完全基于网络,模拟了 Bash、Python 和 IPython 控制台。Python 网络环境中预先安装的 Python 库列在https://www.pythonanywhere.com/batteries_included/上。

软件版本可能会稍微落后于可用的最新稳定版本。在撰写本文时,从 PythonAnywhere Bash 控制台安装 Python 软件似乎有点问题,不建议这样做。

当您第一次访问网址https://www.pythonanywhere.com/login/时,您将看到以下屏幕以登录到 PythonAnywhere 环境:

PythonAnywhere Cloud

提交登录名和密码后,您将看到以下 web 应用程序屏幕:

PythonAnywhere Cloud

建议您上传 Python 源文件,而不是使用 PythonAnywhere 环境,因为它的响应速度不如我们的本地环境。通过点击网络应用程序中的文件标签上传文件。从本章上传bn_demo.py文件:

PythonAnywhere Cloud

要执行程序,点击bn_demp.py文件,然后点击屏幕右上角的运行按钮:

PythonAnywhere Cloud

您将在控制台的代码列表下方看到输出:

PythonAnywhere Cloud

总结

在这一章中,我们考察了 Python 的边界。在 Python 生态系统之外,诸如 R、C、Java 和 Fortran 等编程语言相当流行。我们查看了提供连接 Python 和外部代码的粘合剂的库,R 的rpy2,C 的【SWIG 和 Boost】,Java 的JPype,以及 Fortran 的f2py。云计算旨在通过互联网提供计算能力。本文还简要介绍了 Python 的一个专门的云计算服务。

下一章,第 12 章性能调优、性能分析和并发,给出了提高性能的提示。通常,我们可以通过使用并行化或用 c 语言重写部分代码来优化代码,从而加快 Python 代码的速度。我们还将讨论几种分析工具和并发 API。

十二、性能调整、性能分析和并发性

|   | “过早优化是万恶之源” |   |
|   | - 唐纳德·克努特,著名的计算机科学家和数学家 |

对于现实世界的应用程序,性能与特性、健壮性、可维护性、可测试性和可用性一样重要。性能与应用程序的可伸缩性成正比。结束这本书而不考虑性能增强从来都不是一个选择。事实上,为了避免过早优化,我们将性能这个话题推迟到了本书的最后一章讨论。在本章中,我们将给出使用概要分析作为关键技术来提高性能的提示。我们还将讨论多核分布式系统的相关框架。我们将在本章中讨论以下主题:

  • 分析代码
  • 安装 Cython
  • 调用 C 代码
  • 使用多处理创建池进程
  • 加速与 Joblib 令人尴尬的平行for循环
  • 瓶颈函数与 NumPy 函数的比较
  • 使用 Jug 执行 MapReduce
  • 为 Python 安装 MPI
  • IPython 并行

分析代码

分析包括识别需要性能调整的代码部分,因为它们要么太慢,要么使用大量资源,如处理器功率或内存。我们将从第 9 章分析文本数据和社交媒体中概述情绪分析代码的修改版本。代码经过重构,符合多处理编程准则(您将在本章后面学习多处理)。我们还简化了停止词过滤。第三个变化是在代码中有更少的单词特征,这样减少不会影响准确性。最后一个变化影响最大。原始代码运行了大约 20 秒。新代码运行速度比这更快,并将作为本章的基准。一些更改与性能分析有关,将在本节后面解释。请参考本书代码包中的prof_demo.py文件:

import random 
from nltk.corpus import movie_reviews 
from nltk.corpus import stopwords 
from nltk import FreqDist 
from nltk import NaiveBayesClassifier 
from nltk.classify import accuracy 

import builtins 

# Define profile function so the profile decorator  
#   does not return error when code is not profiled 
try: 
    profile = builtins.profile 
except AttributeError: 
    def profile(func):  
        return func 

@profile 
def label_docs(): 
    docs = [(list(movie_reviews.words(fid)), cat) 
            for cat in movie_reviews.categories() 
            for fid in movie_reviews.fileids(cat)] 
    random.seed(42) 
    random.shuffle(docs) 

    return docs 

@profile 
def isStopWord(word): 
    return word in sw or len(word) == 1 

@profile 
def filter_corpus(): 
    review_words = movie_reviews.words() 
    print("# Review Words", len(review_words)) 
    res = [w.lower() for w in review_words if not isStopWord(w.lower())] 
    print("# After filter", len(res)) 

    return res 

@profile 
def select_word_features(corpus): 
    words = FreqDist(corpus) 
    N = int(.02 * len(words.keys())) 
    return list(words.keys())[:N] 

@profile 
def doc_features(doc): 
    doc_words = FreqDist(w for w in doc if not isStopWord(w)) 
    features = {} 
    for word in word_features: 
        features['count (%s)' % word] = (doc_words.get(word, 0)) 
    return features 

@profile 
def make_features(docs): 
    return [(doc_features(d), c) for (d,c) in docs] 

@profile 
def split_data(sets): 
    return sets[200:], sets[:200] 

if __name__ == "__main__": 
    labeled_docs = label_docs() 

    sw = set(stopwords.words('english')) 
    filtered = filter_corpus() 
    word_features = select_word_features(filtered) 
    featuresets = make_features(labeled_docs) 
    train_set, test_set = split_data(featuresets) 
    classifier = NaiveBayesClassifier.train(train_set) 
    print("Accuracy", accuracy(classifier, test_set)) 
    print(classifier.show_most_informative_features())  
    print(classifier.show_most_informative_features()) 

当我们测量时间时,让尽可能少的进程运行是有帮助的。然而,我们不能确定后台没有运行,所以我们将使用time命令从三次测量中获取最低时间。这是一个可以在各种操作系统和 Cygwin 上使用的命令。按如下方式运行命令:

$ time python3 prof_demo.py

我们得到一个real时间,这是我们用时钟测量的时间。usersys时间测量程序使用的中央处理器时间。sys时间是在内核中度过的时间。在我的机器上,获得了以下以秒为单位的时间(括号内为最低值):

| **时间类型** | **运行 1** | **运行 2** | **运行 3** | | `real` | Eleven point five two one | Ten point eight zero eight | (10.416) | | `user` | Nine point seven five eight | Nine point eight two six | (9.444) | | `sys` | Zero point nine six five | Zero point six four three | (0.620) |

使用标准 Python 分析器对代码进行分析,如下所示:

$ python3 -m cProfile -o /tmp/stat.prof prof_demo.py

-o开关指定输出文件。我们可以用gprof2dot PyPi 包可视化分析器输出。按照以下步骤安装:

$ pip3 install gprof2dot 

使用以下命令创建巴布亚新几内亚可视化:

$ gprof2dot -f pstats /tmp/stat.prof |dot -Tpng -o /tmp/cprof.png

如果出现dot: command not found错误,说明没有安装 Graphviz。你可以从http://www.graphviz.org/Download.php下载 Graphviz。

完整图像太大,无法显示。这是它的一小部分:

Profiling the code

按如下方式查询探查器输出:

$ python3 -m pstats /tmp/stat.prof

使用此命令,我们进入配置文件统计浏览器。从输出中去除文件名,按时间排序,并显示前 10 次:

/tmp/stat.prof% strip
/tmp/stat.prof% sort time
/tmp/stat.prof% stats 10

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

Profiling the code

以下是标题的描述:

| **表头** | **描述** | | `ncalls` | 这是电话号码。 | | `tottime` | 这是在给定函数中花费的总时间(不包括调用子函数的时间)。 | | `percall` | 这是`tottime`除以`ncalls`的商。 | | `cumtime` | 这是在这个和所有子功能中花费的总时间(从调用到退出)。即使对于递归函数,这个数字也是准确的。 | | `percall`(秒) | 这是`cumtime`除以原始调用的商。 |

使用以下命令退出配置文件浏览器:

/tmp/stat.prof% quit

line_profiler是我们可以使用的另一个剖析器。这个分析器可以显示已经用@profile装饰器装饰的函数中每一行的统计数据。使用以下命令安装并运行此探查器:

$ pip3 install line_profiler
$ kernprof -l -v prof_demo.py

完整的报告太长,无法在此复制;相反,下面是每个功能的摘要(有一些重叠):

Function: label_docs at line 9 Total time: 6.19904 s
Function: isStopWord at line 19 Total time: 2.16542 s
File: prof_demo.py Function: filter_corpus at line 23
Function: select_word_features at line 32 Total time: 4.05266 s
Function: doc_features at line 38 Total time: 12.5919 s
Function: make_features at line 46 Total time: 14.566 s
Function: split_data at line 50 Total time: 3.6e-05 s

安装 Cython

Cython 编程语言充当了 Python 和 C/C++之间的粘合剂。有了 Cython 工具,我们可以从普通的 Python 代码生成 C 代码,然后可以编译成二进制,这更接近机器级别。cytoolz包包含由 Cythonizing 方便的 Python toolz包创建的实用程序。以下命令将安装cythoncytoolz:

$ pip3 install cython cytoolz

就像在烹饪节目中一样,我们将在完成相关过程(推迟到下一部分)之前展示 Cythonizing 的结果。timeit Python 模块测量时间。我们将使用这个模块来测量不同的功能。定义以下函数,该函数接受短代码片段、函数调用以及代码作为参数运行的次数:

def time(code, n): 
    times = min(timeit.Timer(code, setup=setup).repeat(3, n)) 

    return round(1000* np.array(times)/n, 3) 

接下来,我们预定义一个包含所有代码的大设置字符串。代码在本书代码包的timeits.py文件中(代码使用cython_module,在您的机器上构建):

import timeit 
import numpy as np 

setup = ''' 
import nltk 
import cython_module as cm 
import collections 
from nltk.corpus import stopwords 
from nltk.corpus import movie_reviews 
from nltk.corpus import names 
import string 
import pandas as pd 
import cytoolz 

sw = set(stopwords.words('english')) 
punctuation = set(string.punctuation) 
all_names = set([name.lower() for name in names.words()]) 
txt = movie_reviews.words(movie_reviews.fileids()[0]) 

def isStopWord(w): 
    return w in sw or w in punctuation 

def isStopWord2(w): 
    return w in sw or w in punctuation or not w.isalpha() 

def isStopWord3(w): 
    return w in sw or len(w) == 1 or not w.isalpha() or w in all_names 

def isStopWord4(w): 
    return w in sw or len(w) == 1 

def freq_dict(words): 
    dd = collections.defaultdict(int) 

    for word in words: 
        dd[word] += 1 

    return dd 

def zero_init(): 
    features = {} 

    for word in set(txt): 
        features['count (%s)' % word] = (0) 

def zero_init2(): 
    features = {} 
    for word in set(txt): 
        features[word] = (0) 

keys = list(set(txt)) 

def zero_init3(): 
    features = dict.fromkeys(keys, 0) 

zero_dict = dict.fromkeys(keys, 0) 

def dict_copy(): 
    features = zero_dict.copy() 
''' 

def time(code, n): 
    times = min(timeit.Timer(code, setup=setup).repeat(3, n)) 

    return round(1000* np.array(times)/n, 3) 

if __name__ == '__main__': 
    print("Best of 3 times per loop in milliseconds") 
    n = 10 
    print("zero_init ", time("zero_init()", n)) 
    print("zero_init2", time("zero_init2()", n)) 
    print("zero_init3", time("zero_init3()", n)) 
    print("dict_copy ", time("dict_copy()", n)) 
    print("\n") 

    n = 10**2 
    print("isStopWord ", time('[w.lower() for w in txt if not isStopWord(w.lower())]', n)) 
    print("isStopWord2", time('[w.lower() for w in txt if not isStopWord2(w.lower())]', n)) 
    print("isStopWord3", time('[w.lower() for w in txt if not isStopWord3(w.lower())]', n)) 
    print("isStopWord4", time('[w.lower() for w in txt if not isStopWord4(w.lower())]', n)) 
    print("Cythonized isStopWord", time('[w.lower() for w in txt if not cm.isStopWord(w.lower())]', n)) 
    print("Cythonized filter_sw()", time('cm.filter_sw(txt)', n)) 
    print("\n") 
    print("FreqDist", time("nltk.FreqDist(txt)", n)) 
    print("Default dict", time('freq_dict(txt)', n)) 
    print("Counter", time('collections.Counter(txt)', n)) 
    print("Series", time('pd.Series(txt).value_counts()', n)) 
    print("Cytoolz", time('cytoolz.frequencies(txt)', n)) 
    print("Cythonized freq_dict", time('cm.freq_dict(txt)', n)) 

因此,我们有几个isStopword()函数版本,运行时间以毫秒为单位如下:

isStopWord  0.843
isStopWord2 0.902
isStopWord3 0.963
isStopWord4 0.869
Cythonized isStopWord 0.924
Cythonized filter_sw() 0.887

作为比较,我们还有一个普通pass语句的运行时间。cytnonizedisStopWord()基于isStopWord3()功能(最精细的滤镜)。如果我们看看prof_demo.py中的doc_features()功能,很明显我们不应该仔细检查每个单词的特征。相反,我们应该只是将文档中的一组单词与作为特征选择的单词相交。所有其他字数都可以安全地设置为零。事实上,最好我们将所有的值初始化为零一次,然后复制这个字典。对于相应的函数,我们得到以下执行时间:

zero_init  0.61
zero_init2 0.555
zero_init3 0.017
dict_copy  0.011

另一个改进是使用 Python defaultdict类,而不是 NLTK FreqDist类。相关例程具有以下运行时间:

FreqDist 2.206
Default dict 0.674
Counter 0.79
Series 7.006
Cytoolz 0.542
Cythonized freq_dict 0.616

正如我们所看到的,Cythonized 版本的速度一直在加快,尽管并没有加快多少。

调用 C 代码

我们可以从 Cython 调用 C 函数。C 字符串strlen()函数相当于 Python len()函数。通过导入以下文件,从 Cython .pyx文件调用该函数:

from libc.string cimport strlen 

然后我们可以从.pyx文件中的其他地方调用strlen().pyx文件可以包含任何 Python 代码。请看一下本书代码包中的cython_module.pyx文件:

from collections import defaultdict 
from nltk.corpus import stopwords 
from nltk.corpus import names 
from libc.string cimport strlen 

sw = set(stopwords.words('english')) 
all_names = set([name.lower() for name in names.words()]) 

def isStopWord(w): 
     py_byte_string = w.encode('UTF-8') 
     cdef char* c_string = py_byte_string 
     truth = (w in sw) or (w in all_names) or (not w.isalpha()) or (strlen(c_string) == 1) 
     return truth 

def filter_sw(words): 
    return [w.lower() for w in words if not isStopWord(w.lower())] 

def freq_dict(words): 
    dd = defaultdict(int) 

    for word in words: 
        dd[word] += 1 

    return dd 

要编译这段代码,我们需要一个包含以下内容的setup.py文件:

from distutils.core import setup 
from Cython.Build import cythonize 

setup( 
    ext_modules = cythonize("cython_module.pyx") 
) 

使用以下命令编译代码:

$ python3 setup.py build_ext --inplace

以下输出显示了cython_module扩展的构建:

$python3 setup.py build_ext --inplace
running build_ext
building 'cython_module' extension
creating build
creating build/temp.macosx-10.12-x86_64-3.6
clang -Wno-unused-result -Wsign-compare -Wunreachable-code -fno-common -dynamic -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes -I/usr/local/include -I/usr/local/opt/openssl/include -I/usr/local/opt/sqlite/include -I/usr/local/Cellar/python3/3.6.0/Frameworks/Python.framework/Versions/3.6/include/python3.6m -c cython_module.c -o build/temp.macosx-10.12-x86_64-3.6/cython_module.o
clang -bundle -undefined dynamic_lookup build/temp.macosx-10.12-x86_64-3.6/cython_module.o -L/usr/local/lib -L/usr/local/opt/openssl/lib -L/usr/local/opt/sqlite/lib -o /Users/armando/gdrive/projects/bitbucket/pubs/2016-pda-e2-packt/chapters/ch-12/cython_module.cpython-36m-darwin.so

我们现在可以修改情绪分析程序来调用 Cython 函数。我们还将添加上一节中提到的改进。由于我们要反复使用一些函数,这些函数被提取到本书代码包的core.py文件中。查看本书代码包中的cython_demo.py文件(代码使用cython_module,在您的机器上构建):

from nltk.corpus import movie_reviews 
from nltk import NaiveBayesClassifier 
from nltk.classify import accuracy  
import cython_module as cm 
import cytoolz 
from core import label_docs 
from core import filter_corpus 
from core import split_data 

def select_word_features(corpus): 
    words = cytoolz.frequencies(filtered) 
    sorted_words = sorted(words, key=words.get) 
    N = int(.02 * len(sorted_words)) 

    return sorted_words[-N:] 

def match(a, b): 
    return set(a.keys()).intersection(b) 

def doc_features(doc): 
    doc_words = cytoolz.frequencies(cm.filter_sw(doc)) 

    # initialize to 0 
    features = zero_features.copy() 

    word_matches = match(doc_words, word_features) 

    for word in word_matches: 
        features[word] = (doc_words[word]) 

    return features 

def make_features(docs): 
    return [(doc_features(d), c) for (d,c) in docs] 

if __name__ == "__main__": 
    labeled_docs = label_docs() 
    filtered = filter_corpus() 
    word_features = select_word_features(filtered) 
    zero_features = dict.fromkeys(word_features, 0) 
    featuresets = make_features(labeled_docs) 
    train_set, test_set = split_data(featuresets) 
    classifier = NaiveBayesClassifier.train(train_set) 
    print("Accuracy", accuracy(classifier, test_set)) 
    print(classifier.show_most_informative_features()) 

使用 time 命令执行代码,如下所示:

$ time python3 cython_demo.py

下表总结了time命令的结果(括号内为最低值):

| **时间类型** | **运行 1** | **运行 2** | **运行 3** | | `real` | (9.639) | Nine point eight one seven | Nine point nine one two | | `user` | (9.604) | Nine point six six one | Nine point six eight three | | `sys` | (0.404) | Zero point four two four | Zero point four five one |

与之前的代码执行相比,我们可以看到性能的提升。为了便于比较,下面的时序表是从上一节复制过来的:

| **时间类型** | **运行 1** | **运行 2** | **运行 3** | | `real` | Eleven point five two one | Ten point eight zero eight | (10.416) | | `user` | Nine point seven five eight | Nine point eight two six | (9.444) | | `sys` | Zero point nine six five | Zero point six four three | (0.620) |

创建具有多处理的进程池

多处理是一个标准的 Python 模块,目标是具有多个处理器的机器。多处理通过创建多个进程围绕全局解释器锁 ( GIL )工作。

GIL 锁 Python 字节码,因此只有一个线程可以访问它。

多处理支持进程池、队列和管道。进程池是可以并行执行功能的系统进程池。队列是通常用于存储任务的数据结构。管道连接不同的进程,使得一个进程的输出成为另一个进程的输入。

Windows 没有os.fork()功能,所以我们需要确保只有导入和def块被定义在if __name__ == "__main__"块之外。

创建一个池并注册一个函数,如下所示:

   p = mp.Pool(nprocs) 

该池有一个map()方法,它是 Python map()函数的并行等价物:

p.map(simulate, [i for i in xrange(10, 50)]) 

我们将模拟一维粒子的运动。粒子执行随机游走,我们感兴趣的是计算粒子的平均末端位置。我们对不同的行走长度重复这个模拟。计算本身并不重要。重要的部分是比较多进程和单个进程的加速比。我们将使用 matplotlib 绘制加速图。完整的代码在本书代码包的multiprocessing_sim.py文件中:

from numpy.random import random_integers 
from numpy.random import randn 
import numpy as np 
import timeit 
import argparse 
import multiprocessing as mp 
import matplotlib.pyplot as plt 

def simulate(size): 
    n = 0 
    mean = 0 
    M2 = 0 

    speed = randn(10000) 

    for i in range(1000):  
        n = n + 1 
        indices = random_integers(0, len(speed)-1, size=size) 
        x = (1 + speed[indices]).prod() 
        delta = x - mean 
        mean = mean + delta/n 
        M2 = M2 + delta*(x - mean) 

    return mean 

def serial(): 
    start = timeit.default_timer() 

    for i in range(10, 50): 
        simulate(i) 

    end = timeit.default_timer() - start 
    print("Serial time", end) 

    return end 
def parallel(nprocs): 
    start = timeit.default_timer() 
    p = mp.Pool(nprocs) 
    print(nprocs, "Pool creation time", timeit.default_timer() - start) 

    p.map(simulate, [i for i in range(10, 50)]) 
    p.close() 
    p.join() 

    end = timeit.default_timer() - start 
    print(nprocs, "Parallel time", end) 
    return end 

if __name__ == "__main__": 
    ratios = [] 
    baseline = serial() 

    for i in range(1, mp.cpu_count()): 
        ratios.append(baseline/parallel(i)) 

    plt.xlabel('# processes') 
    plt.ylabel('Serial/Parallel') 
    plt.plot(np.arange(1, mp.cpu_count()), ratios) 
    plt.grid(True) 
    plt.show() 

如果我们取范围从 1 到 8 的进程池大小的加速值(处理器的数量取决于硬件),我们得到下图:

Creating a process pool with multiprocessing

阿姆达尔定律(见http://en.wikipedia.org/wiki/Amdahl%27s_law)最能描述并行化带来的加速。这个定律预测了最大可能的加速。进程的数量限制了绝对最大加速。然而,正如我们在前面的图中看到的,我们没有用两个进程获得双倍的速度,也没有用三个进程获得三倍的速度,但是我们接近了。任何给定 Python 代码的某些部分都可能无法并行化。例如,我们可能需要等待资源变得可用,或者我们可能正在执行一个必须顺序执行的计算。我们还必须考虑来自并行化设置和相关进程间通信的开销。阿姆达尔定律指出,加速的倒数、进程数的倒数和代码中不能并行的部分之间存在线性关系。

加快了与 Joblib 循环的并行速度,令人尴尬

Joblib 是由 scikit-learn 的开发人员创建的 Python 库。它的主要任务是提高长时间运行的 Python 函数的性能。Joblib 通过使用多处理或幕后线程的缓存和并行化实现了这些改进。按照以下步骤安装 Joblib:

$ pip3 install joblib

我们将重复使用前面例子中的代码,只改变parallel()函数。参考本书代码包中的joblib_demo.py文件:

def parallel(nprocs): 
    start = timeit.default_timer() 
    Parallel(nprocs)(delayed(simulate)(i) for i in xrange(10, 50)) 

    end = timeit.default_timer() - start 
    print(nprocs, "Parallel time", end) 
    return end 

最终结果见下图(处理器数量取决于硬件):

Speeding up embarrassingly parallel for loops with Joblib

比较瓶颈和 NumPy 函数

瓶颈是一组受 NumPy 和 SciPy 启发的函数,但在 Cython 中编写时考虑到了高性能。瓶颈为数组维度、轴和数据类型的每个组合提供单独的 Cython 函数。这不会显示给最终用户,瓶颈的限制因素是要确定执行哪个 Cython 功能。安装瓶颈,如下所示:

$ pip3 install bottleneck

我们将比较numpy.median()scipy.stats.rankdata()函数相对于它们的瓶颈对应物的执行时间。在紧密循环或经常调用的函数中使用 Cython 函数之前,手动确定它可能是有用的。

该程序在本书代码包的bn_demo.py文件中给出:

import bottleneck as bn 
import numpy as np 
import timeit 

setup = ''' 
import numpy as np 
import bottleneck as bn 
from scipy.stats import rankdata 

np.random.seed(42) 
a = np.random.randn(30) 
''' 
def time(code, setup, n): 
    return timeit.Timer(code, setup=setup).repeat(3, n) 

if __name__ == '__main__': 
    n = 10**3 
    print(n, "pass", max(time("pass", "", n))) 
    print(n, "min np.median", min(time('np.median(a)', setup, n))) 
    print(n, "min bn.median", min(time('bn.median(a)', setup, n))) 
    a = np.arange(7) 
    print)"Median diff", np.median(a) - bn.median(a)) 

    print(n, "min scipy.stats.rankdata", min(time('rankdata(a)', setup, n))) 
    print(n, "min bn.rankdata", min(time('bn.rankdata(a)', setup, n))) 

以下是带有运行时间和函数名的输出:

$ python3 bn_demo.py 
1000 pass 7.228925824165344e-06
1000 min np.median 0.019842895912006497
1000 min bn.median 0.0003261091187596321
Median diff 0.0
1000 min scipy.stats.rankdata 0.04070987505838275
1000 min bn.rankdata 0.0011222949251532555 

显然,瓶颈很快;不幸的是,由于它的设置,瓶颈还没有那么多功能。下表列出了从http://pypi.python.org/pypi/Bottleneck实现的功能:

| **类别** | **功能** | | NumPy/SciPy | `median`、`nanmedian`、`rankdata`、`ss`、`nansum`、`nanmin`、`nanmax`、`nanmean`、`nanstd`、`nanargmin`和`nanargmax` | | 功能 | `nanrankdata`、`nanvar`、`partsort`、`argpartsort`、`replace`、`nn`、`anynan`和`allnan` | | 移动窗口 | `move_sum`、`move_nansum`、`move_mean`、`move_nanmean`、`move_median`、`move_std`、`move_nanstd`、`move_min`、`move_nanmin`、`move_max`和`move_nanmax` |

用 Jug 执行 MapReduce

Jug 是一个分布式计算框架,使用任务作为中央并行化单元。Jug 使用文件系统或 Redis 服务器作为后端。Redis 服务器在第 8 章使用数据库中进行了讨论。使用以下命令安装 Jug:

$ pip3 install jug

MapReduce (参见【http://en.wikipedia.org/wiki/MapReduce】)是一种分布式算法,用于用一组计算机处理大型数据集。该算法包括一个映射和一个减少阶段。在地图阶段,数据以并行方式处理。数据被分成多个部分,在每个部分上,执行过滤或其他操作。在缩减阶段,来自映射阶段的结果被聚合,例如,创建一个统计报告。

如果我们有一个文本文件列表,我们可以计算每个文件的字数。这可以在映射阶段完成。最后,我们可以将单个的字数合并成一个语料库词频词典。Jug 具有 MapReduce 功能,这在本书的代码包中的jug_demo.py文件中进行了演示(代码取决于cython_module工件):

import jug.mapreduce 
from jug.compound import CompoundTask 
import cython_module as cm 
import cytoolz 
import pickle 

def get_txts(): 
    return [(1, 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'), (2, 'Donec a elit pharetra, malesuada massa vitae, elementum dolor.'), (3, 'Integer a tortor ac mi vehicula tempor at a nunc.')] 

def freq_dict(file_words): 
    filtered = cm.filter_sw(file_words[1].split()) 

    fd = cytoolz.frequencies(filtered) 

    return fd 

def merge(left, right): 
    return cytoolz.merge_with(sum, left, right) 

merged_counts = CompoundTask(jug.mapreduce.mapreduce, merge, freq_dict, get_txts(), map_step=1) 

在前面的代码中,merge()函数在减少阶段被调用,freq_dict()函数在映射阶段被调用。我们定义了一个由多个子任务组成的 Jug CompoundTask。在运行这段代码之前,我们需要启动一个 Redis 服务器。通过发出以下命令来执行 MapReduce:

$ jug execute jug_demo.py --jugdir=redis://127.0.0.1/&

结尾的&符号(&)表示该命令在后台运行。如果 Redis 服务器可以在网络中访问,我们可以通过这种方式从多台计算机发出命令。在这个例子中,Redis 只在本地机器上运行(127.0.0.1是本地主机的 IP 地址)。但是,我们仍然可以在本地多次运行该命令。我们可以如下检查jug命令的状态:

$ jug status jug_demo.py

默认情况下,如果我们不指定jugdir选项,Jug 会将数据存储在当前工作目录中。使用以下命令清理jug目录:

$ jug cleanup jug_demo.py

为了查询 Redis 并执行剩下的分析,我们将使用另一个程序。在这个程序中,初始化 Jug 如下:

jug.init('jug_demo.py', 'redis://127.0.0.1/') 
import jug_demo 

下面一行是减少阶段的结果:

words = jug.task.value(jug_demo.merged_counts) 

其余代码在本书代码包的jug_redis.py文件中给出:

import jug 

def main(): 
    jug.init('jug_demo.py', 'redis://127.0.0.1/') 
    import jug_demo 
    print("Merged counts", jug.task.value(jug_demo.merged_counts)) 

if __name__ == "__main__": 
    main() 

为 Python 安装 MPI

消息传递接口 ( MPI )(参见http://en.wikipedia.org/wiki/Message_Passing_Interface)是由专家开发的标准协议,用于广泛的分布式机器。最初,在九十年代,MPI 被用来编写 Fortran 和 c 语言的程序,MPI 独立于硬件和编程语言。MPI 功能包括发送和接收操作、MapReduce 功能和同步。MPI 具有涉及两个处理器的点对点功能,以及涉及所有处理器的操作。MPI 有几种编程语言的绑定,包括 Python。从http://www.open-mpi.org/software/ompi/下载 MPI。MPI 2.0.2 在编写本报告时已经安装并使用;我们可以在网站上查看是否有更新的版本。安装 MPI 可能需要一段时间(近 30 分钟)。以下是所涉及的命令,假设我们将其安装在/usr/local目录中:

$ ./configure --prefix=/usr/local
$ make all
$ sudo make install

为 MPI 安装 Python 绑定,如下所示:

$ pip3 install mpi4py

IPython 并行

IPython Parallel 是用于并行计算的 IPython API。我们将设置它使用 MPI 进行消息传递。我们可能必须按如下方式设置环境变量:

$ export LC_ALL=en_US.UTF-8
$ export LANG=en_US.UTF-8

在命令行中发出以下命令:

$ ipython3 profile create --parallel --profile=mpi

前面的命令将在位于home目录的.ipython/profile_mpi文件夹中创建几个文件。

按如下方式启动使用 MPI 配置文件的集群:

$ ipcluster start --profile=mpi --engines=MPI --debug

前面的命令指定我们使用的是带有调试级日志记录的mpi概要文件和 MPI 引擎。我们现在可以通过 IPython 笔记本与集群进行交互。启动笔记本,启用绘图,并自动导入 NumPy、SciPy 和 matplotlib,如下所示:

$ jupyter-notebook --profile=mpi --log-level=DEBUG 

上述命令使用调试日志级别的mpi配置文件。本例的笔记本存储在本书代码包的IPythonParallel.ipynb文件中。导入 IPython 并行Client类和statsmodels.api模块,如下所示:

  In [1]:from ipyparallel import Client 
  import statsmodels.api as sm 

加载太阳黑子数据并计算平均值:

 In [2]: data_loader = sm.datasets.sunspots.load_pandas() 
 vals = data_loader.data['SUNACTIVITY'].values 
 glob_mean = vals.mean() 
 glob_mean 

输出如下:

Out [2]: 49.752103559870541

按照以下方式创建客户端:

In [3]: c = Client(profile='mpi') 

使用以下行创建客户端视图:

In [4]: view=c[:] 

IPython 使用魔法的概念。这些是 IPython 笔记本特有的特殊命令。按如下方式启用 magics:

In [5]: view.activate() 

在本书的代码包中加载mpi_ipython.py文件:

from mpi4py import MPI 
from numpy.random import random_integers 
from numpy.random import randn 
import numpy as np 
import statsmodels.api as sm 
import bottleneck as bn 
import logging 

def jackknife(a, parallel=True): 
    data_loader = sm.datasets.sunspots.load_pandas() 
    vals = data_loader.data['SUNACTIVITY'].values 

    results = [] 

    for i in a: 
        tmp = np.array(vals.tolist()) 
        tmp[i] = np.nan 
        results.append(bn.nanmean(tmp)) 
    results = np.array(results) 

    if parallel: 
        comm = MPI.COMM_WORLD 
        rcvBuf = np.zeros(0.0, 'd') 
        comm.gather([results, MPI.DOUBLE], [rcvBuf, MPI.DOUBLE]) 

   return results 

if __name__ == "__main__": 
    skiplist = np.arange(39, dtype='int') 
    print(jackknife(skiplist, False)) 

前面的程序包含一个执行刀切重采样的功能。刀切重采样是重采样的一种,我们省略样本中的一个观测值,然后计算我们感兴趣的统计估计量。在这种情况下,我们对平均值感兴趣。我们将一个观察值设置为 NumPy NaN,从而将其忽略。然后,我们在新样本上调用瓶颈nanmean()函数。以下是加载命令:

In [6]: view.run('mpi_ipython.py') 

接下来,我们分割并展开一个包含太阳黑子数组所有指数的数组:

In [7]: view.scatter('a',np.arange(len(vals),dtype='int')) 

a数组可以在笔记本中显示如下:

In [8]: view['a'] 

以下是前面命令的输出:

Out[8]:[array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16,  17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33,  34, 35, 36, 37, 38]), ... TRUNCATED ...]

在所有客户端上调用jackknife()功能:

In [9]: %px means = jackknife(a) 

完成所有工作进程后,我们可以查看结果:

In [10]: view['means'] 

结果列表显示了与我们开始时一样多的进程。每个进程返回一个 NumPy 数组,其中包含通过刀切重采样计算的平均值。这个结构不是很有用,所以把它转换成一个平面列表:

In [11]: all_means = [] 

for v in view['means']: 
    all_means.extend(v) 

mean(all_means) 

您将获得以下输出:

Out [11]: 49.752103559870577

我们也可以计算标准差,但这很容易,所以我们将跳过它。画出折刀的直方图更有趣:

In [13]: hist(all_means, bins=sqrt(len(all_means))) 

最终结果见下图:

对于故障排除,我们可以使用下面一行显示工作进程的错误消息:

In [14]: [(k, c.metadata[k]['started'], c.metadata[k]['pyout'], c.metadata[k]['pyerr']) for k in c.metadata.keys()] 

总结

在这一章中,我们从第 9 章分析文本数据和社交媒体调整了情绪分析脚本的表现。使用概要分析、Cython 和各种改进,我们将该示例的执行速度提高了一倍。我们还通过 IPython Parallel 使用了多处理、Joblib、Jug 和 MPI 来利用并行化。

这是这本书的最后一章。当然,学习的过程不应该停止。更改代码以满足您的需求。有一个私人的数据分析项目总是很好的,即使只是为了练习。如果想不出项目,可以参加http://www.kaggle.com/上的比赛。他们有好几个比赛,奖品很好。

十三、附录 a:关键概念

本附录给出了本书使用的技术概念的简要概述和词汇表。

阿姆达尔定律预测了由于并行化可能带来的最大加速。进程的数量限制了绝对最大加速。任何给定 Python 代码的某些部分都可能无法并行化。我们还必须考虑并行化设置和相关进程间通信的开销。阿姆达尔定律指出,加速的倒数、进程数的倒数和不能并行化的代码部分之间存在线性关系。

ARMA 模型结合了自回归和移动平均模型。它们用于预测时间序列的未来值。

人工神经网络 ( ANN )是受动物大脑启发的模型。神经网络是由神经元组成的网络,神经元是具有输入和输出的单元。一个神经元的输出可以传递给一个神经元等等,从而形成一个多层网络。神经网络包含自适应元素,使其适合处理非线性模型和模式识别问题。

增广 Dickey Fuller ( ADF )检验是一个与协整相关的统计检验。ADF 检验用于检验时间序列的平稳性。

自相关是数据集内的相关性,可以指示趋势。例如,如果我们有一个周期的滞后,我们可以检查前一个值是否影响当前值。要做到这一点,自相关值必须相当高。

自相关绘制不同滞后时间序列数据的自相关图。自相关是时间序列与相同滞后时间序列的相关性。

自回归模型是一种使用(通常是线性)回归来使用以前的值预测时间序列的未来值的模型。自回归模型是 ARMA 模型的一个特例。它们相当于零移动平均成分的 ARMA 模型。

单词包模型是一个简化的文本模型,其中文本由一个单词包来表示。在此表示中,单词的顺序被忽略。通常,字数或某些单词的存在被用作该模型中的特征。

气泡图是散点图的延伸。在气泡图中,第三个变量的值由数据点周围气泡的大小表示。

卡珊德拉查询语言 ( CQL )是一种针对 Apache 卡珊德拉的查询语言,语法类似于 SQL。

协整类似于相关性,是时间序列数据的一个统计特征。协整是衡量两个时间序列同步程度的指标。

聚类旨在将数据划分为称为聚类的组。在训练数据没有标记的意义上,聚类通常是无监督的。一些聚类算法需要猜测聚类的数量,而其他算法则不需要。

层叠样式表 ( CSS )是一种用于设计网页元素样式的语言。CSS 由万维网联盟维护和开发。

CSS 选择器是用于选择网页内容的规则。

字符代码包含在 NumPy 中,以向后兼容数字。NumPy 的前身是 NumPy。

数据类型对象是 numpy.dtype 类的实例。它们为 NumPy 数据类型的操作提供了一个面向对象的接口。

特征值是方程 Ax = ax 的标量解,其中 A 是二维矩阵,x 是一维向量。

特征向量是对应于特征值的向量。

指数移动平均线是一种权重随时间呈指数递减的移动平均线。

快速傅里叶变换 ( FFT )是一种计算傅里叶变换的快速算法。FFT 是 O(N log N),这是对旧算法的巨大改进。

滤波是一种信号处理技术,包括去除或抑制部分信号。存在许多滤波器类型,包括中值滤波器和维纳滤波器。

傅立叶分析是基于以数学家约瑟夫·傅立叶命名的傅立叶级数。傅立叶级数是一种将函数表示为正弦和余弦项的无穷级数的数学方法。所讨论的函数可以是实数或复数。

遗传算法基于生物进化论。这种类型的算法对于搜索和优化很有用。

图形处理器单元 ( 图形处理器)是用于高效显示图形的专用电路。最近,图形处理器被用来执行大规模并行计算(例如,训练神经网络)。

分层数据格式 ( HDF )是存储大数值数据的规范和技术。HDF 集团维护着一个相关的软件库。

希尔伯特-黄变换是一种分解信号的数学算法。该方法可用于检测时间序列数据中的周期性周期。它被成功地用于确定太阳黑子周期。

超文本标记语言 ( HTML )是用来创建网页的基础技术。它为媒体、文本和超链接定义标签。

互联网工程任务组 ( IETF )是一个致力于维护和发展互联网的开放团体。IETF 是开放的,原则上任何人都可以加入。

JavaScript 对象符号 ( JSON )是一种数据格式。在这种格式中,数据是用 JavaScript 符号写下来的。JSON 比 XML 等其他数据格式更简洁。

k-fold 交叉验证是一种涉及 k(一个小整数)个随机数据分区的交叉验证形式,称为 fold。在 k 次迭代中,每个折叠使用一次进行验证,其余的数据用于训练。迭代的结果可以在最后合并。

Kruskal-Wallis 单向方差分析是一种分析样本方差的统计方法,不需要对样本的分布做假设。

滞后图是一个时间序列和同一时间序列滞后的散点图。滞后图显示了特定滞后时间序列数据中的自相关。

学习曲线是一种可视化学习算法行为的方式。它是一系列训练数据大小的训练和测试分数图。

对数图(或对数图)是使用对数刻度的图。当数据变化很大时,这种类型的图很有用,因为它们显示数量级。

逻辑回归是一种分类算法。这种算法可以用来预测一个类或事件发生的概率。逻辑回归基于逻辑函数,其值在 0 到 1 之间,就像概率一样。因此,逻辑函数可用于将任意值转换为概率。

MapReduce 是一种分布式算法,用于处理具有一组计算机的大型数据集。该算法由映射阶段和缩减阶段组成。在地图阶段,数据以并行方式处理。数据被分成多个部分,在每个部分上,执行过滤或其他操作。在缩减阶段,将汇总地图阶段的结果。

摩尔定律是指现代计算机芯片中的晶体管数量每两年翻一番的观察结果。自 1970 年左右摩尔定律形成以来,这一趋势一直在持续。还有第二个摩尔定律,也叫洛克定律。该定律指出,集成电路的研发和制造成本呈指数级增长。

移动平均线指定了一个以前看到的数据窗口,每次该窗口向前滑动一个周期时,该窗口将被平均。不同类型的移动平均在用于平均的权重上有本质的不同。

朴素贝叶斯分类是一种基于概率论和统计学中贝叶斯定理的概率分类算法。它被称为幼稚,因为它有很强的独立性假设。

对象关系映射 ( ORM )是一种用于在数据库模式和面向对象编程语言之间进行转换的软件架构模式。

观点挖掘情感分析是一个以高效发现和评价文本中的观点和情感为目标的研究领域。

词性 ( POS )标签是句子中每个单词的标签。这些标签有语法意义,如动词或名词。

表示状态转移 ( REST )是一种用于 web 服务的架构风格。

真正简单的联合 ( RSS )是博客等网络订阅源的发布和检索标准。

散点图是显示笛卡尔坐标系中两个变量之间关系的二维图。一个变量的值用一个轴表示,另一个变量的值用另一个轴表示。我们可以通过这种方式快速可视化相关性。

信号处理是工程和应用数学的一个领域,处理模拟和数字信号的分析,对应随时间变化的变量。

SQL 是关系数据库查询和操作的专用语言。这包括创建表、在表中插入行和删除表。

Stopwords 是低信息值的常用词。通常在分析文本之前会删除停用词。虽然过滤停用词是一种常见的做法,但停用词没有标准的定义。

监督学习是一种需要标注训练数据的机器学习类型。

支持向量机 ( SVM )可用于回归(SVR)和分类(SVC)。SVM 将数据点映射到多维空间中的点。映射由所谓的内核函数执行。核函数可以是线性的或非线性的。

词频-逆文档频率 ( tf-idf )是一个度量一个单词在语料库中重要性的指标。它由术语频率号和逆文档频率号组成。术语“频率”统计单词在文档中出现的次数。逆文档频率计算单词出现的文档数,取该数的倒数。

一个时间序列是一个有序的数据点列表,从最早的测量值开始。通常,每个数据点都有一个相关的时间戳。时间序列可以是平稳的,也可以是非平稳的。

十四、附录 b:实用功能

本附录列出了 matplotlib、NumPy、Pandas、scikit-learn 和 SciPy 的软件包组织的有用功能。

Matplotlib

以下是有用的 matplotlib 函数:

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):此功能显示类似数组数据的图像。

matplotlib.pyplot.legend(*args, **kwargs):该功能在可选的指定位置(例如plt.legend(loc='best'))显示一个图例。

matplotlib.pyplot.plot(*args, **kwargs):这个函数创建一个二维图,其中有一个或多个(x,y)对和一个相应的可选格式字符串。

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):此功能显示一个图。

matplotlib.pyplot.subplot(*args, **kwargs):如果给定了图的行号、列号和索引号,这个函数会创建子图。所有这些数字都是从一开始的。例如,plt.subplot(221)在一个二乘二的网格中创建第一个子场景。

matplotlib.pyplot.title(s, *args, **kwargs):这个功能给剧情加上一个标题。

NumPy

以下是有用的 NumPy 函数:

numpy.arange([start,] stop[, step,], dtype=None):此函数创建一个 NumPy 数组,其值在指定范围内均匀分布。

numpy.argsort(a, axis=-1, kind='quicksort', order=None):该函数返回对输入数组进行排序的索引。

numpy.array(object, dtype=None, copy=True, order=None, subok=False, ndmin=0):这个函数从类似数组的序列比如 Python 列表中创建一个 NumPy 数组。

numpy.dot(a, b, out=None):这个函数计算两个数组的点积。

numpy.eye(N, M=None, k=0, dtype=<type 'float'>):此函数返回单位矩阵。

numpy.load(file, mmap_mode=None):该函数从加载 NumPy 数组或腌制对象。npy,。npz,或者泡菜。内存映射数组存储在文件系统中,不必完全加载到内存中。这对于大型数组尤其有用。

numpy.loadtxt(fname, dtype=<type 'float'>, comments='#', delimiter=None, converters=None, skiprows=0, usecols=None, unpack=False, ndmin=0):该函数将文本文件中的数据加载到 NumPy 数组中。

numpy.mean(a, axis=None, dtype=None, out=None, keepdims=False):此函数计算沿给定轴的算术平均值。

numpy.median(a, axis=None, out=None, overwrite_input=False):此函数计算沿给定轴的中位数。

numpy.ones(shape, dtype=None, order='C'):这个函数创建一个指定形状和数据类型的 NumPy 数组,包含一个。

numpy.polyfit(x, y, deg, rcond=None, full=False, w=None, cov=False):该函数执行最小二乘多项式拟合。

numpy.reshape(a, newshape, order='C'):这个函数改变 NumPy 数组的形状。

numpy.save(file, arr):该功能将 NumPy 数组保存为 NumPy .npy格式的文件。

numpy.savetxt(fname, X, fmt='%.18e', delimiter=' ', newline='\n', header='', footer='', comments='# '):该函数将 NumPy 数组保存到文本文件中。

numpy.std(a, axis=None, dtype=None, out=None, ddof=0, keepdims=False):此函数返回沿给定轴的标准偏差。

numpy.where(condition, [x, y]):该函数根据布尔条件从输入数组中选择数组元素。

numpy.zeros(shape, dtype=float, order='C'):这个函数创建一个指定形状和数据类型的 NumPy 数组,包含零。

Pandas

以下是有用的 Pandas 功能:

pandas.date_range(start=None, end=None, periods=None, freq='D', tz=None, normalize=False, name=None, closed=None):这个函数创建一个固定频率的日期时间索引

pandas.isnull(obj):此函数查找 NaN 和 None 值

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):此函数将数据框对象与列或索引上类似数据库的连接合并

pandas.pivot_table(data, values=None, rows=None, cols=None, aggfunc='mean', fill_value=None, margins=False, dropna=True):这个函数创建一个类似电子表格的数据透视表作为 Pandas 数据框

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):这个函数从 CSV 文件创建一个数据帧

pandas.read_excel(io, sheetname, **kwds):该函数将 Excel 工作表读入数据框

pandas.read_hdf(path_or_buf, key, **kwargs):这个函数从 HDF 商店返回一个 Pandas 对象

pandas.read_json(path_or_buf=None, orient=None, typ='frame', dtype=True, convert_axes=True, convert_dates=True, keep_default_dates=True, numpy=False, precise_float=False, date_unit=None):这个函数从一个 JSON 字符串创建一个 pandas 对象

pandas.to_datetime(arg, errors='ignore', dayfirst=False, utc=None, box=True, format=None, coerce=False, unit='ns', infer_datetime_format=False):此函数将字符串或字符串列表转换为日期时间

科学学习

以下是有用的 scikit 学习功能:

sklearn.cross_validation.train_test_split(*arrays, **options):该功能将数组拆分为随机训练集和测试集

sklearn.metrics.accuracy_score(y_true, y_pred, normalize=True, sample_weight=None):此功能返回准确率分类得分

sklearn.metrics.euclidean_distances (X, Y=None, Y_norm_squared=None, squared=False):该函数计算输入数据的距离矩阵

黑桃

本节显示了有用的 SciPy 函数:

scipy.fftpack

fftshift(x, axes=None):该功能将零频率分量移至频谱中心

rfft(x, n=None, axis=-1, overwrite_x=0):该函数对包含实值的数组进行离散傅里叶变换

scipy .信号

detrend(data, axis=-1, type='linear', bp=0):此函数从数据中移除线性趋势或常数

medfilt(volume, kernel_size=None):该函数对数组应用中值滤波

wiener(im, mysize=None, noise=None):该函数对数组应用维纳滤波器

scipy . state

anderson(x, dist='norm'):该函数对来自指定分布的数据执行安德森-达林测试

kruskal(*args):该功能对数据进行克鲁斯卡尔-沃利斯 H 测试

normaltest(a, axis=0):该功能测试数据是否符合正态分布

scoreatpercentile(a, per, limit=(), interpolation_method='fraction'):该函数计算输入数组中指定百分比的分数

shapiro(x, a=None, reta=False):该函数应用夏皮罗-维尔克检验进行正态性

十五、附录 c:在线资源

以下是文档、论坛、文章和其他信息的链接列表:

阿帕奇卡珊德拉数据库:http://cassandra.apache.org

靓汤:https://www.crummy.com/software/BeautifulSoup/

HDF 集团网站:https://www.hdfgroup.org/

有趣的 ipython 笔记本图库:https://github . com/IPython/IPython/wiki/A-有趣的 IPython 笔记本图库

Graphviz 开源图形可视化软件:http://graphviz.org/

IPython 网站:http://ipython.org/

Jupyter 网站:http://jupyter.org/

Matplotlib(一个 Python 绘图库):http://matplotlib.org/

MongoDB(开源文档数据库):http://www.mongodb.org

mpi4 py 文档:http://mpi4py.scipy.org/docs/usrman/index.html

自然语言工具包(NLTK):http://www.nltk.org/

NumPy 和 SciPy 文档:http://docs.scipy.org/doc/

NumPy 和 SciPy 邮件列表:http://www.scipy.org/Mailing_Lists

打开 MPI(高性能消息传递库):http://www.open-mpi.org

Packt 发布帮助和支持:http://www.packtpub.com/support

Pandas 主页:http://pandas.pydata.org

Python 性能提示:https://wiki.python.org/moin/PythonSpeed/PerformanceTips

Redis(开源键值存储):http://redis.io/

Scikit-learn(Python 机器学习):http://scikit-learn.org/stable/

Scikit-学习性能提示:http://scikit-learn.org/stable/developers/performance.html

SciPy 性能提示:http://wiki.scipy.org/PerformanceTips

SQLAlchemy(Python SQL 工具包和对象关系映射器):http://www.sqlalchemy.org

Toolz 实用程序功能文档:http://toolz.readthedocs.org/en/latest/

plotplotlib 图形转换器:https://plot.ly/matplotlib/getting-started/

离线使用 Python Plotly:https://plot.ly/python/offline/

保存静态图片(PNG、PDF 等):https://plot.ly/python/static-image-export/

用 Python 创建 HTML 或 PDF 报告:https://plot.ly/python/#report-generation

贵公司的安全和 Plotly 的服务器:https://plot.ly/products/on-premise/

用 Plotly 和 Python 创建仪表盘:https://plot.ly/python/dashboard/

连接数据库:https://plot.ly/python/#databases

Plotly 和 IPython / Jupyter 笔记本:https://plot.ly/ipython-notebooks/

posted @ 2025-10-23 15:21  绝不原创的飞龙  阅读(6)  评论(0)    收藏  举报