Python-数据可视化秘籍-全-
Python 数据可视化秘籍(全)
零、前言
最好的数据是我们能看到和理解的数据。作为开发人员,我们希望创建和构建最全面、最容易理解的可视化。它并不总是简单的;我们需要找到数据,阅读它,清洗它,按摩它,然后使用正确的工具来可视化它。这本书解释了如何用简单明了的方法来阅读、清理数据并将其可视化为信息的过程。
如何读取本地数据、远程数据、CSV、JSON 以及关系数据库中的数据,这本书都有讲解。
使用 matplotlib 可以在 Python 中用简单的一行代码绘制一些简单的图表,但是做更高级的图表需要的知识不仅仅是 Python。我们需要理解信息论和人类感知美学来产生最吸引人的视觉化。
这本书将解释 Python 中 matplotlib 绘图背后的一些实践,使用的统计数据,以及我们应该以最佳方式使用的不同图表功能的使用示例。
这本书是用 Python 2.7、IPython 0.13.2、virtualenv 1.9.1、matplotlib 1.2.1、NumPy 1.7.1 和 SciPy 0.11.0 编写的,代码是在 Ubuntu 12.03 上开发的。
这本书涵盖了什么
第 1 章、准备您的工作环境,涵盖了一套关于如何在您的平台上安装所需 Python 包和库的安装方法和建议。
第 2 章、了解你的数据,向你介绍常见的数据格式以及如何读写它们,无论是 CSV、JSON、XSL 还是关系数据库。
第三章、绘制你的第一个图并定制它们,从绘制简单的图开始,涵盖了一些定制。
第 4 章、更多绘图和定制,延续前一章,涵盖更高级的图表和网格定制。
第 5 章、制作三维可视化,涵盖三维数据可视化,如三维条、三维直方图,以及 matplotlib 动画。
第 6 章、用图像和地图绘制图表,涵盖图像处理、将数据投影到地图上以及创建验证码测试图像。
第 7 章、使用正确的图来理解数据,涵盖了一些更高级的绘图技术的解释和配方,如光谱图和相关性。
第 8 章、更多关于 matplotlib Gems 的内容,涵盖了甘特图、箱线图、触须图等一系列图表,还说明了如何在 matplotlib 中使用 LaTeX 渲染文本。
这本书你需要什么
对于这本书,您需要在操作系统上安装 Python 2.7.3 或更高版本。这本书是使用 Ubuntu 12.03 的 Python 默认版本(2.7.3)编写的。
本书使用的其他软件包是 IPython,这是一个非常强大、灵活的交互式 Python 环境。这可以使用基于 Linux 的操作系统的包管理器来安装,也可以使用为 Windows 和 Mac 操作系统准备的安装程序来安装。
如果您是 Python 安装和软件安装的新手,非常建议您使用预打包的科学 Python 发行版,如 Anaconda、entnown Python 发行版或 Python(X,Y)。
其他所需软件主要由 Python 包组成,这些包都是使用 Python 安装管理器pip安装的,该管理器本身是使用 Python 的easy_install安装工具安装的。
这本书是给谁的
Python 数据可视化烹饪书是为已经对 Python 编程有了大致了解的开发人员准备的。如果你听说过数据可视化,但不知道从哪里开始,这本书将从一开始就指导你,帮助你理解数据、数据格式、数据可视化,以及如何使用 Python 可视化数据。
你需要了解一些通用的编程概念,任何编程经验都会有所帮助。然而,这本书里的代码几乎是一行行解释的。这本书不需要数学;引入的每个概念都用简单的英语进行了全面的解释,并且有参考资料可以让你对这个主题产生进一步的兴趣。
惯例
在这本书里,你会发现许多区分不同种类信息的文本风格。以下是这些风格的一些例子,以及对它们的含义的解释。
文本中的码字如下所示:“我们在类DemoPIL中打包了我们的小演示,这样我们可以轻松扩展,同时共享围绕演示函数run_fixed_filters_demo的公共代码。”
代码块设置如下:
def _load_image(self, imfile):
self.im = mplimage.imread(imfile)
当我们希望将您的注意力吸引到代码块的特定部分时,相关的行或项目以粗体显示:
# tidy up tick labels size
all_axes = plt.gcf().axes
for ax in all_axes:
for ticklabel in ax.get_xticklabels() + ax.get_yticklabels():
ticklabel.set_fontsize(10)
任何命令行输入或输出都编写如下:
$ sudo python setup.py install
新名词和重要词语以粗体显示。你在屏幕上看到的单词,例如在菜单或对话框中,出现在如下文本中:“然后我们为主干图和基线位置设置一个标签,默认为 0
注
警告或重要提示会出现在这样的框中。
型式
提示和技巧是这样出现的。
读者反馈
我们随时欢迎读者的反馈。让我们知道你对这本书的看法——你喜欢或可能不喜欢什么。读者反馈对我们开发您真正能从中获得最大收益的标题非常重要。
要给我们发送一般反馈,只需向<[feedback@packtpub.com](mailto:feedback@packtpub.com)>发送电子邮件,并通过您的消息主题提及书名。
如果你对某个主题有专业知识,并且对写作或投稿感兴趣,请参阅我们在www.packtpub.com/authors上的作者指南。
客户支持
现在,您已经自豪地拥有了一本书,我们有许多东西可以帮助您从购买中获得最大收益。
下载示例代码
您可以从您在http://www.packtpub.com的账户中下载您购买的所有 Packt 书籍的示例代码文件。如果您在其他地方购买了这本书,您可以访问http://www.packtpub.com/support并注册,以便将文件直接通过电子邮件发送给您。
勘误表
尽管我们尽了最大努力来确保我们内容的准确性,但错误还是会发生。如果你在我们的某本书里发现了错误——可能是文本或代码中的错误——如果你能向我们报告,我们将不胜感激。通过这样做,你可以让其他读者免受挫折,并帮助我们改进这本书的后续版本。如果您发现任何勘误表,请访问http://www.packtpub.com/submit-errata,选择您的书籍,点击勘误表提交表链接,并输入您的勘误表的详细信息。一旦您的勘误表得到验证,您的提交将被接受,勘误表将上传到我们的网站上,或添加到该标题的勘误表部分下的任何现有勘误表列表中。通过从http://www.packtpub.com/support中选择您的标题,可以查看任何现有的勘误表。
盗版
互联网上版权材料的盗版是所有媒体的一个持续问题。在 Packt,我们非常重视版权和许可证的保护。如果您在互联网上遇到任何形式的我们作品的非法拷贝,请立即向我们提供位置地址或网站名称,以便我们寻求补救。
请通过<[copyright@packtpub.com](mailto:copyright@packtpub.com)>联系我们,获取疑似盗版资料的链接。
我们感谢您在保护我们作者方面的帮助,以及我们为您带来有价值内容的能力。
问题
如果您对本书的任何方面有问题,可以在<[questions@packtpub.com](mailto:questions@packtpub.com)>联系我们,我们将尽最大努力解决。
一、准备你的工作环境
在本章中,我们将介绍以下食谱:
- 安装 matplotlib、NumPy 和 SciPy
- 安装 virtualenv 和 virtualenvwrapper
- 在 Mac OS X 上安装 matplotlib
- 在 Windows 上安装 matplotlib
- 安装用于图像处理的 Python 图像库(PIL)
- 安装请求模块
- 在代码中自定义 matplotlib 的参数
- 为每个项目定制 matplotlib 的参数
简介
本章向读者介绍基本的工具以及它们的安装和配置。这是本书其余部分的必要工作和共同基础。如果您从未使用 Python 进行数据和图像处理及可视化,建议不要跳过这一章。即使您跳过它,您也可以随时返回本章,以防您需要安装一些支持工具或验证您需要什么版本来支持当前的解决方案。
安装 matplotlib、NumPy 和 SciPy
本章描述了在 Linux 下安装 matplotlib 和所需依赖项的几种方式。
做好准备
我们假设你已经安装了 Linux(最好是 Debian/Ubuntu 或者红帽/SciLinux)并且上面安装了 Python。通常,Python 已经安装在提到的 Linux 发行版上,如果没有,它可以通过标准方式轻松安装。我们假设您的工作站上安装了 Python 2.7+版本。
注
几乎所有的代码都应该使用 Python 3.3+版本,但是因为大多数操作系统仍然提供 Python 2.7(有些甚至是 Python 2.6),我们决定编写 Python 2.7 版本的代码。差别很小,主要是包的版本和一些代码(在 Python 3.3+中,xrange 应该用 range 代替)。
我们还假设您知道如何使用操作系统包管理器来安装软件包,并知道如何使用终端。
在构建 matplotlib 之前,必须满足构建要求。
matplotlib 需要 NumPy 、 libpng 、和 freetype 作为构建依赖项。为了能够从源代码构建 matplotlib,我们必须安装 NumPy。以下是如何做到这一点:
从http://www.numpy.org/安装 NumPy(至少 1.4+,或者 1.5+如果你想和 Python 3 一起使用的话)。
注
NumPy 将为我们提供数据结构和数学函数,用于大数据集。Python 的默认数据结构,如元组、列表或字典,非常适合插入、删除和连接。NumPy 的数据结构支持“矢量化”操作,使用和执行都非常高效。它们是在考虑大数据的情况下实现的,并且依赖于允许高效执行时间的 C 实现。
SciPy,在 NumPy 之上构建,是事实上标准的科学和数字 Python 工具包,包括大量精选的特殊函数和算法,其中大多数实际上是用 C 和 Fortran 实现的,来自著名的 Netlib 存储库(参见http://www.netlib.org)。
执行以下步骤安装 NumPy:
-
安装 Python-NumPy 包:
$ sudo apt-get install python-numpy -
检查安装版本:
$ python -c 'import numpy; print numpy.__version__' -
Install the required libraries:
-
libpng 1.2 : PNG 文件支持(需要 zlib)
-
freetype 1.4+ :真字体支持
$ sudo apt-get install build-dep python-matplotlib
如果您正在使用红帽或该发行版的变体(Fedora、SciLinux 或 CentOS),您可以使用 yum 来执行相同的安装:
$ su -c 'yum-builddep python-matplotlib' -
怎么做...
可以通过多种方式安装 matplotlib 及其依赖项:从源代码、预编译的二进制文件、从 OS 包管理器,以及使用预打包的 python 发行版和内置的 matplotlib。
最有可能的最简单的方法是使用你的发行包管理器。对于 Ubuntu,应该是:
# in your terminal, type:
$ sudo apt-get install python-numpy python-matplotlib python-scipy
如果你想在流血的边缘,最好的选择是从源头安装。这条路径包括几个步骤:获取源代码、构建需求以及配置、编译和安装。
按照以下步骤,通过从代码主机www.github.com下载最新源代码:
$ cd ~/Downloads/
$ wget https://github.com/downloads/matplotlib/matplotlib/matplotlib-1.2.0.tar.gz
$ tar xzf matplotlib-1.2.0.tar.gz
$ cd matplotlib-1.2.0
$ python setup.py build
$ sudo python setup.py install
型式
下载示例代码
您可以从您在http://www.packtpub.com的账户中下载您购买的所有 Packt 书籍的示例代码文件。如果您在其他地方购买了这本书,您可以访问http://www.packtpub.com/support并注册,以便将文件直接通过电子邮件发送给您。
它是如何工作的...
我们使用标准的 Python 分发实用程序,被称为 Distutils ,从源代码安装 matplotlib。这个过程需要我们预先安装依赖项,正如我们已经在本食谱的准备部分解释的那样。依赖项使用标准的 Linux 打包工具安装。
还有更多...
根据您的数据可视化项目,您可能需要安装更多的可选包。
不不管你在做什么项目,我们都建议安装 IPython——一个支持 PyLab 模式的 Interactive Python shell,在这里你已经有了 matplotlib 和相关的包,比如 NumPy 和 SciPy,已经导入并可以玩了!请参考 IPython 的官方网站,了解如何安装和使用它——尽管它非常简单。
安装 virtualenv 和 virtualenvwrapper
如果你同时在多个项目上工作,或者甚至只是频繁地在它们之间切换,你会发现在系统范围内安装所有的东西并不是最好的选择,并且会在未来在你想要运行软件的不同系统(生产)上带来问题。这不是发现您缺少某个包或者已经安装在生产系统上的包之间存在版本冲突的好时机;因此,virtualenv。
virtualenv 是由 Ian Bicking 启动的一个开源项目,它使开发者能够隔离每个项目的工作环境,以便于不同包版本的维护。
例如,您继承了基于 Django 1.1 和 Python 2.3 的遗留 Django 网站,但同时您正在处理一个必须用 Python 2.6 编写的新项目。这是我通常的情况——根据我正在做的项目,有不止一个必需的 Python 版本(和相关的包)。
virtualenv 使我能够轻松切换到不同的环境,如果我需要切换到另一台机器或将软件部署到生产服务器(或客户端的工作站),也可以轻松复制相同的软件包。
做好准备
要安装 virtualenv,您必须安装 Python 和 pip。Pip 是一个安装和管理 Python 包的工具,它是简单安装的替代品。我们将在本书的大部分内容中使用 pip 进行包管理。Pip 很容易安装,因为 root 会在您的终端中执行以下代码:
# easy_install pip
virtualenv by 本身确实很有用,但是在 virtualenvwrapper 的帮助下,这一切变得很容易做到,也很容易组织很多虚拟环境。参见上的所有功能。
怎么做...
通过执行以下步骤,您可以安装 virtualenv 和 virtualenvwrapper 工具:
-
安装 virtualenv 和 virtualenvwrapper:
$ sudo pip virtualenv $ sudo pip virtualenvwrapper # Create folder to hold all our virtual environments and export the path to it. $ export VIRTENV=~/.virtualenvs $ mkdir -p $VIRTENV # We source (ie. execute) shell script to activate the wrappers $ source /usr/local/bin/virtualenvwrapper.sh # And create our first virtual environment $ mkvirtualenv virt1 -
现在可以在
virt1:(virt1)user1:~$ pip install matplotlib里面安装我们喜欢的包了
-
您可能想要将以下行添加到您的
~/.bashrc文件中:source /usr/loca/bin/virtualenvwrapper.sh
少数有用的和最常用的命令如下:
mkvirtualenv ENV:此创建名为 ENV 的虚拟环境并激活workon ENV:这激活之前创建的 ENVdeactivate:这个让我们脱离了当前的虚拟环境
在 Mac OS X 上安装 matplotlib
在 Mac OS X 上获取 matplotlib 最简单的方法是使用预打包的 python 发行版,如【entnown Python 发行版】 ( EPD )。去 EPD 网站下载安装最新稳定版你的 OS 就行了。
如果您对 EPD 不满意或者因为其他原因无法使用,例如随其分发的版本,有一种手动(阅读:hard)方式安装 Python、matplotlib 及其依赖项。
做好准备
我们将使用 自制软件项目来简化苹果没有在你的操作系统上安装的所有软件的安装,包括 Python 和 matplotlib。在幕后,家酿是一套自动下载和安装的 Ruby 和 Git。遵循这些说明应该可以让安装工作。首先,我们将安装 Homebrew,然后是 Python,接着是 virtualenv 等工具,然后是 matplotlib 的依赖项(NumPy 和 SciPy),最后是 matplotlib。坚持住,我们走。
怎么做...
-
In your Terminal paste and execute the following command:
ruby <(curl -fsSkL raw.github.com/mxcl/homebrew/go)命令完成后,尝试运行 brew update 或 brew doctor 来验证安装是否正常工作。
-
接下来,将自制程序目录添加到您的系统路径中,这样您使用自制程序安装的软件包比其他版本具有更高的优先级。打开
~/.bash_profile(或/Users/[your-user-name]/.bash_profile)并在文件末尾添加以下一行:export PATH=/usr/local/bin:$PATH -
You will need to restart the terminal so it picks a new path. Installing Python is as easy as firing up another one-liner:
brew install python --framework --universal这也将安装 Python 所需的任何先决条件。
-
现在,您需要更新您的路径(添加到同一行):
export PATH=/usr/local/share/python:/usr/local/bin:$PATH -
要验证安装是否有效,请在命令行中键入
python --version,您应该会在响应中看到2.7.3作为版本号。 -
你现在应该已经安装了 pip。如果没有安装,使用
easy_install添加 pip:$ easy_install pip -
现在,安装任何所需的软件包都很容易;例如,virtualenv 和 virtualenvwrapper 都很有用:
pip install virtualenv pip install virtualenvwrapper -
Next step is what we really wanted to do all along—install matplotlib:
pip install numpy brew install gfortran pip install scipy注
山狮用户将需要通过执行以下代码来安装 SciPy (0.11)的开发版本:
pip install -e git+https://github.com/scipy/scipy#egg=scipy-dev -
确认一切正常。调用 Python 并执行以下命令:
import numpy print numpy.__version__ import scipy print scipy.__version__ quit() -
安装 matplot lib:t0]
在 Windows 上安装 matplotlib
在这个食谱中,我们将演示如何安装 Python 并开始使用 matplotlib 安装。我们假设 Python 不是之前安装的。
做好准备
在 Windows 上安装 matplotlib 有两种方式。更简单的方法是通过安装预打包的 Python 环境,如 EPD、Anaconda 和 Python(x,y)。这是建议安装 Python 的方法,尤其是对于初学者。
第二种方法是使用预编译 matplotlib 的二进制文件和所需的依赖项来安装所有内容。这更困难,因为您必须小心您正在安装的 NumPy 和 SciPy 版本,因为不是每个版本都与最新版本的 matplotlib 二进制文件兼容。这样做的好处是,你甚至可以编译你的 matplotlib 的特定版本或任何具有最新特性的库,即使它们不是由作者提供的。
怎么做...
安装免费或商业 Python 科学发行版的建议方法就像遵循项目网站上提供的步骤一样简单。
如果您只是想开始使用 matplotlib,不想被 Python 版本和依赖关系所困扰,那么您可能想考虑使用 entsure Python 发行版(EPD)。EPD 包含使用 matplotlib 所需的预打包库以及所有必需的依赖项(SciPy、NumPy、IPython 等)。
像往常一样,我们下载 Windows Installer ( *.exe),它将安装我们开始使用 matplotlib 所需的所有代码以及本书中的所有食谱。
还有一个免费的科学项目 Python(x,y)(http://code.google.com/p/pythonxy/)针对 Windows 32 位系统,包含所有解析的依赖项,是一个轻松(而且免费!)在 Windows 上安装 matplotlib 的方式。因为 Python(x,y)与 Python 模块安装程序兼容,所以可以很容易地用其他 Python 库扩展。在安装 Python(x,y)之前,系统上不应存在 Python 安装。
让我简短地解释一下,我们将如何使用预编译的 Python、NumPy、SciPy 和 matplotlib 二进制文件来安装 matplotlib。首先,我们使用针对我们平台(x86 或 x86-64)的官方 MSI Installer 下载并安装标准 Python。之后下载 NumPy 和 SciPy 的官方二进制文件,先安装。当您确定 NumPy 和 SciPy 安装正确后,我们将下载 matplotlib 的最新稳定版本二进制文件,并按照官方说明进行安装。
还有更多...
请注意,许多示例没有包含在 Windows 安装程序中。如果您想尝试演示,请下载 matplotlib 源代码并查看示例子目录。
安装 Python 图像库(PIL)进行图像处理
Python 图像库 ( PIL )使用 Python 实现图像处理,具有广泛的文件格式支持,对于图像处理足够强大。
PIL 的一些流行特征是快速访问数据、点操作、过滤、图像大小调整、旋转和任意仿射变换。例如,直方图方法允许我们获得关于图像的统计数据。
PIL 还可以用于其他目的,例如批处理、图像存档、创建缩略图、图像格式之间的转换以及打印图像。
PIL 读大量的格式,而写支持(有意地)限于最常用的交换和表示格式。
怎么做...
最简单也是最推荐的方法是使用平台的包管理器。对于 Debian/Ubuntu 使用以下命令:
$ sudo apt-get build-dep python-imaging
$ sudo pip install http://effbot.org/downloads/Imaging-1.1.7.tar.gz
它是如何工作的...
这样我们就满足了所有使用apt-get系统的构建依赖,同时也安装了 PIL 最新的稳定版本。一些旧版本的 Ubuntu 通常不提供最新版本。
在 RedHat/SciLinux 上:
# yum install python-imaging
# yum install freetype-devel
# pip install PIL
还有更多...
有一本很好的在线手册,特别是针对 PIL 的。可以在http://www.pythonware.com/library/pil/handbook/index.htm阅读,也可以从http://www.pythonware.com/media/data/pil-handbook.pdf下载 PDF 版本。
还有一个 PIL 叉,枕头,其主要目的是解决安装问题。枕头可以在http://pypi.python.org/pypi/Pillow找到,安装方便。
在 Windows 上,也可以使用二进制安装文件安装 PIL。通过从http://www.pythonware.com/products/pil/执行.exe在您的 Python 站点包中安装 PIL。
现在,如果您希望在虚拟环境中使用 PIL,请手动将PIL.pth文件和位于C:\Python27\Lib\site-packages的 PIL 目录复制到您的 virtualenv 站点包目录中。
安装请求模块
我们现在需要的大多数数据都可以通过 HTTP 或类似的协议获得,所以我们需要一些东西来获取它。Python 库请求使这项工作变得简单。
尽管 Python 附带了 urllib2 模块来处理远程资源并支持 HTTP 功能,但它需要大量工作来完成基本任务。
请求模块带来了新的应用编程接口,使网络服务的使用变得无缝和无痛苦。许多 HTTP 1.1 的东西被隐藏起来,只有当你需要它的行为不同于默认的时候才会暴露出来。
怎么做...
使用 pip 是安装请求的最佳方式。使用以下命令进行同样的操作:
$ pip install requests
就这样。如果您不需要每个项目的请求,或者想要支持每个项目的不同请求版本,这也可以在 virtualenv 中完成。
为了让您快速取得成功,这里有一个关于如何使用请求的小例子:
import requests
r = requests.get('http://github.com/timeline.json')
print r.content
它是如何工作的...
我们将GET HTTP 请求发送到位于www.github.com的一个 URI,该请求返回一个 JSON 格式的 GitHub 上的活动时间表(您可以在https://github.com/timeline上看到该时间表的 HTML 版本)。成功读取响应后,r对象包含响应的内容和其他属性(响应代码、cookies 集、头元数据,甚至是我们为了获得该响应而发送的请求)。
在代码中自定义 matplotlib 的参数
我们在本书中最常用的图书馆是 matplotlib 它提供绘图功能。大多数属性的默认值已经在 matplotlib 的配置文件中设置,称为.rc 文件。这个配方描述了如何从我们的应用代码中修改 matplotlib 属性。
做好准备
正如我们已经说过的,matplotlib 配置是从配置文件中读取的。这个文件为 matplotlib 的某些属性提供了一个设置永久默认值的地方,几乎可以设置 matplotlib 中的所有属性。
怎么做...
在代码执行过程中,有两种方法可以更改参数:使用参数字典(rcParams)或调用matplotlib.rc()命令。前者使我们能够将已经存在的字典加载到rcParams中,而后者使我们能够使用关键字参数的元组来调用函数。
如果要恢复动态变化的参数,可以使用matplotlib.rcdefaults()调用恢复标准 matplotlib 设置。
以下两个代码示例说明了前面解释的行为:
matplotlib.rcParams示例:
import matplotlib as mp
mpl.rcParams['lines.linewidth'] = 2
mpl.rcParams['lines.color'] = 'r'
matplotlib.rc()呼叫示例:
import matplotlib as mpl
mpl.rc('lines', linewidth=2, color='r')
这两个例子在语义上是相同的。在第二个示例中,我们定义所有后续的图将具有线宽为 2 点的线。前面代码的最后一条语句定义了这条语句后面的每一行的颜色都是红色,除非我们通过本地设置覆盖它。请参见以下示例:
import matplotlib.pyplot as plt
import numpy as np
t = np.arange(0.0, 1.0, 0.01)
s = np.sin(2 * np.pi * t)
# make line red
plt.rcParams['lines.color'] = 'r'
plt.plot(t,s)
c = np.cos(2 * np.pi * t)
# make line thick
plt.rcParams['lines.linewidth'] = '3
plt.plot(t,c)
plt.show()
它是如何工作的...
首先,我们导入matplotlib.pyplot和 NumPy,允许我们绘制正弦和余弦图。在绘制第一个图形之前,我们使用plt.rcParams['lines.color'] = 'r'将线条颜色明确设置为红色。
接下来,我们转到第二个图(余弦函数),使用plt.rcParams['lines.linewidth'] = '3'显式设置线宽为 3 点。
如果要重置具体设置,应该调用matplotlib.rcdefaults()。
定制每个项目的 matplotlib 参数
这个食谱解释了 matplotlib 使用的各种配置文件在哪里,以及为什么我们想要使用其中一个。此外,我们解释了这些配置文件中的内容。
做好准备
如果您不想在每次使用 matplotlib 时都将其配置为代码的第一步(就像我们在上一个食谱中所做的那样),这个食谱将解释如何为不同的项目配置不同的 matplotlib 默认配置。这样,您的代码就不会被配置数据弄得乱七八糟,而且,您可以轻松地与同事甚至其他项目共享配置模板。
怎么做...
如果您的工作项目总是对 matplotlib 中的某些参数使用相同的设置,您可能不希望每次添加新的图形代码时都设置它们。相反,您需要的是代码之外的一个永久文件,它为 matplotlib 参数设置默认值。
matplotlib 通过其matplotlibrc配置文件支持这一点,该文件包含 matplotlib 的大部分可变属性。
它是如何工作的...
这个文件可以存在三个不同的地方,它的位置定义了它的用途。它们是:
- 当前工作目录:这是你的代码运行的地方。这是为可能包含当前项目代码的当前目录定制 matplotlib 的地方。文件名为
matplotlibrc。 - 每个用户。matplotlib/matplotlibrc :这通常是在用户的
$HOME目录下(在 Windows 下,这是你的Documents and Settings目录)。您可以使用matplotlib.get_configdir()命令找到您的配置目录。检查下一个命令。 - 每个安装配置文件:这通常在您的 python 站点包中。这是一个系统范围的配置,但是每次重新安装 matplotlib 时都会被覆盖;因此,最好使用每个用户配置文件进行更持久的自定义。到目前为止,对我来说,最好的用法是,如果我弄乱了用户的配置文件,或者如果我需要新的配置来为不同的项目进行定制,就将它用作默认模板。
下面的一行代码将打印配置目录的位置,并且可以从 shell 运行。
$ python -c 'import matplotlib as mpl; print mpl.get_configdir()'
配置文件包含以下设置:
axes:处理面和边缘颜色、刻度尺寸和网格显示。backend:设置目标输出:TkAgg和GTKAgg。figure:处理 dpi、边缘颜色、图表尺寸和子图设置。font:查看字体系列、字体大小和样式设置。grid:处理网格颜色和线条设置。legend:指定内部的图例和文本将如何显示。lines:检查线条(颜色、样式、宽度等)和标记设置。patch:面片是填充 2D 空间的图形对象,比如多边形、圆形;设置线宽、颜色、抗锯齿等。savefig:保存的图形有单独的设置。例如,制作白色背景的渲染文件。text:这看起来像文本颜色的,如何解释文本(纯文本和乳胶标记)等等。verbose:它检查 matplotlib 在运行时给出了多少信息:静默、有帮助、调试和令人讨厌的调试。xticks和yticks:设置 x 轴和 y 轴的大刻度和小刻度的颜色、大小、方向和标签大小。
还有更多...
如果您对每个提到的设置(以及一些我们在这里没有提到的设置)的更多细节感兴趣,最好去 matplotlib 项目的网站,那里有最新的 API 文档。如果没有帮助,用户和开发列表总是留下问题的好地方。有关有用的在线资源,请参见本书的背面。
二、了解您的数据
在这一章中,我们将涵盖以下食谱:
- 从 CSV 导入数据
- 从微软 Excel 文件导入数据
- 从固定宽度数据文件导入数据
- 从制表符分隔的文件导入数据
- 从 JSON 资源导入数据
- 将数据导出到 JSON、CSV 和 Excel
- 从数据库导入数据
- 从异常值中清除数据
- 分块读取文件
- 读取流数据源
- 将图像数据导入 NumPy 数组
- 生成受控随机数据集
- 平滑真实数据中的噪声
简介
本章介绍从各种格式导入和导出数据的基本知识。还介绍了清理数据的方法,例如标准化值、添加缺失数据、实时数据检查,以及使用一些类似的技巧来为可视化正确准备数据。
从 CSV 导入数据
在这个食谱中,我们将使用在数据的狂野世界中会遇到的最常见的文件格式,CSV。它代表逗号分隔值,几乎解释了所有的格式。(文件还有一个标题部分,但是那些值也是逗号分隔的。)
Python 有一个叫csv的模块,支持各种方言的 CSV 文件的读写。方言很重要,因为没有标准的 CSV,不同的应用实现 CSV 的方式略有不同。文件的方言几乎总是可以通过第一次查看文件来识别。
做好准备
这个食谱我们需要的是 CSV 文件本身。我们将使用您可以从ch02-data.csv下载的样本 CSV 数据。
我们假设示例数据文件与读取它的代码在同一个文件夹中。
怎么做...
下面的代码示例演示如何从 CSV 文件导入数据。我们将:
-
打开
ch02-data.csv文件进行读取。 -
先看标题。
-
阅读其余的行。
-
如果出现错误,引发异常。
-
阅读完所有内容后,打印标题和其余行。
import csv filename = 'ch02-data.csv' data = [] try: with open(filename) as f: reader = csv.reader(f) header = reader.next() data = [row for row in reader] except csv.Error as e: print "Error reading CSV file at line %s: %s" % (reader.line_num, e) sys.exit(-1) if header: print header print '==================' for datarow in data: print datarow
它是如何工作的...
首先,我们导入csv模块,以便能够访问所需的方法。然后,我们使用with复合语句打开带有数据的文件,并将其绑定到对象f。上下文管理器with语句在我们完成对资源的操作后,释放我们对关闭资源的关心。这是处理资源类文件的一种非常方便的方式,因为它确保在对资源执行代码块后释放资源(例如,关闭文件)。
然后,我们使用返回reader对象的csv.reader()方法,允许我们迭代读取文件的所有行。每一行都只是一个值列表,并在循环中打印。
读取第一行有些不同,因为它是文件的标题,描述了每一列中的数据。这对于 CSV 文件来说并不是强制性的,有些文件没有标题,但它们确实是提供关于数据集的最少元数据的好方法。但有时,您会发现单独的文本甚至 CSV 文件只是用作元数据,描述数据的格式和附加数据。
检查第一行是什么样子的唯一方法是打开文件并进行视觉检查(例如,查看文件的前几行)。这可以在 Linux 上使用 bash 命令(如head)高效地完成,如下所示:
$ head some_file.csv
在数据迭代过程中,我们将第一行保存在header中,而每隔一行添加到data列表中。
如果在读取过程中出现任何错误,csv.reader()将生成一个错误,我们可以捕捉到该错误并打印出有用的消息给用户,以帮助检测错误。
还有更多...
如果你想了解csv模块的背景和推理,可以在http://www.python.org/dev/peps/pep-0305/获得 PEP 定义的文档 CSV 文件 API 。
如果我们有更大的文件要加载,通常最好使用众所周知的库,如 NumPy 的loadtxt(),可以更好地处理大型 CSV 文件。
基本用法很简单,如下面的代码片段所示:
import numpy
data = numpy.looadtxt('ch02-data.csv', dtype='string', delimiter=',')
请注意,我们需要定义一个分隔符来指示 NumPy 适当地分隔我们的数据。函数numpy.loadtxt()比类似的函数numpy.genfromtxt()稍快,但后者可以更好地处理丢失的数据,并且您可以提供函数来表示在处理某些列加载的数据文件时要做什么。
注
目前在 Python 2.7.x 中,csv模块不支持 Unicode,必须将读取的数据显式转换为 UTF-8 或 ASCII 可打印。Python CSV 官方文档提供了如何解决数据编码问题的好例子。
在 Python 3.3 和更高版本中,默认情况下支持 Unicode,不存在此类问题。
从微软 Excel 文件导入数据
虽然微软 Excel 支持一些图表制作,但有时候你需要更灵活更强大的可视化,需要将数据从现有的电子表格导出到 Python 中进一步使用。
从 Excel 文件中导入数据的一种常见方法是将数据从 Excel 导出到 CSV 格式的文件中,并使用前面配方中描述的工具使用 Python 从 CSV 文件中导入数据。如果我们有一个或两个文件(并且安装了微软 Excel 或 OpenOffice.org),这是一个相当简单的过程,但是如果我们正在自动化许多文件的数据管道(作为正在进行的数据处理工作的一部分),我们不能手动将每个 Excel 文件转换成 CSV。所以,我们需要一种读取任何 Excel 文件的方法。
Python 对通过项目www.python-excel.org读写 Excel 文件有不错的支持。这种支持是以读写不同模块的形式提供的,是平台无关的;换句话说,我们不必为了读取 Excel 文件而在 Windows 上运行。
微软 Excel 文件格式随着时间的推移而改变,不同的 Python 库支持不同的版本。XLRD 的最新稳定版本在撰写本文时是 0.90,它支持阅读。xlsx文件。
做好准备
首先我们需要安装所需的模块。对于这个例子,我们将使用模块xlrd。我们将在我们的虚拟环境中使用pip。
$ mkvirtualenv xlrdexample
(xlrdexample)$ pip install xlrd
安装成功后,使用样品文件ch02-xlsxdata.xlsx。
怎么做...
下面的代码示例演示如何从已知的 Excel 文件中读取示例数据集。我们将:
-
打开文件工作簿。
-
按名称查找工作表。
-
使用行数(
nrows)和列数(ncols)读取单元格。 -
出于演示目的,我们只打印读取的数据集。
import xlrd file = 'ch02-xlsxdata.xlsx' wb = xlrd.open_workbook(filename=file) ws = wb.sheet_by_name('Sheet1') dataset = [] for r in xrange(ws.nrows): col = [] for c in range(ws.ncols): col.append(ws.cell(r, c).value) dataset.append(col) from pprint import pprint pprint(dataset)
它是如何工作的...
让我们试着解释一下xlrd使用的简单对象模型。在顶层,我们有一个工作簿(Python 类xlrd.book.Book),它由一个或多个工作表(xlrd.sheet.Sheet)组成,每个工作表都有一个单元格(xlrd.sheet.Cell),我们可以从中读取值。
我们使用open_workbook()从一个文件中加载一个工作簿,该文件返回包含工作簿所有信息的xlrd.book.Book实例,例如工作表。我们使用sheet_by_name()访问工作表;如果我们需要所有的表,我们可以使用 sheets(),它返回一个xlrd.sheet.Sheet实例的列表。xlrd.sheet.Sheet类有许多列和行作为属性,我们可以使用这些属性来推断循环的范围,以便使用方法 cell()访问工作表中的每个特定单元格。有一个xrld.sheet.Cell类,虽然不是我们想直接用的东西。
请注意,日期是作为浮点数存储的,而不是作为单独的数据类型存储的,但是xlrd模块能够检查该值,并尝试推断数据是否实际上是日期。因此,我们可以检查单元格的单元格类型,以获得 Python 日期对象。如果数字格式字符串看起来像日期,模块xlrd将返回xlrd.XL_CELL_DATE作为单元格类型。下面是演示这一点的代码片段:
from datetime import datetime
from xlrd import open_workbook, xldate_as_tuple
…
cell = sheet.cell(1, 0)
print cell
print cell.value
print cell.ctype
if cell.ctype == xlrd.XL_CELL_DATE:
date_value = xldate_as_tuple(cell.value, book.datemode)
print datetime(*date_value)
此字段仍有问题,因此请参考官方文档和邮件列表,以防您需要大量的日期工作。
还有更多...
xlrd的一个简洁的特性是它能够只加载内存中需要的部分文件。有一个on_demand参数可以在调用open_workbook时传递值True,这样工作表只有在被请求时才会被加载。例如:
book = open_workbook('large.xls', on_demand=True)
在这一节中,我们没有提到编写 Excel 文件,部分是因为将有一个单独的方法,部分是因为有一个不同的模块——T0。您将在本章的将数据导出到 JSON、CSV 和 Excel 配方中了解更多信息。
如果您需要前面解释的模块和示例中没有涉及到的具体用法,这里列出了 PyPi 上的其他 Python 模块,这些模块可能会帮助您处理电子表格:http://pypi.python.org/pypi?:action=browse&c = 377。
从固定宽度的数据文件导入数据
事件日志文件和时间序列数据文件是数据可视化的常见来源。有时,我们可以用 CSV 方言读取制表符分隔的数据,但有时它们没有被任何特定的字符分隔。相反,字段的宽度是固定的,我们可以推断出匹配和提取数据的格式。
实现这一点的一种方法是逐行读取文件,然后使用字符串操作函数将字符串拆分成单独的部分。这种方法看起来很简单,如果性能不是问题,应该首先尝试。
如果性能更重要或者要解析的文件很大(几百兆字节),使用 Python 模块struct(http://docs.python.org/library/struct.html)可以加快速度,因为该模块是用 C 实现的,而不是用 Python 实现的。
做好准备
由于模块struct是 Python 标准库的一部分,我们不需要安装任何额外的软件来实现这个配方。
怎么做...
我们将使用具有一百万行固定宽度记录的预生成数据集。以下是示例数据:
…
207152670 3984356804116 9532
427053180 1466959270421 5338
316700885 9726131532544 4920
138359697 3286515244210 7400
476953136 0921567802830 4214
213420370 6459362591178 0546
…
该数据集是使用可在本章ch02-generate_f_data.py的存储库中找到的代码生成的。
现在我们可以读取数据了。我们可以使用下面的代码示例。我们将:
-
定义要读取的数据文件。
-
定义如何读取数据的掩码。
-
使用掩码逐行读取,将每行解包到单独的数据字段中。
-
将每行打印为单独的字段。
import struct import string datafile = 'ch02-fixed-width-1M.data' # this is where we define how to # understand line of data from the file mask='9s14s5s' with open(datafile, 'r') as f: for line in f: fields = struct.Struct(mask).unpack_from(line) print 'fields: ', [field.strip() for field in fields]
它是如何工作的...
我们根据之前在数据文件中看到的内容定义格式掩码。要查看该文件,我们可以使用 Linux shell 命令,如head或more,或类似的命令。
字符串格式用于定义要提取的数据的预期布局。我们使用格式字符来定义我们期望的数据类型。因此,如果将掩码定义为9s15s5s,我们可以将其理解为“一个宽度为 9 个字符的字符串,后跟一个 15 个字符的字符串,再后跟一个 5 个字符的字符串。”
一般来说,c定义字符(C 中的char类型)或长度为 1 的字符串,s定义字符串(C 中的char[]类型),d定义浮点数(C 中的double类型)等等。完整表格可在 Python 官方网站http://docs . Python . org/library/struct . html # format-characters查阅。
然后我们逐行读取文件,并根据指定的格式提取(T0 方法)该行。因为我们的字段前(或后)可能有多余的空格,所以我们使用strip()来去除每个提取的字段。
对于解包,我们使用了使用struct.Struct类的面向对象 ( OO )方法,但是我们也可以使用非对象方法,其中的线是:
fields = struct.unpack_from(mask, line)
唯一不同的是模式的用法。如果我们要使用相同的格式掩码来执行更多的处理,那么面向对象的方法可以避免我们在每次调用中使用这种格式。此外,它使我们能够在将来继承struct.Struct类,为特定需求扩展或提供额外的功能。
从制表符分隔的文件导入数据
平面数据文件的另一种非常常见的格式是制表符分隔的文件。这也可以来自 Excel 导出,但也可以是一些自定义软件的输出,我们必须从这些软件中获取输入。
好的一点是,通常这种格式可以以几乎与 CSV 文件相同的方式读取,因为 Python 模块csv支持所谓的方言,这使我们能够使用相同的原理来读取类似文件格式的变体——其中之一是制表符分隔格式。
做好准备
我们已经能够读取 CSV 文件。如果没有,请先参考从 CSV 配方导入数据。
怎么做...
我们将重新使用中的代码,从 CSV 配方中导入数据,我们只需要改变我们使用的方言。
import csv
filename = 'ch02-data.tab'
data = []
try:
with open(filename) as f:
reader = csv.reader(f, dialect=csv.excel_tab)
header = reader.next()
data = [row for row in reader]
except csv.Error as e:
print "Error reading CSV file at line %s: %s" % (reader.line_num, e)
sys.exit(-1)
if header:
print header
print '==================='
for datarow in data:
print datarow
它是如何工作的...
基于方言的方法与我们已经在中从 CSV 配方导入数据的非常相似,除了我们实例化csv读取器对象的一行,给它参数dialect并指定我们想要的'excel_tab'方言。
还有更多...
如果数据是“脏的”,也就是说,如果某些行不是以新的行字符结束,而是有额外的\t(制表符)标记,基于 CSV 的方法将不起作用。所以我们需要在拆分之前单独清洗特殊线路。样本“脏”制表符分隔的文件可以在ch02-data-dirty.tab中找到。下面的代码示例在读取数据时清除数据:
datafile = 'ch02-data-dirty.tab'
with open(datafile, 'r') as f:
for line in f:
# remove next comment to see line before cleanup
# print 'DIRTY: ', line.split('\t')
# we remove any space in line start or end
line = line.strip()
# now we split the line by tab delimiter
print line.split('\t')
我们还看到还有另一种方法——使用split('\t')函数。
有时使用csv模块方法相对于split()的优势在于,我们可以通过更改方言并使用文件扩展名(.csv和.tab)或一些其他方法(例如,使用csv.Sniffer类)检测方言来重新使用相同的代码进行读取。
从 JSON 资源导入数据
这个食谱将告诉我们如何读取 JSON 数据格式。此外,我们将在本食谱中使用远程资源。它会给食谱增加一点点复杂性,但它会让它变得更有用,因为在现实生活中,我们会遇到比本地更多的远程资源。
JavaScript 对象符号 ( JSON )作为一种独立于平台的格式被广泛用于系统或应用之间的数据交换。
在这种情况下,资源是我们可以读取的任何东西,无论是文件还是网址端点(可以是远程进程/程序的输出,也可以只是远程静态文件)。简而言之,我们不在乎谁生产了一种资源,如何生产;我们只需要它采用已知的格式,比如 JSON。
做好准备
为了开始使用这个配方,我们需要在虚拟环境中安装并导入 requests模块(在PYTHONPATH中)。我们已经在第 1 章、准备您的工作环境中安装了该模块。
我们还需要互联网连接,因为我们将阅读远程资源。
怎么做...
下面的代码示例从 GitHub(http://github.com)站点读取并解析最近的活动时间线。我们将为此执行以下步骤:
-
定义 GitHub URL 来读取 JSON 格式。
-
使用
requests模块从网址获取内容。 -
以 JSON 的形式阅读内容。
-
对于 JSON 对象中的每个条目,读取每个存储库的网址值。
import requests url = 'https://github.com/timeline.json' r = requests.get(url) json_obj = r.json() repos = set() for entry in json_obj: try: repos.add(entry['repository']['url']) except KeyError as e: print "No key %s. Skipping..." % (e) from pprint import pprint pprint(repos)
它是如何工作的...
首先,我们使用requests模块获取远程资源。这非常简单,因为requests模块提供了一个简单的 API 来定义 HTTP 动词,所以我们只需要发出一个get()方法调用。这个方法检索数据并请求元数据,将其包装在Response对象中,以便我们可以检查它。对于这个食谱,我们只对Response.json()方法感兴趣,它会自动读取内容(可在Response.content获得),并将其解析为 JSON,然后将其加载到 JSON 对象中。
现在我们有了 JSON 对象,我们可以处理数据了。为了做到这一点,我们需要了解数据是什么样子的。我们可以通过使用我们最喜欢的网络浏览器或命令行工具(如wget或curl)打开 JSON 资源来实现这种理解。
另一种方法是从 IPython 获取数据,并以交互方式进行检查。我们可以通过从 IPython 运行我们的程序(使用%run program_name.py)来实现这一点。执行之后,我们剩下程序产生的所有变量。使用%who或%whos列出它们。
无论我们使用什么方法,我们都会获得关于 JSON 数据结构的知识,并能够看到该结构中我们感兴趣的部分。
JSON 对象基本上只是一个 Python 字典(或者更复杂的话,一个字典的字典),我们可以使用一个众所周知的基于键的符号来访问它的一部分。我们得到了最近更新的参考entry['repository']['url']库的网址列表。
entry['repository']['url']匹配实际 JSON 文件中的该部分:
…
"repository" : {
...
"url" : "https://github.com/ipython/ipython",
...
},
…
我们现在可以看到嵌套结构如何对应于 Python 代码中的多维键索引。
还有更多...
JSON 格式(由 RFC 4627 指定;参考http://tools.ietf.org/html/rfc4627.html)最近变得非常流行,因为它比 XML 更具可读性,也不那么冗长。因此,就传输数据所需的语法而言,它更简单。它在网络应用领域非常受欢迎,因为它是 JavaScript 的 T2 本地语言,JavaScript 是当今大多数丰富的互联网应用所使用的语言。
Python JSON 模块的功能比我们这里展示的还要多;例如,我们可以专门化基本的JSONEncoder / JSONDecoder类,将我们的 Python 数据转换为 JSON 格式。经典的例子使用这种方法来 JSON-ify 用于复数的 Python 内置类型。
对于简单的定制,我们不必子类化JSONDecoder / JSONEncoder类,因为一些参数可以解决我们的问题。
例如,json.loads()将解析一个浮点数为 Python 类型float,大部分时间都是对的。然而,有时 JSON 文件中的浮点值代表价格值,这最好用十进制表示。我们可以指示json解析器将浮点解析为十进制。例如,我们有这个 JSON 字符串:
jstring = '{"name":"prod1","price":12.50}'
接下来是这两行代码:
from decimal import Decimal
json.loads(jstring, parse_float=Decimal)
前面两行代码将生成以下输出:
{u'name': u'prod1', u'price': Decimal('12.50')}
将数据导出到 JSON、CSV、Excel
而作为数据可视化的生产者,我们大多使用别人的数据;导入和读取数据是主要活动。我们确实需要编写或导出我们生产或处理的数据,无论数据是供我们或他人当前或未来使用。
我们将演示如何使用前面提到的 Python 模块来导入、导出和将数据写入各种格式,如 JSON、CSV 和 XLSX。
出于演示目的,我们使用从固定宽度数据文件导入数据的预生成数据集。
做好准备
对于 Excel 编写部分,我们需要通过执行以下命令来安装xlwt模块(在我们的虚拟环境中):
$ pip install xlwt
怎么做...
我们将展示一个包含我们想要演示的所有格式的代码示例:CSV、JSON 和 XLSX。程序的主要部分接受输入并调用适当的函数来转换数据。我们将遍历代码的不同部分,解释它的用途。
-
导入所需的模块。
import os import sys import argparse try: import cStringIO as StringIO except: import StringIO import struct import json import csv -
然后,定义读写数据的适当函数。
def import_data(import_file): ''' Imports data from import_file. Expects to find fixed width row Sample row: 161322597 0386544351896 0042 ''' mask = '9s14s5s' data = [] with open(import_file, 'r') as f: for line in f: # unpack line to tuple fields = struct.Struct(mask).unpack_from(line) # strip any whitespace for each field # pack everything in a list and add to full dataset data.append(list([f.strip() for f in fields])) return data def write_data(data, export_format): '''Dispatches call to a specific transformer and returns data set. Exception is xlsx where we have to save data in a file. ''' if export_format == 'csv': return write_csv(data) elif export_format == 'json': return write_json(data) elif export_format == 'xlsx': return write_xlsx(data) else: raise Exception("Illegal format defined") -
我们单独为每个数据格式(CSV、JSON 和 XLSX)指定单独实现。
def write_csv(data): '''Transforms data into csv. Returns csv as string. ''' # Using this to simulate file IO, # as csv can only write to files. f = StringIO.StringIO() writer = csv.writer(f) for row in data: writer.writerow(row) # Get the content of the file-like object return f.getvalue() def write_json(data): '''Transforms data into json. Very straightforward. ''' j = json.dumps(data) return j def write_xlsx(data): '''Writes data into xlsx file. ''' from xlwt import Workbook book = Workbook() sheet1 = book.add_sheet("Sheet 1") row = 0 for line in data: col = 0 for datum in line: print datum sheet1.write(row, col, datum) col += 1 row += 1 # We have hard limit here of 65535 rows # that we are able to save in spreadsheet. if row > 65535: print >> sys.stderr, "Hit limit of # of rows in one sheet (65535)." break # XLS is special case where we have to # save the file and just return 0 f = StringIO.StringIO() book.save(f) return f.getvalue() -
最后,我们有主代码入口点,在这里我们从命令行解析类似参数的文件,以导入数据并将其导出为所需的格式。
if __name__ == '__main__': # parse input arguments parser = argparse.ArgumentParser() parser.add_argument("import_file", help="Path to a fixed-width data file.") parser.add_argument("export_format", help="Export format: json, csv, xlsx.") args = parser.parse_args() if args.import_file is None: print >> sys.stderr, "You myst specify path to import from." sys.exit(1) if args.export_format not in ('csv','json','xlsx'): print >> sys.stderr, "You must provide valid export file format." sys.exit(1) # verify given path is accesible file if not os.path.isfile(args.import_file): print >> sys.stderr, "Given path is not a file: %s" % args.import_file sys.exit(1) # read from formated fixed-width file data = import_data(args.import_file) # export data to specified format # to make this Unix-lixe pipe-able # we just print to stdout print write_data(data, args.export_format)
它是如何工作的...
简单来说,我们导入固定宽度数据集(定义在中,从固定宽度数据文件中导入数据),然后将其导出到stdout,这样我们就可以在文件中捕捉到它,或者将其作为另一个程序的输入。
我们从命令行调用程序员,给两个强制参数:输入文件名和导出数据格式(JSON、CSV 和 XLSX)。
如果我们成功解析了这些参数,我们会将输入文件的读取发送到函数import_data(),该函数返回 Python 数据结构(列表列表),我们可以轻松操作该数据结构以获得适当的导出格式。
我们在write_data()函数中路由我们的请求,在那里我们只需将调用转发给适当的函数(例如write_csv())。
对于 CSV,我们获得csv.writer()实例,用于编写我们迭代的每一行数据。
我们只返回给定的字符串,因为我们将把这个输出从我们的程序重定向到另一个程序(或者只是猫在一个文件中)。
这个例子不需要 JSON 导出,因为json模块为我们提供了dump()方法,可以愉快地读取我们的 Python 结构。就像 CSV 一样,我们只需将这个输出返回并转储到stdout。
Excel 导出需要更多的代码,因为我们需要为 Excel 工作簿和保存数据的工作表创建一个更复杂的模型。这项活动之后是类似的迭代方法。我们有两个循环,外部循环遍历源数据集中迭代的每一行,而内部循环遍历给定行中的每个字段。
在完成所有这些之后,我们将Book实例保存到一个类似文件的流中,我们可以返回到stdout并在读取文件和 web 服务使用的文件中使用它。
还有更多...
当然,这只是我们可以导出的一小组可能的数据格式。修改行为相当容易。基本上有两个地方需要改变:导入和导出功能。如果我们想导入一种新的数据源,导入函数需要改变。
如果我们想添加一个新的导出格式,我们需要首先添加函数,这些函数将返回一个格式的数据流。然后,我们需要更新write_data()函数来添加新的elif分支,让它调用我们新的write_*函数。
我们还可以做的一件事是将它做成一个 Python 包,这样我们就可以在更多的项目中重用它。在这种情况下,我们希望使import更加灵活,并可能为import添加一些更多的配置功能。
从数据库导入数据
很多时候,我们在数据分析和可视化方面的工作是在数据管道的消费者端。我们通常使用已经产生的数据,而不是自己产生数据。例如,现代应用在关系数据库(或其他数据库)中保存不同的数据集,我们使用这些数据库并生成漂亮的图形。
这个食谱将向您展示如何使用 Python 中的 SQL 驱动程序来访问数据。
我们将使用一个 SQLite 数据库演示这个方法,因为它需要最少的设置工作,但是界面类似于大多数其他基于 SQL 的数据库引擎(MySQL 和 PostgreSQL)。然而,这些数据库引擎支持的 SQL 方言存在差异。此示例使用简单的 SQL 语言,并且应该可以在大多数常见的 SQL 数据库引擎上重现。
做好准备
为了能够执行这个食谱,我们需要安装 SQLite 库。
$ sudo apt-get install sqlite3
默认情况下,Python 支持 SQLite,所以我们不需要安装任何与 Python 相关的东西。只需在 IPython 中激发以下代码片段来验证一切都在那里:
import sqlite3
sqlite3.version
sqlite3.sqlite_version
我们得到类似如下的输出:
In [1]: import sqlite3
In [2]: sqlite3.version
Out[2]: '2.6.0'
In [3]: sqlite3.sqlite_version
Out[3]: '3.6.22'
这里,sqlite3.version获取 Python sqlite3模块的版本,sqlite_version返回系统 SQLite 库版本。
怎么做...
为了能够读取数据库,我们需要:
- 连接到数据库引擎(或者在 SQLite 的情况下连接到文件)。
- 对选定的表运行查询。
- 读取数据库引擎返回的结果。
我不会尝试在这里教 SQL,因为有很多关于这个特定主题的书。但是为了清楚起见,我们将在这个代码示例中解释 SQL 查询:
SELECT ID, Name, Population FROM City ORDER BY Population DESC LIMIT 1000
ID、Name和Population是表City的列(字段),我们从中选择数据。ORDER BY告诉数据库引擎按照Population列对我们的数据进行排序,DESC表示降序。LIMIT允许我们只获得找到的第一个 1000 条记录。
对于本例,我们将使用world.sql示例表,其中保存了世界上的城市名称和人口。这个表有 5000 多个条目。
我们的桌子是这样的:

首先,我们需要将这个 SQL 文件导入到 SQLite 数据库中。以下是如何做到这一点:
import sqlite3
import sys
if len(sys.argv) < 2:
print "Error: You must supply at least SQL script."
print "Usage: %s table.db ./sql-dump.sql" % (sys.argv[0])
sys.exit(1)
script_path = sys.argv[1]
if len(sys.argv) == 3:
db = sys.argv[2]
else:
# if DB is not defined
# create memory database
db = ":memory:"
try:
con = sqlite3.connect(db)
with con:
cur = con.cursor()
with open(script_path,'rb') as f:
cur.executescript(f.read())
except sqlite3.Error as err:
print "Error occured: %s" % err
这将读取 SQL 文件,并针对打开的 SQLite db文件执行 SQL 语句。如果我们不指定文件名,SQLite 会在内存中创建数据库。然后一行行地执行这些语句。
如果我们遇到任何错误,我们会捕捉异常并将错误消息打印给用户。
将数据导入数据库后,我们就可以查询数据并进行一些处理。下面是从数据库文件中读取数据的代码:
import sqlite3
import sys
if len(sys.argv) != 2:
print "Please specify database file."
sys.exit(1)
db = sys.argv[1]
try:
con = sqlite3.connect(db)
with con:
cur = con.cursor()
query = 'SELECT ID, Name, Population FROM City ORDER BY Population DESC LIMIT 1000'
con.text_factory = str
cur.execute(query)
resultset = cur.fetchall()
# extract column names
col_names = [cn[0] for cn in cur.description]
print "%10s %30s %10s" % tuple(col_names)
print "="*(10+1+30+1+10)
for row in resultset:
print "%10s %30s %10s" % row
except sqlite3.Error as err:
print "[ERROR]:", err
它是如何工作的...
首先,我们验证用户是否提供了数据库文件路径。这只是一个快速检查,我们可以继续进行其余的代码。
然后,我们尝试连接到数据库;如果失败,我们捕捉sqlite3.Error并打印给用户。
如果连接成功,我们使用con.cursor()获得一个光标。游标是一种类似迭代器的结构,它使我们能够遍历从数据库返回的结果集的记录。
我们定义一个通过连接执行的查询,并使用cur.fetchall()获取结果集。如果我们只期望一个结果,我们会只使用fetchone()。
对cur.description的列表理解允许我们获取列名。description是一个只读属性,只返回列名所需的内容,所以我们只从每个列的 7 项元组中获取第一项。
然后,我们使用简单的字符串格式打印带有列名的表格标题。之后,我们迭代resultset并以类似的方式打印每一行。
还有更多...
数据库是当今最常见的数据来源。我们无法在这份简短的食谱中呈现所有内容,但我们可以建议进一步的研究方向。
官方 Python 文档是寻找如何使用数据库的解释的第一个地方。最常见的数据库是开源数据库,如 MySQL、PostgreSQL 和 SQLite,另一端是企业数据库系统,如微软的 SQL、甲骨文和 Sybase。大多数情况下,Python 支持它们,并且接口总是抽象的,所以如果底层数据库发生变化,您不必更改程序,但是可能需要进行一些调整。这取决于您是否使用了特定数据库系统的细节。例如,Oracle 支持一种特定的语言 PL/SQL,这种语言不是标准的 SQL,如果您的数据库从 Oracle 更改为 MS SQL,有些东西将不起作用。同样,SQLite 不支持来自 MySQL 数据类型或数据库引擎类型(MyISAM 和 InnoDB)的细节。这些事情可能很烦人,但是让你的代码依赖于标准的 SQL(http://en.wikipedia.org/wiki/SQL:2011)会让你的代码从一个数据库系统移植到另一个系统。
从异常值中清除数据
这个食谱描述了如何处理来自现实世界的数据集,以及如何在可视化之前清理它们。
我们将展示几种技术,本质上不同,但是有相同的目标,那就是清理数据。
然而,清洁不应该是全自动的。在我们应用任何强大的现代算法来清理数据之前,我们需要理解给定的数据,并且能够理解什么是异常值以及数据点代表什么。这不是一个可以在食谱中定义的东西,因为它依赖于大量的领域,如统计数据、领域知识和良好的眼光(然后是一些运气)。
做好准备
我们将使用我们已经知道的标准 Python 模块,因此不需要额外的安装。
在这个食谱中,我将引入一个新的术语,MAD。统计学中的中值绝对偏差 ( MAD )代表定量数据的单变量(拥有一个变量)样本的可变性的度量。这是统计离差的一种度量。它属于一组稳健的统计数据,因此对异常值更有弹性。
怎么做...
这里有一个例子,展示了如何使用 MAD 来检测数据中的异常值。我们将为此执行以下步骤:
-
生成正态分布的随机数据。
-
加入一些异常值。
-
使用功能
is_outlier()检测异常值。 -
绘制两个数据集(
x和filtered)以查看差异。import numpy as np import matplotlib.pyplot as plt def is_outlier(points, threshold=3.5): """ Returns a boolean array with True if points are outliers and False otherwise. Data points with a modified z-score greater than this # value will be classified as outliers. """ # transform into vector if len(points.shape) == 1: points = points[:,None] # compute median value median = np.median(points, axis=0) # compute diff sums along the axis diff = np.sum((points - median)**2, axis=-1) diff = np.sqrt(diff) # compute MAD med_abs_deviation = np.median(diff) # compute modified Z-score # http://www.itl.nist.gov/div898/handbook/eda/section4/eda43.htm#Iglewicz modified_z_score = 0.6745 * diff / med_abs_deviation # return a mask for each outlier return modified_z_score > threshold # Random data x = np.random.random(100) # histogram buckets buckets = 50 # Add in a few outliers x = np.r_[x, -49, 95, 100, -100] # Keep valid data points # Note here that # "~" is logical NOT on boolean numpy arrays filtered = x[~is_outlier(x)] # plot histograms plt.figure() plt.subplot(211) plt.hist(x, buckets) plt.xlabel('Raw') plt.subplot(212) plt.hist(filtered, buckets) plt.xlabel('Cleaned') plt.show()
请注意,在 NumPy 中,~运算符被重载为逻辑运算符,而不是布尔数组。例如,假设我们在pylab模式下启动 IPython。
$ ipython –pylab
我们得到:
In [1]: ~numpy.array(False)
Out[1]: True
我们应该会看到两个不同的直方图,第一个直方图几乎什么都没有显示——除了一个桶中最大的异常值——第二个直方图显示了多样化的数据桶,因为我们移除了异常值。

识别异常值的另一种方法是目视检查您的数据。为了做到这一点,我们可以创建散点图,在散点图中,我们可以很容易地发现中央群体之外的值。我们也可以做一个方框图,里面会显示的中位数,中位数上下的四分位数,以及距离这个方框较远的点。
该框从数据的下四分位数延伸到上四分位数,中间有一条线。触须从方框中延伸出来,显示数据的范围。飞人点是那些超过胡须末端的点。
这里有一个例子来证明:
from pylab import *
# fake up some data
spread= rand(50) * 100
center = ones(25) * 50
# generate some outliers high and low
flier_high = rand(10) * 100 + 100
flier_low = rand(10) * -100
# merge generated data set
data = concatenate((spread, center, flier_high, flier_low), 0)
subplot(311)
# basic plot
# 'gx' defining the outlier plotting properties
boxplot(data, 0, 'gx')
# compare this with similar scatter plot
subplot(312)
spread_1 = concatenate((spread, flier_high, flier_low), 0)
center_1 = ones(70) * 25
scatter(center_1, spread_1)
xlim([0, 50])
# and with another that is more appropriate for
# scatter plot
subplot(313)
center_2 = rand(70) * 50
scatter(center_2, spread_1)
xlim([0, 50])
show()
我们可以然后看到代表异常值的 x 形标记:

我们还可以看到,在散点图中显示类似数据集的第二个图不是很直观,因为 x 轴的所有值都在 25,我们并没有真正区分内联和外联。
第三个图,我们在 x 轴上生成的值为分布在从 0 到 50 的范围内,让我们更清楚地看到不同的值,我们可以看到哪些值是 y 轴上的异常值。
在下面的代码示例中,我们看到了相同的数据(在本例中是均匀分布的)如何以非常不同的方式显示自己,有时还会欺骗性地传达一些不真实的信息:
# generate uniform data points
x = 1e6*rand(1000)
y = rand(1000)
figure()
# crate first subplot
subplot(211)
# make scatter plot
scatter(x, y)
# limit x axis
xlim(1e-6, 1e6)
# crate second subplot
subplot(212)
# make scatter plot
scatter(x,y)
# but make x axis logarithmic
xscale('log')
# set same x axis limit
xlim(1e-6, 1e6)
show()
这是结果输出:

如果我们有一个缺失值的数据集呢?我们可以使用 NumPy 加载器来补偿丢失的值,或者我们可以编写代码来用我们需要的值替换现有的值,以便进一步使用。
假设我们想要说明美国地理地图上的一些数据集,并且可能在数据集中有不一致的州名值。例如,我们有代表美国俄亥俄州的值OH、Ohio、OHIO、US-OH和OH-USA。在这种情况下,我们必须手动检查数据集,或者将其加载到电子表格处理器(如微软 Excel 或 OpenOffice.org Calc)中。有时候,用 Python 打印所有的行就足够简单了。如果文件是 CSV 或者类 CSV,我们可以用任何文本编辑器打开,直接检查数据。
在我们总结了数据中的内容之后,我们可以编写 Python 代码来对这些相似的值进行分组,并用一个将使进一步处理一致的值来替换它们。通常的方法是使用readlines()读入文件的行,并使用标准的 Python 字符串操作函数来执行操作。
还有更多...
有一些特殊的产品,包括商业和非商业的(比如 OpenRefine—https://github.com/OpenRefine,它们围绕“肮脏”的实时数据集上的转换提供了一些自动化。
尽管如此,还是需要手工操作,这取决于数据有多嘈杂,以及我们对数据的理解有多深刻。
如果你想了解更多关于清理异常值和清理一般数据的信息,请寻找统计模型和抽样理论。
分块读取文件
Python 非常擅长处理读写文件或类似文件的对象。例如,如果你试图加载大文件,比如几百 MB,假设你有一台至少有 2gb RAM 的现代机器,Python 将能够毫无问题地处理它。它不会试图一次加载所有内容,而是聪明地玩,根据需要加载。
因此,即使有合适的文件大小,做一些像下面的代码这样简单的事情也能直接工作:
with open('/tmp/my_big_file', 'r') as bigfile:
for line in bigfile:
# line based operation, like 'print line'
但是,如果我们想跳转到文件中的某个特定位置或进行其他非顺序读取,我们将需要使用手工方法并使用 IO 功能,如seek()、tell()、read()和next(),这些功能为大多数用户提供了足够的灵活性。这些函数中的大多数只是绑定到 C 实现(并且是特定于操作系统的),所以它们速度很快,但是它们的行为会根据我们运行的操作系统而有所不同。
怎么做...
根据我们的目标,处理大文件有时可以分块管理。例如,您可以读取 1000 行,并使用 Python 标准的基于迭代器的方法来处理它们。
import sys
filename = sys.argv[1] # must pass valid file name
with open(filename, 'rb') as hugefile:
chunksize = 1000
readable = ''
# if you want to stop after certain number of blocks
# put condition in the while
while hugefile:
# if you want to start not from 1st byte
# do a hugefile.seek(skipbytes) to skip
# skipbytes of bytes from the file start
start = hugefile.tell()
print "starting at:", start
file_block = '' # holds chunk_size of lines
for _ in xrange(start, start + chunksize):
line = hugefile.next()
file_block = file_block + line
print 'file_block', type(file_block), file_block
readable = readable + file_block
# tell where are we in file
# file IO is usually buffered so tell()
# will not be precise for every read.
stop = hugefile.tell()
print 'readable', type(readable), readable
print 'reading bytes from %s to %s' % (start, stop)
print 'read bytes total:', len(readable)
# if you want to pause read between chucks
# uncomment following line
#raw_input()
我们从 Python 命令行解释器调用这段代码,给出文件名路径作为第一个参数。
$ python ch02-chunk-read.py myhugefile.dat
它是如何工作的...
我们希望能够读取行块进行处理,而不读取内存中的整个文件。
我们打开文件,在内部for循环中读入行。我们移动文件的方式是在文件对象上调用next()。这个函数从文件中读取一行,并将文件指针移动到下一行。在循环执行期间,我们在file_block变量中添加行。为了简化示例代码,我们不做任何处理,只是添加file_block来完成输出变量readable。
我们在执行过程中做一些打印,只是为了说明某些变量的当前状态。
while循环中的最后一个注释行raw_input()可以取消注释,我们可以暂停执行并读取上面打印的行。
还有更多...
当然,这个方法只是读取大(巨大)文件的可能方法之一。其他方法可能包括特定的 Python 或 C 库,但它们都取决于我们打算如何处理数据以及我们希望如何处理数据。
像 MapReduce 范例这样的并行方法最近变得非常流行,因为我们可以以较低的价格获得更多的处理能力和内存。
多处理有时也是一种可行的方法,因为 Python 有很好的库支持来创建和管理带有多个库的线程,例如multiprocessing、threading和thread。
如果处理巨大的文件对于一个项目来说是一个重复的过程,我们建议构建您的数据管道,这样每次您需要在输出端以特定的格式准备好数据时,您就不必去源并手动完成它。
读取流数据源
如果来源的数据是连续的呢?如果我们需要读取连续数据怎么办?这个方法将展示一个简单的解决方案,它将适用于许多常见的现实场景,尽管它不是通用的,如果您在应用中遇到特殊情况,您将需要修改它。
怎么做...
在这个食谱中,我们将向您展示如何读取一个总是变化的文件并打印输出。我们将使用常见的 Python 模块来实现这一点。
import time
import os
import sys
if len(sys.argv) != 2:
print >> sys.stderr, "Please specify filename to read"
filename = sys.argv[1]
if not os.path.isfile(filename):
print >> sys.stderr, "Given file: \"%s\" is not a file" % filename
with open(filename,'r') as f:
# Move to the end of file
filesize = os.stat(filename)[6]
f.seek(filesize)
# endlessly loop
while True:
where = f.tell()
# try reading a line
line = f.readline()
# if empty, go back
if not line:
time.sleep(1)
f.seek(where)
else:
# , at the end prevents print to add newline, as readline()
# already read that.
print line,
它是如何工作的...
代码的核心在while True:循环内部。这个循环从未停止(除非我们通过按下键盘上的 Ctrl + C 来中断它)。我们首先移动到正在读取的文件的末尾,然后尝试读取一行。如果没有行,说明我们使用seek()检查后,文件中没有添加任何内容。所以,我们睡一秒钟,然后再试一次。
如果有一个非空行,我们打印出来,并抑制新的行字符。
还有更多...
我们可能想读最后一行。我们可以通过接近文件的结尾来做到这一点。我们可以通过寻找文件,也就是file.seek(filesize – N * avg_line_len)去那里。这里,avg_line_len应该是该文件中平均行长度的近似值(大约 1,024)。然后,我们可以使用readlines()从那个点读取行,然后只打印列表中的[-N]行。
*这个例子中的想法可以用于各种解决方案。例如,输入必须是类似文件的对象或远程 HTTP 可访问的资源。因此,可以从远程服务中读取输入,并持续解析它和更新实时图表,例如,或者更新中间队列、缓冲区或数据库。
一个特定的模块对于流处理非常有用— io。它是从 2.6 版本开始的 Python 中的,是作为文件模块的替代而构建的,并且是 Python 3.x 中的默认接口
在一些更复杂的数据管道中,我们需要启用某种消息队列,在这里,我们的传入连续数据必须排队一段时间才能被接受。这使得我们作为数据的消费者,能够在超负荷的情况下暂停处理。在公共消息总线上拥有数据使项目中的其他客户端能够使用相同的数据,并且不会干扰我们的软件。
将图像数据导入 NumPy 数组
我们将在演示如何使用 Python 的库如 NumPy 和 SciPy 来进行图像处理。
在科学的计算中,图像通常被视为 n 维数组。它们通常是二维数组;在我们的示例中,它们被表示为 NumPy 数组数据结构。因此,在这些结构上执行的功能和操作被视为矩阵操作。
从这个意义上说,图像并不总是二维的。对于医学或生物科学,图像是更高维度的数据结构,例如 3D(以 z 轴作为深度或时间轴)或 4D(以三个空间维度和一个时间维度作为第四维度)。我们不会在这个食谱中使用这些。
我们可以使用各种技术导入图像;它们都取决于你想用图像做什么。此外,这还取决于你所使用的工具的更大的生态系统,以及你运行你的项目的平台。
在这个食谱中,我们将演示几种在 Python 中使用图像处理的方法,主要与科学处理有关,较少涉及图像处理的艺术方面。
做好准备
在本食谱的一些例子中,我们使用了 SciPy 库,如果您已经安装了 NumPy,那么您已经安装了该库。如果没有,可以通过执行以下命令,使用操作系统的软件包管理器轻松安装:
$ sudo apt-get install python-scipy
对于 Windows 用户,我们建议使用预打包的 Python 环境,例如 EPD,我们在第 1 章、准备您的工作环境中讨论过。
如果您想使用官方源代码发行版安装这些,请确保您已经安装了系统依赖项,例如:
- BLAS 和 LAPACK:
libblas和liblapack - c 和 Fortran 编译器:
gcc和gfortran
怎么做...
无论谁在数字信号处理领域工作过,甚至上过这方面或相关学科的大学课程,都一定遇到过 Lena 的图像,这是事实上的标准图像,用于演示图像处理算法。
SciPy 包含这个已经打包在misc.模块中的图像,所以我们重用那个图像真的很简单。这是如何阅读和展示这张图片:
import scipy.misc
import matplotlib.pyplot as plt
# load already prepared ndarray from scipy
lena = scipy.misc.lena()
# set the default colormap to gray
plt.gray()
plt.imshow(lena)
plt.colorbar()
plt.show()
这应该会打开一个新窗口,图中以灰色调和轴显示莉娜的图像。颜色条显示了图中的数值范围;这里显示 0-黑色到 255-白色。

此外,我们可以用下面的代码来检查这个对象:
print lena.shape
print lena.max()
print lena.dtype
前面代码的输出如下:
(512, 512)
245
dtype('int32')
这里我们看到的图像是:
- 宽 512 点,高 512 点
- 整个数组(即图像)中的最大值是 254
- 每个点都表示为一个小端 32 位长整数
我们也可以使用 Python 图像库 ( PIL )读取图像,我们在第 1 章、准备 您的工作环境中安装了该图像。
import numpy
import Image
import matplotlib.pyplot as plt
bug = Image.open('stinkbug.png')
arr = numpy.array(bug.getdata(), numpy.uint8).reshape(bug.size[1], bug.size[0], 3)
plt.gray()
plt.imshow(arr)
plt.colorbar()
plt.show()
我们应该看到类似莉娜的形象如下:

如果我们已经在利用一个使用 PIL 作为默认图像加载器的现有系统,这将非常有用。
它是如何工作的...
除了加载图像之外,我们真正想做的是使用 Python 来处理图像。我们希望能够加载一个由 RGB 通道组成的真实图像,将其转换为一个通道ndarray,然后使用数组切片来放大图像的部分。下面的代码演示了我们如何使用 NumPy 和 matplotlib 来做到这一点。
import matplotlib.pyplot as plt
import scipy
import numpy
bug = scipy.misc.imread('stinkbug1.png')
# if you want to inspect the shape of the loaded image
# uncomment following line
#print bug.shape
# the original image is RGB having values for all three
# channels separately. We need to convert that to greyscale image
# by picking up just one channel.
# convert to gray
bug = bug[:,:,0]
bug[:,:,0]叫做 阵切片。这个 NumPy 特性允许我们选择多维数组的任何部分。例如,让我们看一个一维数组:
>>> a = array(5, 1, 2, 3, 4)
>>> a[2:3]
array([2])
>>> a[:2]
array([5, 1])
>>> a[3:]
array([3, 4])
对于多维数组,我们用逗号(,)分隔每个维度。例如:
>>> b = array([[1,1,1],[2,2,2],[3,3,3]]) # matrix 3 x 3
>>> b[0,:] # pick first row
array([1,1,1])
>>> b[:,0] # we pick the first column
array([1,2,3])
看看下面的代码:
# show original image
plt.figure()
plt.gray()
plt.subplot(121)
plt.imshow(bug)
# show 'zoomed' region
zbug = bug[100:350,140:350]
这里我们放大整个图像的特定部分。记住图像只是一个表示为 NumPy 数组的多维数组。这里的缩放意味着从这个矩阵中选择一系列的行和列。所以我们从 100 到 250 行和 140 到 350 列中选择一个部分矩阵。请记住,索引从 0 开始,因此坐标 100 处的行是第 101 行。
plt.subplot(122)
plt.imshow(zbug)
plt.show()
这将显示如下:

还有更多...
对于大的图像,我们建议使用numpy.memmap进行图像的记忆映射。这将加快处理图像数据的速度。例如:
import numpy
file_name = 'stinkbug.png'
image = numpy.memmap(file_name, dtype=numpy.uint8, shape = (375, 500))
在这里,我们将一个大文件的一部分加载到内存中,以 NumPy 数组的形式访问它。这是非常有效的,允许我们将文件数据结构作为标准的 NumPy 数组进行操作,而无需将所有内容加载到内存中。参数形状定义了从file_name参数加载的数组的形状,它是一个类似文件的对象。请注意,这是一个与 Python 的mmap参数(http://docs.python.org/2/library/mmap.html)相似的概念,但在一个非常重要的方面有所不同——NumPy 的memmap属性返回一个类似数组的对象,而 Python 的mmap返回一个类似文件的对象。因此,我们使用它们的方式非常不同,但在每个环境中都非常自然。
有一些专门的包只是专注于图像处理,比如 sci kit-image(http://scikit-image.org/);这基本上是一个免费的图像处理算法集合,建立在 NumPy/SciPy 库之上。如果你想做边缘检测,去除图像中的噪声,或者寻找轮廓,scikit 是用来寻找算法的工具。最好的开始方式是查看示例库,找到示例图像和代码(http://scikit-image.org/docs/dev/auto_examples/)。
生成受控随机数据集
在这个食谱中,我们将展示生成随机数序列和单词序列的不同方法。有些示例使用标准 Python 模块,有些使用 NumPy/SciPy 函数。
我们将进入一些统计术语,但我们将解释每个术语,这样您就不必在阅读本食谱时随身携带统计参考书。
我们使用常见的 Python 模块生成人工数据集。通过这样做,我们能够理解分布、方差、抽样和类似的统计术语。更重要的是,我们可以利用这些假数据来了解我们的统计方法是否能够发现我们想要发现的模型。我们可以这样做,因为我们提前知道模型,并通过在已知数据上应用它来验证我们的统计方法。在现实生活中,我们没有这种能力,总是有一定比例的不确定性我们必须假设,让位于错误。
做好准备
为了练习这些例子,我们不需要在系统上安装任何新的东西。掌握一些统计学知识是有用的,尽管不是必需的。
为了更新我们的统计知识,这里有一个小词汇表,我们将在这一章和后面的章节中使用。
- 分布或概率分布 :这将统计实验的结果与该实验发生的概率联系起来。
- 标准偏差:这是一个数值,表示个体与群体相比在上的差异。如果它们的变化更大,标准偏差就会很大,反之亦然——如果所有的单个实验在整个组中大致相同,标准偏差就会很小。
- 方差 :等于标准偏差的平方。
- 人口或统计人口 :这是一个所有潜在可观察案例的集合,例如,世界上所有学生的所有成绩,如果我们有兴趣得到世界学生平均成绩的话。
- 样本:这是人群的一个子集。我们无法获得世界上所有学生的所有成绩,所以我们只能收集数据样本并对其进行建模。
怎么做...
我们可以使用 Python 的模块random生成一个简单的随机样本。这里有一个例子:
import pylab
import random
SAMPLE_SIZE = 100
# seed random generator
# if no argument provided
# uses system current time
random.seed()
# store generated random values here
real_rand_vars = []
# pick some random values
real_rand_vars = [random.random() for val in xrange(SIZE)]
# create histogram from data in 10 buckets
pylab.hist(real_rand_vars, 10)
# define x and y labels
pylab.xlabel("Number range")
pylab.ylabel("Count")
# show figure
pylab.show()
这是一个均匀分布的样本。当我们运行这个例子时,我们应该会看到类似于下面的图:

尝试将SAMPLE_SIZE设置为一个大数字(比如10000),看看直方图的表现。
如果我们希望值的范围不是从 0 到 1,而是从 1 到 6(例如,模拟单骰子投掷),我们可以使用random.randint(min, max);这里,min和max分别是包含下限和包含上限。如果你想生成的是浮点数而不是整数,有一个random.uniform(min, max)函数来提供。
以类似的方式,并使用相同的工具,我们可以生成虚构的价格增长数据的时间序列图,带有一些随机噪声。
import pylab
import random
# days to generate data for
duration = 100
# mean value
mean_inc = 0.2
# standard deviation
std_dev_inc = 1.2
# time series
x = range(duration)
y = []
price_today = 0
for i in x:
next_delta = random.normalvariate(mean_inc, std_dev_inc)
price_today += next_delta
y.append(price_today)
pylab.plot(x,y)
pylab.xlabel("Time")
pylab.xlabel("Time")
pylab.ylabel("Value")
pylab.show()
这个代码定义了一系列 100 个数据点(虚构的日子)。对于接下来的每一天,我们从从mean_inc到std_dev_inc的正态分布(random.normalvariate())中选择一个随机值,并将该值添加到昨天的价格值(price_today)中。
如果我们想要更多的控制,我们可以使用不同的分布。下面的代码演示并可视化了不同的分布。我们将对单独的代码段进行注释。我们首先导入所需的模块,并定义一些直方图桶。我们还创建了一个保存直方图的图形。
# coding: utf-8
import random
import matplotlib
import matplotlib.pyplot as plt
SAMPLE_SIZE = 1000
# histogram buckets
buckets = 100
plt.figure()
# we need to update font size just for this example
matplotlib.rcParams.update({'font.size': 7})
为了布局所有需要的图,我们为所有直方图定义了一个 6 乘 2 的子图网格。第一个图是正态分布随机变量。
plt.subplot(621)
plt.xlabel("random.random")
# Return the next random floating point number in the range [0.0, 1.0).
res = [random.random() for _ in xrange(1, SAMPLE_SIZE)]
plt.hi
对于第二个图,我们绘制了一个均匀分布的随机变量。
plt.subplot(622)
plt.xlabel("random.uniform")
# Return a random floating point number N such that a <= N <= b for a <= b and b <= N <= a for b < a.
# The end-point value b may or may not be included in the range depending on floating-point rounding in the equation a + (b-a) * random().
a = 1
b = SAMPLE_SIZE
res = [random.uniform(a, b) for _ in xrange(1, SAMPLE_SIZE)]
plt.hist(res, buckets)
第三个图是三角形分布。
plt.subplot(623)
plt.xlabel("random.triangular")
# Return a random floating point number N such that low <= N <= high and with the specified # mode between those bounds. The low and high bounds default to zero and one. The mode
# argument defaults to the midpoint between the bounds, giving a symmetric distribution.
low = 1
high = SAMPLE_SIZE
res = [random.triangular(low, high) for _ in xrange(1, SAMPLE_SIZE)]
plt.hist(res, buckets)
第四个图是贝塔分布。参数的条件是α和β应该大于零。返回值的范围在 0 和 1 之间。
plt.subplot(624)
plt.xlabel("random.betavariate")
alpha = 1
beta = 10
res = [random.betavariate(alpha, beta) for _ in xrange(1, SAMPLE_SIZE)]
plt.hist(res, buckets)
第五个图显示了指数分布。lambd是 1.0 除以所需平均值。它应该是非零的。(该参数将被称为 lambda,但在 Python 中这是一个保留字。)如果lambd为正,返回值范围从 0 到正无穷大,如果lambd为负,返回值范围从负无穷大到 0。
plt.subplot(625)
plt.xlabel("random.expovariate")
lambd = 1.0 / ((SAMPLE_SIZE + 1) / 2.)
res = [random.expovariate(lambd) for _ in xrange(1, SAMPLE_SIZE)]
plt.hist(res, buckets)
我们的下一个图是伽马分布,其中参数的条件是α和β大于 0。概率分布函数为:

伽马分布的代码如下:
plt.subplot(626)
plt.xlabel("random.gammavariate")
alpha = 1
beta = 10
res = [random.gammavariate(alpha, beta) for _ in xrange(1, SAMPLE_SIZE)]
plt.hist(res, buckets)
对数正态分布是我们的下一个图。如果你取这个分布的自然对数,你会得到一个均值mu和标准差sigma的正态分布。mu可以有任意值,sigma必须大于零。
plt.subplot(627)
plt.xlabel("random.lognormvariate")
mu = 1
sigma = 0.5
res = [random.lognormvariate(mu, sigma) for _ in xrange(1, SAMPLE_SIZE)]
plt.hist(res, buckets)
下一个图是正态分布,其中mu是平均值,sigma是标准差。
plt.subplot(628)
plt.xlabel("random.normalvariate")
mu = 1
sigma = 0.5
res = [random.normalvariate(mu, sigma) for _ in xrange(1, SAMPLE_SIZE)]
plt.hist(res, buckets)
最后一个图是帕累托分布。alpha是形状参数。
plt.subplot(629)
plt.xlabel("random.paretovariate")
alpha = 1
res = [random.paretovariate(alpha) for _ in xrange(1, SAMPLE_SIZE)]
plt.hist(res, buckets)
plt.tight_layout()
plt.show()
这是一个很大的代码示例,但是基本上,我们根据各种分布选择了 1000 个随机数。这些是不同统计分支(经济学、社会学、生物科学等)中使用的常见分布。
我们应该看到基于所使用的分布算法的直方图的差异。花点时间了解以下九个绘图:

使用seed()初始化伪随机发生器,因此random()产生相同的预期随机值。这有时很有用,比预先生成随机数据并将其保存到文件中要好。后一种技术并不总是可行的,因为它需要在文件系统上保存(可能是大量的)数据。
如果您想防止您随机生成的序列的任何重复性,我们建议使用random.SystemRandom,它下面使用os.urandom;os.urandom提供更多熵源。如果使用这个随机生成器界面,seed()和setstate() 没有效果;因此,这些样品是不可复制的。
如果我们想有一些随机词,最简单的方法(在 Linux 上)可能是使用/usr/share/dicts/words。我们可以在下面的例子中看到这是如何做到的:
import random
with open('/usr/share/dict/words', 'rt') as f:
words = f.readlines()
words = [w.rstrip() for w in words]
for w in random.sample(words, 5):
print w
这个解决方案只适用于 Unix,不会在 Windows 上运行(不过会在 Mac OS 上运行)。对于 Windows,您可以使用从各种免费来源构建的文件(古登堡计划、维基词典、英国国家语料库或彼得·诺维格博士的http://norvig.com/big.txt)。
平滑真实数据中的噪声
在这个食谱中,我们引入了一些高级算法来帮助清理来自真实世界来源的数据。这些算法在信号处理领域是众所周知的,我们不会深入数学,而只是举例说明它们是如何以及为什么工作的,以及它们可以用于什么目的。
做好准备
来自不同现实生活传感器的数据通常不平滑和干净,并且包含一些我们通常不想在图表和绘图上显示的噪声。我们希望图表清晰明了,能够显示信息,让观众花最少的力气去解读。
我们不需要安装任何新软件,因为我们将使用一些已经熟悉的 Python 包:NumPy、SciPy 和 matplotlib。
怎么做...
基本算法基于使用滚动窗口(例如卷积)。该窗口滚动数据,用于计算该窗口的平均值。
对于我们的离散数据,我们使用 NumPy 的 convolve函数;它返回两个一维序列的离散线性卷积。我们还使用了 NumPy 的linspace功能,它为指定的时间间隔生成一系列均匀间隔的数字。
函数ones定义了一个数组或矩阵(例如多维数组),其中每个元素都有值1。这有助于生成用于平均的窗口。
它是如何工作的...
平滑我们正在处理的数据中的噪声的一个简单而天真的技术是对某个窗口(样本)求平均值,并绘制给定窗口的平均值,而不是所有数据点的平均值。这是更高级算法的基础。
from pylab import *
from numpy import *
def moving_average(interval, window_size):
'''Compute convoluted window for given size
'''
window = ones(int(window_size)) / float(window_size)
return convolve(interval, window, 'same')
t = linspace(-4, 4, 100)
y = sin(t) + randn(len(t))*0.1
plot(t, y, "k.")
# compute moving average
y_av = moving_average(y, 10)
plot(t, y_av,"r")
#xlim(0,1000)
xlabel("Time")
ylabel("Value")
grid(True)
show()
这里,我们展示了与原始数据点(绘制为点)相比,平滑线的外观:

按照这个想法,我们可以跳到一个更高级的例子,并使用现有的 SciPy 库使这个窗口平滑工作得更好。
我们将要演示的方法是基于缩放窗口与信号(即数据点)的卷积(函数求和)。这个信号是以一种巧妙的方式准备的,在两端添加相同信号的副本,但反射它,因此我们将边界效应降至最低。这段代码基于 SciPy Cookbook 的例子,可以在这里找到:http://www.scipy.org/Cookbook/SignalSmooth。
import numpy
from numpy import *
from pylab import *
# possible window type
WINDOWS = ['flat', 'hanning', 'hamming', 'bartlett', 'blackman']
# if you want to see just two window type, comment previous line,
# and uncomment the following one
# WINDOWS = ['flat', 'hanning']
def smooth(x, window_len=11, window='hanning'):
"""
Smooth the data using a window with requested size.
Returns smoothed signal.
x -- input signal
window_len -- lenght of smoothing window
window -- type of window: 'flat', 'hanning', 'hamming',
'bartlett', 'blackman'
flat window will produce a moving average smoothing.
"""
if x.ndim != 1:
raise ValueError, "smooth only accepts 1 dimension arrays."
if x.size < window_len:
raise ValueError, "Input vector needs to be bigger than window size."
if window_len < 3:
return x
if not window in WINDOWS:
raise ValueError("Window is one of 'flat', 'hanning', 'hamming', "
"'bartlett', 'blackman'")
# adding reflected windows in front and at the end
s=numpy.r_[x[window_len-1:0:-1], x, x[-1:-window_len:-1]]
# pick windows type and do averaging
if window == 'flat': #moving average
w = numpy.ones(window_len, 'd')
else:
# call appropriate function in numpy
w = eval('numpy.' + window + '(window_len)')
# NOTE: length(output) != length(input), to correct this:
# return y[(window_len/2-1):-(window_len/2)] instead of just y.
y = numpy.convolve(w/w.sum(), s, mode='valid')
return y
# Get some evenly spaced numbers over a specified interval.
t = linspace(-4, 4, 100)
# Make some noisy sinusoidal
x = sin(t)
xn = x + randn(len(t))*0.1
# Smooth it
y = smooth(x)
# windows
ws = 31
subplot(211)
plot(ones(ws))
# draw on the same axes
hold(True)
# plot for every windows
for w in WINDOWS[1:]:
eval('plot('+w+'(ws) )')
# configure axis properties
axis([0, 30, 0, 1.1])
# add legend for every window
legend(WINDOWS)
title("Smoothing windows")
# add second plot
subplot(212)
# draw original signal
plot(x)
# and signal with added noise
plot(xn)
# smooth signal with noise for every possible windowing algorithm
for w in WINDOWS:
plot(smooth(xn, 10, w))
# add legend for every graph
l=['original signal', 'signal with noise']
l.extend(WINDOWS)
legend(l)
title("Smoothed signal")
show()
我们应该看下面两个图,看看开窗算法如何影响噪声信号。上图表示可能的窗口算法,下图显示从原始信号到加噪信号的所有可能结果,甚至每个窗口算法的平滑信号。尝试对可能的窗口类型进行评论,只留下一两个以获得更好的理解。

还有更多...
另一个非常流行的信号平滑算法是中值滤波。该滤波器的主要思想是逐个条目地遍历信号条目,用相邻条目的中值替换每个条目。这个想法使得这个过滤器既快速又适用于等一维数据集,也适用于二维数据集(如图像)。
在下面的例子中,我们使用了 SciPy 信号工具箱中的实现:
import numpy as np
import pylab as p
import scipy.signal as signal
# get some linear data
x = np.linspace (0, 1, 101)
# add some noisy signal
x[3::10] = 1.5
p.plot(x)
p.plot(signal.medfilt(x,3))
p.plot(signal.medfilt(x,5))
p.legend(['original signal', 'length 3','length 5'])
p.show ()
我们在下图中看到,窗口越大,我们的信号与原始信号相比失真越大,但看起来越平滑:

还有很多方法可以平滑从外部来源接收的数据(信号)。这在很大程度上取决于你工作的区域和信号的性质。许多算法是专门针对特定信号的,可能并没有针对您遇到的每种情况的通用解决方案。
然而,有一个重要的问题:“什么时候你不应该平滑一个信号?”不应该平滑信号的一种常见情况是在统计过程之前,例如最小二乘曲线拟合,因为所有的平滑算法都至少有轻微的损耗,并且会改变信号形状。此外,平滑的噪声可能被误认为是实际信号。*
三、绘制您的第一个绘图并自定义它们
在这一章中,我们将更详细地介绍 matplotlib 的大部分可能性。我们将涵盖:
- 定义绘图类型-条形图、折线图和堆叠图
- 绘制简单的正弦和余弦曲线
- 定义轴长度和限制
- 定义绘图线样式、特性和格式字符串
- 设置记号、标签和网格
- 添加图例和标注
- 将脊椎移向中心
- 制作直方图
- 用误差线制作条形图
- 让饼图有价值
- 用填充区域绘图
- 用彩色标记绘制散点图
简介
虽然我们已经使用 matplotlib 绘制了我们的第一个图,但是我们没有详细讨论它们是如何工作的,如何设置它们,或者使用 matplotlib 有什么可能性。我们探索和练习最常见的数据可视化类型:折线图、条形图、直方图、饼图及其变体。
matplotlib 是一个强大的工具箱,它几乎满足了我们对 2D 的所有需求,也满足了一些 3D 绘图需求。作者打算让你学习 matplotlib 的最好方法是通过例子。当我们需要画一个绘图时,我们会寻找一个类似的例子,并试图改变它以适应我们的需要。这样,我们也准备给大家呈现一些有用的例子,相信这个例子会帮助大家找到一个最符合自己需要的绘图。
定义绘图类型–条形图、折线图和堆叠图
在这个食谱中,我们将展示不同的基本绘图以及它们的用途。这里描述的大部分图都是日常使用的,其中一些图为理解更多数据可视化高级概念提供了基础。
做好准备
我们从matplotlib.pyplot库中的一些常见图表开始,仅包含样本数据集,以开始基本的图表制作,并为以下食谱奠定基础。
怎么做...
我们从在 IPython 中创建一个简单的图开始。IPython 非常棒,因为它允许我们交互式地更改绘图,并立即看到结果。
-
通过在命令提示符下键入以下命令启动 IPython:
$ ipython --pylab -
然后输入 matplotlib
plot代码:In [1]: plot([1,2,3,2,3,2,2,1]) Out[1]: [<matplotlib.lines.Line2D at 0x412fb50>]
该图应在新窗口中打开,显示图的默认外观和一些支持信息:

matplotlib 中的基本绘图包含以下元素:
- x 轴和 y 轴:这是水平轴和垂直轴。
- x 和 y 刻度:这些是表示轴段的小刻度。可以有大票和小票。
- x 和 y 刻度标签:代表特定轴上的值。
- 绘图区:这是实际绘图的地方。
您会注意到我们提供给plot()的值是 y 轴值。plot()提供 x 轴的默认值;它们是从 0 到 7 的线性值(y 值的数量减 1)。
现在,尝试为 x 轴添加值;作为plot()函数的第一个参数,同样在同一个 IPython 会话中,键入:
In [2]: plot([4,3,2,1],[1,2,3,4])
Out[2]: [<matplotlib.lines.Line2D at 0x31444d0>]
型式
注意 IPython 如何计数输入和输出线(In [2]和Out [2])。这将帮助我们记住我们在当前会话中的位置,并启用更高级的功能,例如将会话的一部分保存在 Python 文件中。在数据分析过程中,使用 IPython 进行原型制作是获得令人满意的解决方案,然后将特定会话保存到文件中的最快方法,如果需要重现相同的绘图,可以稍后执行。
这将更新绘图如下:

我们在这里看到如何 matplotlib 扩展 y 轴以适应新的值范围,并自动改变第二条绘图线的颜色以使我们能够区分新的绘图。
除非我们关闭 hold属性(通过调用hold(False)),否则所有后续的图都将绘制在相同的轴上。这是 IPython 中pylab模式的默认行为,而在常规 Python 脚本中,hold默认为关闭。
让我们打包一些更常见的图,并在同一数据集上进行比较。您可以在 IPython 中键入它,或者从单独的 Python 脚本中运行它:
from matplotlib.pyplot import *
# some simple data
x = [1,2,3,4]
y = [5,4,3,2]
# create new figure
figure()
# divide subplots into 2 x 3 grid
# and select #1
subplot(231)
plot(x, y)
# select #2
subplot(232)
bar(x, y)
# horizontal bar-charts
subplot(233)
barh(x, y)
# create stacked bar charts
subplot(234)
bar(x, y)
# we need more data for stacked bar charts
y1 = [7,8,5,3]
bar(x, y1, bottom=y, color = 'r')
# box plot
subplot(235)
boxplot(x)
# scatter plot
subplot(236)
scatter(x,y)
show()
这就是应该如何将从变成的图形:

它是如何工作的...
借助figure()我们创建一个新的图形。如果我们提供一个字符串参数,比如sample charts,它将是一个窗口的后端标题。如果我们用相同的参数(也可以是数字)调用figure()函数,我们将激活相应的图形,并对该图形执行以下所有绘图。
接下来,我们使用subplot(231)调用将图分成 2 乘 3 的网格。我们可以用subplot(3, 2, 1)来称呼这个,其中第一个参数是行数,第二个是列数,第三个代表图号。
我们继续使用简单的调用创建垂直条形图(bar())和水平条形图(barh())来创建一个常见的图表类型。对于堆叠条形图,我们需要将两个条形图调用绑定在一起。我们通过使用参数bottom = y将第二个条形图与前一个连接起来。
使用boxplot()调用创建方框图,其中方框从下四分位数延伸到上四分位数,线条位于中间值。我们将很快回到方块图。
我们最后创建一个散点图,让您了解基于点的数据集。当我们在一个数据集中有数千个数据点时,这可能更适合使用,但是在这里,我们想说明同一数据集的不同表示。
还有更多...
我们现在可以回到方框图,因为我们需要解释最重要的显示选项。
对于初学者,我们可以添加从框延伸的胡须来表示数据集的整个范围。方框和须图主要用于表示一个或多个数据集中数据的变化;它们很容易比较和易读。在同一个图中,它们可以表示五个统计数据:
- 最小值:在数据集中
- 第二个四分位数:低于这个四分位数的是给定数据集的 25%
- 中值:这是数据集的中值
- 第三个四分位数:高于这个四分位数的是给定数据集的上 25%
- 最大值:这是给定数据集的最大值
为了说明这种行为,我们将演示在方框图和直方图中绘制相同的数据集,如以下代码所示:
from pylab import *
dataset = [113, 115, 119, 121, 124,
124, 125, 126, 126, 126,
127, 127, 128, 129, 130,
130, 131, 132, 133, 136]
subplot(121)
boxplot(dataset, vert=False)
subplot(122)
hist(dataset)
show()
这将为我们提供以下图表:

在之前的比较中,我们可以观察到两个不同图表中相同数据集的表示中的差异。左侧的指向上述五个统计值,而右侧的(直方图)显示给定范围内数据集的分组。
绘制简单的正弦和余弦图
本食谱将复习绘制数学函数的基础知识以及与数学图形相关的几件事,例如在标签和曲线上写希腊符号。
做好准备
我们最常用的图形是线图命令,它在图形图上绘制给定的(x,y)坐标。
怎么做...
我们从开始,计算同一线性区间内的正弦和余弦函数——从π到π,中间有 256 个点——并在同一曲线上绘制正弦(x)和余弦(x)的值:
import matplotlib.pyplot as pl
import numpy as np
x = np.linspace(-np.pi, np.pi, 256, endpoint=True)
y = np.cos(x)
y1 = np.sin(x)
pl.plot(x,y)
pl.plot(x, y1)
pl.show()
这将为我们提供以下图表:

按照这个简单的图,我们可以定制更多,以提供更多信息,并更精确地了解轴和边界:
from pylab import *
import numpy as np
# generate uniformly distributed
# 256 points from -pi to pi, inclusive
x = np.linspace(-np.pi, np.pi, 256, endpoint=True)
# these are vectorised versions
# of math.cos, and math.sin in built-in Python maths
# compute cos for every x
y = np.cos(x)
# compute sin for every x
y1 = np.sin(x)
# plot cos
plot(x, y)
# plot sin
plot(x, y1)
# define plot title
title("Functions $\sin$ and $\cos$")
# set x limit
xlim(-3.0, 3.0)
# set y limit
ylim(-1.0, 1.0)
# format ticks at specific values
xticks([-np.pi, -np.pi/2, 0, np.pi/2, np.pi],
[r'$-\pi$', r'$-\pi/2$', r'$0$', r'$+\pi/2$', r'$+\pi$'])
yticks([-1, 0, +1],
[r'$-1$', r'$0$', r'$+1$'])
show()
这应该会给我们一个稍微好一点的图表:

我们看到我们使用了这样的表达方式$\sin$,或者$-\pi$来写数字中的希腊字母。这是 LaTex 语法,我们将在后面的章节中进一步探讨。在这里,我们只是说明了让你的数学图表对某些受众来说更易读是多么容易。
定义轴长度和极限
这个配方将围绕极限和长度展示各种有用的轴属性,我们可以在 matplotlib 中进行配置。
做好准备
对于这个食谱,我们想激发 IPython:
$ ipython --pylab
怎么做...
开始尝试轴的各种属性。只需调用一个空的axis()函数就会返回轴的默认值:
In [1]: axis()
Out[1]: (0.0, 1.0, 0.0, 1.0)
请注意,如果您处于交互模式并使用窗口后端,将显示一个带有空轴的图形。
这里的数值分别代表xmin、xmax、ymin和ymax。同样,我们可以设置 x 轴和 y 轴的值:
In [2]: l = [-1, 1, -10, 10]
In [3]: axis(l)
Out[3]: [-1, 1, -10, 10]
同样,如果您处于交互模式,这将更新相同的数字。此外,我们还可以使用关键字参数(**kwargs)单独更新任何值,只需将xmax设置为某个值。
它是如何工作的...
如果我们不使用axis()或其他设置,matplotlib 将自动使用允许我们在一个图中看到所有数据点的最小值。如果我们将axis()限制设置为小于数据集中的最大值,matplotlib 将按照指示执行,并且我们不会看到图上的所有点。这可能是一个混乱甚至错误的来源,我们认为我们看到了我们画的一切。避免这种情况的一种方法是调用autoscale() ( matplotlib.pyploy.autoscale()),它将计算轴的最佳大小以适合要显示的数据。
如果要给同一个图形添加新的轴,可以使用matplotlib.pyploy.axes()。我们通常想在这个默认调用中添加一些属性;例如,rect—可以用归一化单位(0,1)表示属性left、bottom、width和height—也可能是axisbg,指定坐标轴的背景色。
我们还可以为添加的轴设置其他属性,如sharex / sharey,它接受其他轴实例的值,并与其他轴共享当前轴(x/y)。或者定义我们是否要使用极轴的参数polar。
添加新的轴可能很有用;例如,如果需要紧密耦合同一数据的不同视图来说明其属性,可以将多个图表组合在一个图上。
如果我们想在当前图形中只添加一行,可以使用matplotlib.pyploy.axhline()或matplotlib.pyplot.axvline()。功能axhilne()和axvline()将分别为给定的 x 和 y 数据值绘制横轴和纵轴。它们共享相似的参数,最重要的是 y 位置、xmin,以及用于axhline()和 x 位置、ymin的xmax,以及用于axvline()的ymax。
让我们看一下它的外观,在同一个 IPython 会话中继续:
In [3]: axhline()
Out[3]: <matplotlib.lines.Line2D at 0x414ecd0>
In [4]: axvline()
Out[4]: <matplotlib.lines.Line2D at 0x4152490>
In [5]: axhline(4)
Out[5]: <matplotlib.lines.Line2D at 0x4152850>
我们应该有一个像下面这样的图:

这里我们看到只是在没有参数的情况下调用这些函数让它们取默认值,为y=0 ( axhline())画一条水平线,为x=0 ( axvline())画一条垂直线。
与此类似的是两个相关的函数,允许我们在轴上添加水平跨度(矩形)。这些是matplotlib.pyplot.axhspan()和matplotlib.pyplot.axspan()。功能axhspan()有ymin和ymax作为定义水平跨度有多宽的必需参数。与此类似,axvspan()有xmin和xmax来定义垂直跨度的宽度。
还有更多...
默认情况下,图形中的网格处于关闭状态,但可以轻松打开和自定义。对matplotlib.pyplot.grid()的默认调用将切换网格的可见性。其他控制参数如下:
which:定义要绘制的网格刻度类型(可以是major、minor或both)axis:定义画哪组网格线(可以是both、x或y)
轴通常通过matplotlib.pyplot.axis()控制。在内部,轴由几个 Python 类表示,父类是matplotlib.axes.Axes,它包含了大多数操作轴的方法。单个轴由matplotlib.axis.Axis类表示,其中 x 轴使用matplotlib.axis.XAxis,y 轴使用matplotlib.axis.YAxis类。
我们不需要使用这些来执行我们的配方,但重要的是要知道如果我们对更高级的轴控制感兴趣,以及当我们达到通过matplotlib.pyplot命名空间可用的极限时,应该从哪里寻找。
定义绘图线样式、特性和格式字符串
这个食谱展示了我们如何改变各种线条属性,例如样式、颜色或宽度。根据所呈现的信息,适当设置线条,并使其足够清晰以适合目标观众(如果观众是较年轻的人群,我们可能希望用更鲜艳的颜色来瞄准他们;如果他们年龄较大,我们可能希望使用更多对比色)可以在几乎不引人注意和给观众留下很大影响之间产生差异。
做好准备
尽管我们强调了从美学角度调整演示文稿的重要性,但我们首先必须学会如何做。
如果你对配色没有特别的眼光,有免费的商业在线工具可以为你生成颜色集。其中最著名的是彩色啤酒 2,可以在 http://colorbrewer2.org/ T2 找到。
一些严肃的研究已经在数据可视化中使用了颜色,但是解释这个理论超出了本书的范围。如果你每天都在使用更高级的可视化工具,那么这个主题的材料是必读的。
怎么做...
让我们学习如何更改线条属性。我们可以用不同的方法改变我们的绘图。
首先也是最常见的是通过将关键字参数传递给函数来定义行,如plot():
plot(x, y, linewidth=1.5)
因为对plot()的调用会返回行实例(matplotlib.lines.Line2D),所以我们可以在该实例上使用一组 setter 方法来设置各种属性:
line, = plot(x, y)
line.set_linewidth(1.5)
使用 MATLAB 的人会觉得有必要使用第三种方式配置线属性——使用 setp()功能:
lines = plot(x, y)
setp(lines, 'linewidth', 1.5)
另一种使用setp()的方式是:
setp(lines, linewidth=1.5)
无论你喜欢用什么方式配置线路,选择一种方法在整个项目中保持一致(或者至少一个文件)。这种的方式,当你(或者将来是你的另一个人)回到代码时,会更容易理解和改变它。
它是如何工作的...
我们可以为一条线更改的所有属性都包含在matplotlib.lines.Line2D类中。我们在下表中列出了其中的一些:
财产
|
值类型
|
描述
|
| --- | --- | --- |
| alpha | float | 设置用于混合的 alpha 值;并非所有后端都支持。 |
| color或c | 任何 matplotlib 颜色 | 设置线条的颜色。 |
| dashes | 以点为单位的开/关油墨顺序 | 设置破折号序列,即以磅为单位的带开/关墨水的破折号序列。如果seq为空或者如果seq = (None, None),linestyle将被设置为solid。 |
| label | 任何字符串 | 将自动图例的标签设置为s。 |
| linestyle或ls | [ '-' | '–' | '-.' | ':' | 'steps' | ...] | 设置线条的线条样式(也接受绘图样式)。 |
| linewidth或lw | float以点数表示的值 | 以磅为单位设置线宽。 |
| marker | [ 7 | 4 | 5 | 6 | 'o' | 'D' | 'h' | 'H' | '_' | '' | 'None' | ' ' | None | '8' | 'p' | ',' | '+' | '.' | 's' | '*' | 'd' | 3 | 0 | 1 | 2 | '1' | '3' | '4' | '2' | 'v' | '<' | '>' | '^' | '|' | 'x' | '$...$' | tuple | Nx2 array ] | 设置线条标记。 |
| markeredgecolor或mec | 任何 matplotlib 颜色 | 设置标记边缘颜色。 |
| markeredgewidth或mew | float以点数表示的值 | 以磅为单位设置标记边缘宽度。 |
| markerfacecolor或mfc | 任何 matplotlib 颜色 | 设置标记面颜色。 |
| markersize或ms | float | 以磅为单位设置标记的大小。 |
| solid_capstyle | ['butt' | 'round' | 'projecting'] | 为实线样式设置帽样式。 |
| solid_joinstyle | ['miter' | 'round' | 'bevel'] | 设置实线样式的连接样式。 |
| visible | [True | False] | 设置艺术家的可见性。 |
| xdata | np.array | 为 x 设置数据np.array。 |
| ydata | np.array | 设置数据np.array为 y。 |
| Zorder | Any number | 为艺术家设置 z 轴顺序。先画出Zorder值较低的艺术家。如果 x 轴和 y 轴水平向右,垂直于屏幕顶部,则 z 轴是向观看者延伸的轴。所以 0 值会在屏幕上,1,上面一层,以此类推。 |
以下表格显示了一些线型:
|线条样式
|
描述
|
| --- | --- |
| '-' | 固体 |
| '--' | 虚线 |
| '-.' | 虚线点 |
| ':' | 有点的 |
| 'None', ' ', '' | 什么都不画 |
下表显示了线标记:
|标记
|
描述
|
| --- | --- |
| 'o' | 圆 |
| 'D' | 钻石 |
| 'h' | 六边形 1 |
| 'H' | 六边形 2 |
| '_' | 水平线 |
| '', 'None', ' ', None | 没有任何东西 |
| '8' | 八角形 |
| 'p' | 五边形 |
| ',' | 像素 |
| '+' | 加 |
| '.' | 要点 |
| 's' | 平方 |
| '*' | 星星 |
| 'd' | 薄钻石 |
| 'v' | 三角形向下 |
| '<' | 三角形 _ 左侧 |
| '>' | 三角形 _ 右 |
| '^' | 三角形向上 |
| '|' | 垂直线 |
| 'x' | X |
颜色
我们可以通过调用matplotlib.pyplot.colors()获得【matplotlib 支持的所有颜色;这将给出:
别名
|
颜色
|
| --- | --- |
| b | 蓝色 |
| g | 格林(姓氏);绿色的 |
| r | 红色 |
| c | 蓝绿色 |
| m | 品红 |
| y | 黄色 |
| k | 黑色 |
| w | 怀特(姓氏) |
这些颜色可以在不同的 matplotlib 函数中使用,这些函数接受颜色参数。
如果这些基本的颜色不够——随着我们的进步,它们将不够——我们可以使用另外两种方式来定义颜色值。我们可以使用一个 HTML 十六进制字符串:
color = '#eeefff'
我们也可以使用合法的 HTML 颜色名称('red'、'chartreuse')。我们还可以传递一个归一化为[0, 1]的 RGB 元组:
color = (0.3, 0.3, 0.4)
参数颜色被一系列函数接受,例如title():
title('Title in a custom color', color='#123456')
背景颜色
通过为matplotlib.pyplot.axes()或matplotlib.pyplot.subplot()等功能提供axisbg,我们可以定义轴的背景颜色:
subplot(111, axisbg=(0.1843, 0.3098, 0.3098))
设置刻度、标签和网格
在这个配方中,我们将继续设置轴和线属性,并为我们的图形和图表添加更多数据。
做好准备
让我们了解一下图表和子图。
在 matplotlib 中,figure() 用于显式创建图形,表示用户界面窗口。图形是通过调用plot()或类似的函数隐式创建的。这对于简单的图表来说很好,但是具有能力来显式地创建图形并获得对其实例的引用对于更高级的使用非常有用。
一个图表包含一个或多个子图。子图允许我们在一个规则的网格中安排绘图。我们已经使用了subplot(),其中我们指定了行数和列数以及我们所指的图的数量。
如果我们想要更多的控制,我们需要使用matplotlib.axes.Axes类的轴实例。它们允许我们在图中的任何位置放置绘图。一个例子是把一个小的绘图放在一个大的绘图里。
怎么做...
刻度是数字的一部分。它们由刻度定位器(刻度出现的地方)和显示刻度如何出现的刻度格式化器组成。有大有小。默认情况下,次要刻度不可见。更重要的是,大刻度和小刻度可以相互独立地格式化和定位。
我们可以使用matplotlib.pyplot.locator_params()来控制刻度定位器的行为。即使刻度位置通常是自动确定的,我们也可以控制刻度的数量,如果我们想的话,可以使用紧凑的视图,例如当图较小时。
from pylab import *
# get current axis
ax = gca()
# set view to tight, and maximum number of tick intervals to 10
ax.locator_params(tight=True, nbins = 10)
# generate 100 normal distribution values
ax.plot(np.random.normal(10, .1, 100))
show()
这应该会给我们以下图表:

我们看到 x 轴和 y 轴是如何划分的,显示了哪些值。我们本可以使用定位器类实现相同的设置。这里我们说的是“将主定位器设置为为 10 的倍数”:
ax.xaxis.set_major_locator(matplotlib.ticker.MultipleLocator(10))
同样可以指定刻度格式化程序。格式化程序指定值(通常是数字)的显示方式。例如,matplotlib.ticker.FormatStrFormatter简单地指定'%2.1f'或'%1.1f cm'作为用于跑马灯标签的字符串。
让我们看一个使用日期的例子。
注
matplotlib 将浮点值中的日期表示为自世界协调时 0001-01-01 加 1 后经过的天数。所以,0001-01-01 世界协调时 06:00 是 1.25。
然后我们可以使用诸如matplotlib.dates.date2num()、matplotlib.dates.num2date()、matplotlib.dates.drange()等辅助函数在不同的表示之间转换日期。
让我们看另一个例子:
from pylab import *
import matplotlib as mpl
import datetime
fig = figure()
# get current axis
ax = gca()
# set some daterange
start = datetime.datetime(2013, 01, 01)
stop = datetime.datetime(2013, 12, 31)
delta = datetime.timedelta(days = 1)
# convert dates for matplotlib
dates = mpl.dates.drange(start, stop, delta)
# generate some random values
values = np.random.rand(len(dates))
ax = gca()
# create plot with dates
ax.plot_date(dates, values, linestyle='-', marker='')
# specify formater
date_format = mpl.dates.DateFormatter('%Y-%m-%d')
# apply formater
ax.xaxis.set_major_formatter(date_format)
# autoformat date labels
# rotates labels by 30 degrees by default
# use rotate param to specify different rotation degree
# use bottom param to give more room to date labels
fig.autofmt_xdate()
show()
前面的代码将给出下面的图表:

添加图例和标注
图例和标注在上下文中清晰地解释了数据图。通过给每个绘图分配一个关于它所代表的数据的简短描述,我们在读者(观众)的头脑中启用了一个更简单的心智模型。这个食谱将展示如何在我们的图形上标注特定的点,以及如何创建和定位数据图例。
做好准备
你有多少次看着图表想知道数据代表什么?更多的时候,报纸和其他日报和周刊会制造不包含适当传说的绘图,这样读者就可以自由地解读这些表象。这给读者造成了歧义,增加了出错的可能性。
怎么做...
让我们用下面的例子演示如何添加图例和标注:
from matplotlib.pyplot import *
# generate different normal distributions
x1 = np.random.normal(30, 3, 100)
x2 = np.random.normal(20, 2, 100)
x3 = np.random.normal(10, 3, 100)
# plot them
plot(x1, label='plot')
plot(x2, label='2nd plot')
plot(x3, label='last plot')
# generate a legend box
legend(bbox_to_anchor=(0., 1.02, 1., .102), loc=3,
ncol=3, mode="expand", borderaxespad=0.)
# annotate an important value
annotate("Important value", (55,20), xycoords='data',
xytext=(5, 38),
arrowprops=dict(arrowstyle='->'))
show()
在代码之前的将给出如下图:

我们所做的是为每个图分配一个字符串标签,这样legend()将尝试并确定在图例框中添加什么。
我们通过定义loc参数来设置图例框的位置。这是可选的,但是我们希望指定一个最不可能在绘图线上绘制图例框的位置。
它是如何工作的...
下表给出了所有位置参数字符串:
|线
|
数值
|
| --- | --- |
| upper right | 1 |
| upper left | 2 |
| lower left | 3 |
| lower right | 4 |
| right | 5 |
| center left | 6 |
| center right | 7 |
| lower center | 8 |
| upper center | 9 |
| center | 10 |
要不在图例中显示标签,请将标签设置为_nolegend_。
对于图例,我们用ncol = 3定义列数,用lower left设置位置。我们指定了一个边界框(bbox_to_anchor)从位置(0., 1.02)开始,宽度为1,高度为0.102。这些是标准化的轴坐标。参数mode为None或expand,允许图例框水平扩展填充轴区域。参数borderaxespad定义轴和图例边框之间的填充。
对于标注,我们已经定义了要在坐标xy上绘制的字符串。坐标系被指定为与数据坐标系相同;因此,坐标系为xycoord = 'data'。文本的起始位置由xytext的值定义。
一个箭头从xytext画到xy坐标,并且arrowprops字典可以为该箭头定义许多属性。对于这个例子,我们使用arrowstyle来定义箭头样式。
将脊椎移向中央
这个食谱将演示如何将脊椎移到中间。
脊线定义数据区边界;它们连接轴刻度标记。有四根刺。我们可以把它们放在任何我们想放的地方;默认情况下,它们被放置在轴的边界上,因此我们看到数据图周围有一个框。
怎么做...
要将刺移到绘图中心,我们需要去掉两个刺,使其隐藏(将color设置为none)。之后,我们移动另外两个坐标(0,0)。坐标在数据空间坐标中指定。
下面的代码显示了如何做到这一点:
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(-np.pi, np.pi, 500, endpoint=True)
y = np.sin(x)
plt.plot(x, y)
ax = plt.gca()
# hide two spines
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
# move bottom and left spine to 0,0
ax.spines['bottom'].set_position(('data',0))
ax.spines['left'].set_position(('data',0))
# move ticks positions
ax.xaxis.set_ticks_position('bottom')
ax.yaxis.set_ticks_position('left')
plt.show()
这就是绘图会是什么样子:

它是如何工作的...
这段代码依赖于绘制的图,因为我们正在将脊线移动到位置(0,0),并且在(0,0)位于图中间的间隔上绘制正弦函数。
然而,这展示了如何将脊椎移动到一个特定的位置,以及如何去掉我们不想展示的脊椎。
还有更多...
此外,spines 可以被限制在数据结束的地方,例如,使用set_smart_bounds(True)调用。在这种情况下,matplotlib 试图以一种复杂的方式设置界限,例如,处理倒排界限,或者在数据超出视图范围时剪切要查看的行。
制作直方图
直方图很简单,但是将正确的数据放入其中很重要。我们现在将讨论 2D 的直方图。
直方图用于可视化数据分布的估计。一般来说,我们在谈到直方图时会用到几个术语。垂直矩形表示数据点在特定时间间隔内的频率,称为箱。以固定的间隔创建面元,因此直方图的总面积等于数据点的数量。
直方图可以显示数据的相对频率,而不是使用数据的绝对值。在这种情况下,总面积等于 1。
直方图通常在图像处理软件中用作可视化图像属性的一种方式,例如特定颜色通道中的光分布。此外,这些图像直方图可用于计算机视觉算法中,以检测峰值,帮助边缘检测、图像分割等。
在第 5 章、制作三维可视化中,我们有处理三维直方图的食谱。
做好准备
箱的数量是我们想要得到的值,但是很难得到正确的值,因为没有关于最佳箱数量的严格规则。关于如何计算箱数有不同的理论,最简单的是基于天花板函数的理论,其中箱数( k )等于天花板(max(x)–min(x)/h),其中 x 是绘制的数据集, h 是所需的箱宽度。这只是一种选择,因为正确显示数据所需的箱数取决于实际数据分布。
怎么做...
我们用一组参数创建一个名为matplotlib.pyploy.hist()的直方图。以下是一些最有用的方法:
bins:这要么是整数个箱,要么是给出箱的序列。默认为10。range:这是仓位的范围,如果仓位是按顺序给定的,则不使用。离群值被忽略,默认为None。normed:如果该值为True,直方图值归一化,形成概率密度。默认为False。histtype:这是默认的条形直方图。其他选项包括:barstacked:对于多个数据,这给出了堆叠视图直方图。step:这将创建一个未填充的线图。stepfilled:这将创建默认填充的线图。默认为bar。
align:这将使料箱边缘之间的条居中。默认为mid。其他数值有left和right。color:指定直方图的颜色。它可以是单个值,也可以是一系列颜色。如果指定了多个数据集,颜色序列将以相同的顺序使用。如果未指定,则使用默认的线条颜色序列。orientation:这允许通过将orientation设置为horizontal来创建水平直方图。默认为vertical。
以下代码演示了hist()的用法:
import numpy as np
import matplotlib.pyplot as plt
mu = 100
sigma = 15
x = np.random.normal(mu, sigma, 10000)
ax = plt.gca()
# the histogram of the data
ax.hist(x, bins=35, color='r')
ax.set_xlabel('Values')
ax.set_ylabel('Frequency')
ax.set_title(r'$\mathrm{Histogram:}\ \mu=%d,\ \sigma=%d$' % (mu, sigma))
plt.show()
这为我们的数据样本创建了一个整洁的红色直方图:

它是如何工作的...
我们从生成一些正态分布的数据开始。直方图以指定的箱数(35 个)绘制,并通过将normed设置为True(或1)进行归一化;我们将color设置为red ( r)。
之后,我们为绘图设置标签和标题。在这里,我们使用编写 LaTeX 表达式的能力来编写数学符号,并将其与 Python 格式字符串混合在一起。
用误差线制作条形图
在本食谱中,我们将展示如何创建条形图以及如何绘制误差线。
做好准备
为了可视化数据集中的测量不确定性或指示误差,我们可以使用误差线。误差线可以很容易地给出数据集无误差的概念。它们可以显示一个标准偏差、一个标准误差或 95%的置信区间。这里没有标准,所以一定要明确说明误差线显示什么值(误差)。实验科学中的大多数论文应该包含误差条来表示数据的准确性。
怎么做...
即使只有两个参数是强制的— left和height —我们经常想要使用更多的参数。以下是我们可以使用的一些参数:
width:这给出了条的宽度。默认值为0.8。bottom:如果指定了bottom,则该值加到高度上。默认为None。edgecolor:这给出了条边的颜色。ecolor:指定任意误差线的颜色。linewidth:这给出了条边的宽度;特殊值为None(使用默认值)和0(不显示条边时)。orientation:这有两个值vertical和horizontal。xerr和yerr:用于在条形图上生成误差线。
一些可选参数(color、edgecolor、linewidth、xerr和yerr)可以是单个值或长度与小节数相同的序列。
它是如何工作的...
让我们用一个例子来说明这一点:
import numpy as np
import matplotlib.pyplot as plt
# generate number of measurements
x = np.arange(0, 10, 1)
# values computed from "measured"
y = np.log(x)
# add some error samples from standard normal distribution
xe = 0.1 * np.abs(np.random.randn(len(y)))
# draw and show errorbar
plt.bar(x, y, yerr=xe, width=0.4, align='center', ecolor='r', color='cyan', label='experiment #1');
# give some explainations
plt.xlabel('# measurement')
plt.ylabel('Measured values')
plt.title('Measurements')
plt.legend(loc='upper left')
plt.show()
前面的代码将绘制以下内容:

为了能够绘制误差线,我们需要一些措施(x);对于计算出的每一个度量值(y,我们引入了误差(xe)。
我们使用 NumPy 来生成和计算值;标准分布对于演示目的来说已经足够好了,但是如果你碰巧提前知道你的数据分布,你总是可以制作一些原型可视化,并尝试不同的布局来找到呈现信息的最佳选项。
另一个有趣的选择,如果我们正在准备黑白介质的可视化是阴影;它可以具有以下值:
|阴影值
|
描述
|
| --- | --- |
| / | 对角线阴影线 |
| \ | 后对角线 |
| | | 垂直阴影线 |
| - | 水平的 |
| + | 交叉的 |
| x | 交叉对角线 |
| o | 小圆 |
| 0 | 大圈 |
| . | 光点图形 |
| * | 星形图案 |
还有更多...
我们刚才使用的是误差线,称为对称误差线。如果数据集的性质使得误差在两个方向上(负的和正的)不相同,我们也可以使用不对称误差线分别指定它们。
我们所要做的不同是使用两元素列表(如 2D 数组)来指定xerr或yerr,其中第一个列表包含负误差的值,第二个列表包含正误差的值。
让饼图有价值
饼图在很多方面都很特别,最重要的是它们显示的数据集总和必须达到 100%,否则它们就完全无效。
做好准备
饼图代表数字比例,其中每个线段的弧长与其代表的数量成比例。
它们很紧凑,看起来很有美感,但它们一直被批评为难以比较。饼图的另一个不符合其最大利益的特性是,饼图以特定的角度(视角)呈现,而线段使用特定的颜色,这会扭曲我们的感知,并影响我们对所呈现信息的结论。
我们将在这里展示使用饼图呈现数据的不同方式。
怎么做...
首先,我们创建一个所谓的分解的饼图:
from pylab import *
# make a square figure and axes
figure(1, figsize=(6,6))
ax = axes([0.1, 0.1, 0.8, 0.8])
# the slices will be ordered
# and plotted counter-clockwise.
labels = 'Spring', 'Summer', 'Autumn', 'Winter'
# fractions are either x/sum(x) or x if sum(x) <= 1
x = [15, 30, 45, 10]
# explode must be len(x) sequence or None
explode=(0.1, 0.1, 0.1, 0.1)
pie(x, explode=explode, labels=labels,
autopct='%1.1f%%', startangle=67)
title('Rainy days by season')
show()
如果饼图位于方形图中,并且有方形轴,则看起来最佳。
饼图总和的分数定义为x/sum(x),如果是sum(x) <= 1,则定义为x。我们通过定义一个爆炸序列来获得爆炸效果,其中每一项代表偏移每条弧线的半径分数。我们使用autopct参数来格式化将要在弧内绘制的标签;它们可以是格式字符串或可调用函数。
我们还可以使用布尔阴影参数为饼图添加阴影效果。
如果我们不指定startangle,分数将从 x 轴(角度 0)逆时针开始排序。如果我们将90指定为startangle的值,那么饼图将从 y 轴开始。
这是生成的饼图:

绘制填充区域
在本食谱中,我们将向您展示如何填充曲线下或两条不同曲线之间的区域。
做好准备
库 matplotlib 允许我们用颜色填充曲线之间和之下的区域,这样我们就可以向观众显示该区域的价值。有时,读者(观众)理解给定的专业是必要的。
怎么做...
下面是一个如何填充两个轮廓之间区域的示例:
from matplotlib.pyplot import figure, show, gca
import numpy as np
x = np.arange(0.0, 2, 0.01)
# two different signals are measured
y1 = np.sin(2*np.pi*x)
y2 = 1.2*np.sin(4*np.pi*x)
fig = figure()
ax = gca()
# plot and
# fill between y1 and y2 where a logical condition is met
ax.plot(x, y1, x, y2, color='black')
ax.fill_between(x, y1, y2, where=y2>=y1, facecolor='darkblue', interpolate=True)
ax.fill_between(x, y1, y2, where=y2<=y1, facecolor='deeppink', interpolate=True)
ax.set_title('filled between')
show()
它是如何工作的...
在我们为预定义的时间间隔生成随机信号后,我们使用规则的plot()绘制这两个信号。然后我们用必需属性和强制属性来调用fill_between()。
fill_between() 功能是使用x作为拾取y值(y1、y2)的位置,然后以特定定义的颜色绘制多边形。
我们用where参数指定一个条件来填充曲线,该参数接受布尔值(可以是表达式),因此只有在满足where条件时才会进行填充。

还有更多...
与其他绘图功能类似,该功能也接受更多参数,例如,hatch(指定填充图案而不是颜色)和线条选项(linewidth和linestyle)。
还有fill_betweenx(),启用类似的填充特征,但在水平曲线之间也是如此。
更通用的功能fill() 提供了用颜色或阴影填充任何多边形的能力。
用彩色标记绘制散点图
如果你有两个变量,并想找出它们之间的相关性,散点图可能是点模式的解决方案。
这种类型的绘图也非常适合作为多维数据的更高级可视化的起点,例如,绘制散点图矩阵。
做好准备
散点图显示两组数据的值。数据可视化是作为没有线连接的点的集合来完成的。它们中的每一个都有由变量值决定的坐标。一个变量是受控的(独立变量),而另一个变量是测量的(因变量),通常绘制在 y 轴上。
怎么做...
下面是一个代码示例,绘制了两个图:一个具有不相关的数据,另一个具有强正相关:
import matplotlib.pyplot as plt
import numpy as np
# generate x values
x = np.random.randn(1000)
# random measurements, no correlation
y1 = np.random.randn(len(x))
# strong correlation
y2 = 1.2 + np.exp(x)
ax1 = plt.subplot(121)
plt.scatter(x, y1, color='indigo', alpha=0.3, edgecolors='white', label='no correl')
plt.xlabel('no correlation')
plt.grid(True)
plt.legend()
ax2 = plt.subplot(122, sharey=ax1, sharex=ax1)
plt.scatter(x, y2, color='green', alpha=0.3, edgecolors='grey', label='correl')
plt.xlabel('strong correlation')
plt.grid(True)
plt.legend()
plt.show()
在这里,我们还使用了更多的参数,例如color用于设置绘图的颜色,marker用于用作点标记(默认为circle)、alpha (alpha 透明度)、edgecolors(标记边缘的颜色)和label(用于图例框)。
这些是我们得到的图:

它是如何工作的...
散点图通常用于识别两个变量之间的潜在关联,并且通常在拟合回归函数之前绘制。它给出了相关性的良好视觉图像,特别是对于非线性关系。matplotlib 提供了scatter()功能来绘制x与y一维数组,一维数组的长度与散点图相同。
四、更多绘图和自定义
在本章中,我们将了解:
- 设置轴标签的透明度和大小
- 给图表线条添加阴影
- 向图中添加数据表
- 使用子图
- 自定义网格
- 创建等高线图
- 填充地下区域
- 绘制极坐标图
- 使用极坐标条可视化文件系统树
简介
在本章中,我们将探索 matplolib 库的更高级的属性。我们将介绍更多的选择,并将研究如何获得某些视觉上令人愉悦的结果。
在这一章中,我们将寻求在简单的图表还不够时,用数据表示一些非平凡问题的解决方案。我们将尝试使用多种类型的图形或创建混合图形来覆盖一些高级数据结构和所需的表示。
设置轴标签的透明度和大小
Axes标签描述了图中的数据所代表的内容,对于观众理解图本身非常重要。通过为axes背景提供标签,我们帮助观众以适当的方式理解中的信息。
做好准备
在我们深入研究代码之前,了解 matplotlib 如何组织我们的数据是很重要的。
在顶层,有一个Figure实例,它包含了我们看到的一切和更多的东西(我们没有看到的)。该图包含了Axes类作为字段Figure.axes的实例。Axes实例包含我们关心的几乎所有东西:所有的线、点、记号和标签。所以,当我们呼叫plot()时,我们正在Axes.lines列表中添加一行(matplotlib.lines.Line2D)。如果我们绘制直方图(hist()),我们将在Axes.patches列表中添加矩形(“面片”是从 MATLAB 继承而来的术语,代表“颜色面片”的概念)。
Axes的一个实例还包含对XAxis和YAxis实例的引用,它们又分别引用 x 轴和 y 轴。XAxis和YAxis管理轴、标签、刻度、刻度标签、定位器和格式化器的绘制。我们可以分别通过Axes.xaxis和Axes.yaxis来参考。我们不必一直到XAxis或YAxis实例才能到达标签,因为 matplotlib 为我们提供了一个辅助方法(实际上是一个快捷方式),可以通过这些标签实现迭代:matplotlib.pyplot.xlabel()和matplotlib.pyplot.ylabel()。
怎么做...
我们现在将创建一个新的数字,我们将:
-
用一些随机生成的数据创建一个图。
-
添加
title和axes标签。 -
添加 alpha 设置。
-
给
title和axes标签添加阴影效果。import matplotlib.pyplot as plt from matplotlib import patheffects import numpy as np data = np.random.randn(70) fontsize = 18 plt.plot(data) title = "This is figure title" x_label = "This is x axis label" y_label = "This is y axis label" title_text_obj = plt.title(title, fontsize=fontsize, verticalalignment='bottom') title_text_obj.set_path_effects([patheffects.withSimplePatchShadow()]) # offset_xy -- set the 'angle' of the shadow # shadow_rgbFace -- set the color of the shadow # patch_alpha -- setup the transparency of the shadow offset_xy = (1, -1) rgbRed = (1.0,0.0,0.0) alpha = 0.8 # customize shadow properties pe = patheffects.withSimplePatchShadow(offset_xy = offset_xy, shadow_rgbFace = rgbRed, patch_alpha = alpha) # apply them to the xaxis and yaxis labels xlabel_obj = plt.xlabel(x_label, fontsize=fontsize, alpha=0.5) xlabel_obj.set_path_effects([pe]) ylabel_obj = plt.ylabel(y_label, fontsize=fontsize, alpha=0.5) ylabel_obj.set_path_effects([pe]) plt.show()
它是如何工作的...
我们已经知道所有熟悉的导入、生成数据的零件以及基本的绘图技术,所以我们将跳过这些。如果您无法破译示例的前几行,请参考第 2 章、了解您的数据、第 3 章、绘制您的第一个图并定制它们,这些概念在这里已经解释过了。
绘制数据集后,我们准备添加标题和标签,并自定义它们的外观。
首先,我们添加标题。然后我们定义标题文本的字体大小和垂直对齐方式为bottom。如果我们使用的是不带参数的matplotlib.patheffects.withSimplePatchShadow(),默认阴影效果会添加到标题中。参数默认值为:offset_xy=(2,-2)、shadow_rgbFace=None和patch_alpha=0.7。其他数值有center、top、baseline但是我们选择bottom作为文字会有一些阴影。下一行我们添加阴影效果。路径效果是支持matplotlib.text.Text和matplotlib.patches.Patch的matplotlib模块matplotlib.patheffects的一部分。
我们现在想给 x 轴和 y 轴添加不同的阴影设置。首先,我们自定义阴影相对于父对象的位置(偏移),然后设置阴影的颜色。此处,颜色由 0.0 到 1.0 之间的三个浮点值(三元组)表示,用于每个 RGB 通道。因此,我们的红色被表示为(1.0, 0.0, 0.0)(全红,无绿,无蓝)。
透明度(或 alpha)被设置为一个规范化的值,我们也希望在这里将其设置为不同于默认值。
有了所有的设置,我们实例化matplotlib.patheffects.withSimplePatchShadow并将对它的引用保存在变量pe中,以便几行后重用它。
为了能够应用阴影效果,我们需要到达label对象。这很简单,因为matplotlib.pyplot.xlabel()返回对对象(matplotlib.text.Text)的引用,然后我们用它来调用set_path_effects([pe])。
我们终于展示了绘图,并为我们的工作感到自豪。
还有更多...
如果对matplotlib.patheffects当前提供的效果不满意,可以继承matplotlib.patheffects._Base类并覆盖draw_path方法。在这里看一下代码和关于如何做到这一点的评论:
https://github . com/matplotlib/matplotlib/blob/master/lib/matplotlib/patheffects . py # l47
给图表线条添加阴影
为了能够区分图中的一条特定的绘图线或,只是为了适合我们的图所在的输出的整体风格,我们有时需要给图表线(或直方图,就此而言)添加阴影效果。在这个食谱中,我们将学习如何给图表线条添加阴影效果。
做好准备
为了给图表中的线条或矩形添加阴影,我们需要使用 matplotlib 中构建的位于matplotlib.transforms的变换框架。
为了理解这一切是如何工作的,我们需要解释变换在 matplotlib 中是什么以及它们是如何工作的。
变换知道如何将给定坐标从其坐标系转换成显示。他们也知道如何将它们从显示坐标转换成自己的坐标系。
下表总结了现有坐标系及其代表的内容:
|坐标系
|
转换对象
|
描述
|
| --- | --- | --- |
| Data | Axes.transData | 表示用户的数据坐标系。 |
| Axes | Axes.transAxes | 表示Axes坐标系,其中(0,0)表示轴的左下角,(1,1)表示轴的右上角。 |
| Figure | Figure.transFigure | 这是Figure坐标系,其中(0,0)代表图的左下角,(1,1)代表图的右上角。 |
| Display | None | 表示用户显示的像素坐标系,其中(0,0)表示显示的左下角,元组(宽度,高度)表示显示的右上角,其中宽度和高度以像素为单位。 |
请注意,显示在列中没有值。这是因为默认坐标系是Display,所以坐标相对于您的显示坐标系总是以像素为单位。这不是很有用,大多数情况下,我们希望将它们归一化到Figure或Axes或Data坐标系中。
这个框架使我们能够将当前对象转换为偏移对象,也就是说,将该对象放置在与原始对象偏移一定距离的位置。
我们将使用这个框架在绘制的正弦波上创建我们想要的效果。
怎么做...
以下是为绘制的图表添加阴影的代码配方。代码将在下一节中解释。
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.transforms as transforms
def setup(layout=None):
assert layout is not None
fig = plt.figure()
ax = fig.add_subplot(layout)
return fig, ax
def get_signal():
t = np.arange(0., 2.5, 0.01)
s = np.sin(5 * np.pi * t)
return t, s
def plot_signal(t, s):
line, = axes.plot(t, s, linewidth=5, color='magenta')
return line,
def make_shadow(fig, axes, line, t, s):
delta = 2 / 72\. # how many points to move the shadow
offset = transforms.ScaledTranslation(delta, -delta, fig.dpi_scale_trans)
offset_transform = axes.transData + offset
# We plot the same data, but now using offset transform
# zorder -- to render it below the line
axes.plot(t, s, linewidth=5, color='gray',
transform=offset_transform,
zorder=0.5 * line.get_zorder())
if __name__ == "__main__":
fig, axes = setup(111)
t, s = get_signal()
line, = plot_signal(t, s)
make_shadow(fig, axes, line, t, s)
axes.set_title('Shadow effect using an offset transform')
plt.show()
它是如何工作的...
在if __name__检查之后,我们从底部开始读取代码。首先,我们在setup()中创建图形和轴;之后,我们获得一个信号(或产生数据——正弦波)。我们在plot_signal()中绘制基本信号。然后,我们进行阴影变换,在make_shadow()中绘制阴影。
我们使用偏移效果在下方创建一个偏移对象,并且距离原始对象只有几个点。
原始对象是一个简单的正弦波,我们使用标准函数plot()绘制。
要添加到该偏移变换,matplotlib 包含辅助变换— matplotlib.transforms.ScaledTranslation。
dx和dy的值是以点定义的,由于点是 1/72 英寸,我们将偏移对象向右移动 2pt,向下移动 2pt。
注
如果你想了解更多关于我们如何将该点转换为 1/71 英寸的信息,请阅读维基百科的这篇文章:http://en.wikipedia.org/wiki/Point_%28typography%29。
我们可以用matplotlib.transforms.ScaledTransformation(xtr, ytr, scaletr);这里,xtr和ytr是平移偏移,scaletr是变换时间和显示前可调用缩放xtr和ytr的变换。这方面最常见的使用情形是从点转换到显示空间:例如,转换到 DPI,这样无论实际输出是什么,无论是显示器还是打印材料,偏移量总是保持在相同的位置。我们用于此的可调用程序已经内置,可在Figure.dpi_scale_trans获得。
然后,我们用应用的转换绘制相同的数据。
还有更多...
使用变换来添加阴影只是这个框架的一个,并不是最流行的用例。为了能够使用转换框架做更多的事情,您将需要了解转换管道如何工作以及扩展点是什么(继承什么类以及如何继承)的细节。这已经足够容易了,因为 matplotlib 是开源的,即使有些代码没有很好的文档化,也有一个你可以阅读和使用或更改的源代码,从而有助于 matplotlib 的整体质量和有用性。
在图中添加数据表
虽然 matplotlib 主要是一个绘图库,但它在我们创建图表时帮助我们完成的小差事,比如在我们漂亮的图表旁边有一个整洁的数据表。在本食谱中,我们将学习如何在图中的图表旁边显示数据表。
做好准备
理解我们为什么要在图表中添加表格是很重要的。直观绘制数据的主要目的是解释不可理解(或难以理解)的数据值。现在,我们想把这些数据加回去。仅仅在图表下面塞进一张大桌子的数值是不明智的。
但是,仔细挑选,也许从整个图表数据集中总结或突出显示的值可以识别图表的重要部分,或者强调那些精确值(例如,以美元表示的年销售额)很重要(甚至是必需的)的地方的重要值。
怎么做...
下面是向图中添加示例表的代码:
import matplotlib.pylab as plt
import numpy as np
plt.figure()
ax = plt.gca()
y = np.random.randn(9)
col_labels = ['col1','col2','col3']
row_labels = ['row1','row2','row3']
table_vals = [[11, 12, 13], [21, 22, 23], [28, 29, 30]]
row_colors = ['red', 'gold', 'green']
my_table = plt.table(cellText=table_vals,
colWidths=[0.1] * 3,
rowLabels=row_labels,
colLabels=col_labels,
rowColours=row_colors,
loc='upper right')
plt.plot(y)
plt.show()
先前的代码片段给出了如下图:

它是如何工作的...
使用plt.table()我们创建一个单元格表,并将其添加到当前轴。该表可以有(可选的)行和列标题。每个表格单元格包含补丁或文本。可以指定表格的列宽和行高。返回值是构成该表的一系列对象(文本、行和面片实例)。
基本功能签名是:
table(cellText=None, cellColours=None,
cellLoc='right', colWidths=None,
rowLabels=None, rowColours=None, rowLoc='left',
colLabels=None, colColours=None, colLoc='center',
loc='bottom', bbox=None)
该函数实例化并返回matplotlib.table.Table实例。matplotlib 通常就是这种情况;将表添加到图中只有一种方法。面向对象的接口可以直接访问。我们可以直接使用matplotlib.table.Table类来微调我们的表,然后用add_table()将其添加到我们的axes实例中。
还有更多...
如果直接创建一个matplotlib.table.Table的实例,并在将其添加到axes实例之前对其进行配置,可以获得更多的控制权。您可以使用Axes.add_table(table)将table实例添加到axes,其中table是matplotlib.table.Table的实例。
使用子图
如果你从一开始就阅读这本书,你可能对subplot类很熟悉,它是axes的后代,生活在subplot实例的规则网格上。我们将解释并演示如何以高级方式使用子图。
在这个食谱中,我们将学习如何在我们的绘图中创建自定义的子绘图配置。
做好准备
子图的基类是matplotlib.axes.SubplotBase。这些子绘图是matplotlib.axes.Axes实例,但提供了在图形中生成和操纵一组Axes的辅助方法。
有一个类matplotlib.figure.SubplotParams,保存subplot的所有参数。尺寸标准化为图形的宽度或高度。正如我们已经知道的,如果我们不指定任何自定义值,它们将从rc参数中读取。
脚本层(matplotlib.pyplot)包含一些辅助方法来操作子场景。
matplotlib.pyplot.subplots用于轻松创建子图的通用布局。我们可以指定网格的大小——子图网格的行数和列数。
我们可以创建共享 x 轴或 y 轴的子场景。这是使用sharex或sharey关键字参数实现的。参数sharex可以有值True,在这种情况下,x 轴在所有子绘图中共享。除了最后一行图,刻度标签在所有图上都不可见。也可以定义为字符串,枚举值为row、col、all或none。数值all与True相同,数值none与False相同。如果指定了值row,则每个子图行共享 x 轴。如果指定了值col,则每个子图列共享 x 轴。该助手返回元组fig, ax,其中ax或者是轴实例,或者,如果创建了多个子图,则是轴实例的数组。
matplotlib.pyplot.subplots_adjust用于调整子图布局。关键字参数指定图形内的子图坐标(left、right、bottom和top)归一化为图形大小。分别使用宽度和高度的wspace和hspace参数,可以指定在子绘图之间留出空白。
怎么做...
-
我们将向您展示在 matplotlib 工具包中使用另一个助手函数的示例—
subplot2grid。我们定义网格的几何形状和子图的位置。请注意,这个位置是从 0 开始的,而不是像我们在plot.subplot()中习惯的从 1 开始。我们还可以使用colspan和rowspan来允许子绘图跨越给定网格中的多列和多行。例如,我们将:创建一个图形;使用subplot2grid添加各种子图布局;重新配置刻度标签大小。 -
Show the plot:
import matplotlib.pyplot as plt plt.figure(0) axes1 = plt.subplot2grid((3, 3), (0, 0), colspan=3) axes2 = plt.subplot2grid((3, 3), (1, 0), colspan=2) axes3 = plt.subplot2grid((3, 3), (1, 2)) axes4 = plt.subplot2grid((3, 3), (2, 0)) axes5 = plt.subplot2grid((3, 3), (2, 1), colspan=2) # tidy up tick labels size all_axes = plt.gcf().axes for ax in all_axes: for ticklabel in ax.get_xticklabels() + ax.get_yticklabels(): ticklabel.set_fontsize(10) plt.suptitle("Demo of subplot2grid") plt.show()当我们执行前面的代码时,会创建以下图:
![How to do it...]()
它是如何工作的...
我们为subplot2grid提供形状、位置(loc)以及可选的rowspan和colspan。这里重要的区别是位置是从 0 开始索引的,而不是像在figure.add_subplot那样从 1 开始。
还有更多...
举一个另一种方式的例子,你可以自定义当前的axes或subplot:
axes = fig.add_subplot(111)
rectangle = axes.patch
rectangle.set_facecolor('blue')
这里我们看到每个axes实例都包含一个引用rectangle实例的字段补丁,因此代表了当前axes实例的背景。这个实例有我们可以更新的属性,因此更新当前axes背景。例如,我们可以改变它的颜色,但也可以加载图像来添加水印保护。
也可以先创建一个补丁,然后将其添加到axes背景中:
fig = plt.figure()
axes = fig.add_subplot(111)
rect = matplotlib.patches.Rectangle((1,1), width=6, height=12)
axes.add_patch(rect)
# we have to manually force a figure draw
axes.figure.canvas.draw()
定制网格
网格通常便于在线条和图表下绘制,因为它有助于人眼发现图案上的差异,并直观地比较图中的图。为了能够设置网格的显示方式、频率和样式,或者是否显示,我们应该使用matplotlib.pyplot.grid。
在这个食谱中,我们将学习如何打开和关闭网格,以及如何改变网格上的主要和次要刻度。
做好准备
最频繁的网格定制可以在matplotlib.pyplot.grid助手功能中到达。
要看这个的交互效果,应该在ipython –pylab下运行以下。对plt.grid()的基本调用将切换上一个 IPython PyLab 环境启动的当前交互会话中的网格可见性:
In [1]: plt.plot([1,2,3,3.5,4,4.3,3])
Out[1]: [<matplotlib.lines.Line2D at 0x3dcc810>]

现在我们可以在同一个图上切换网格:
In [2]: plt.grid()
我们重新打开网格,如下图所示:

然后我们再次关闭 if:
In [3]: plt.grid()

除了打开和关闭它们,我们还可以进一步定制网格外观。
我们可以仅使用主要刻度或次要刻度或两者来操纵网格;因此,函数参数which的值可以是'major'、'minor'或'both'。与此类似,我们可以使用参数axis分别控制水平和垂直刻度,该参数可以有值'x'、'y'或'both'。
所有其他属性都通过kwargs传递,并表示一个matplotlib.lines.Line2D实例可以接受的标准属性集,例如color、linestyle和linewidth;这里有一个例子:
ax.grid(color='g', linestyle='--', linewidth=1)
怎么做...
这很好,但我们希望能够定制更多。为了做到这一点,我们需要深入 matplotlib 和mpl_toolkits并找到AxesGrid模块,该模块允许我们以简单和可管理的方式制作轴的网格:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import ImageGrid
from matplotlib.cbook import get_sample_data
def get_demo_image():
f = get_sample_data("axes_grid/bivariate_normal.npy", asfileobj=False)
# z is a numpy array of 15x15
Z = np.load(f)
return Z, (-3, 4, -4, 3)
def get_grid(fig=None, layout=None, nrows_ncols=None):
assert fig is not None
assert layout is not None
assert nrows_ncols is not None
grid = ImageGrid(fig, layout, nrows_ncols=nrows_ncols,
axes_pad=0.05, add_all=True, label_mode="L")
return grid
def load_images_to_grid(grid, Z, *images):
min, max = Z.min(), Z.max()
for i, image in enumerate(images):
axes = grid[i]
axes.imshow(image, origin="lower", vmin=min, vmax=max,
interpolation="nearest")
if __name__ == "__main__":
fig = plt.figure(1, (8, 6))
grid = get_grid(fig, 111, (1, 3))
Z, extent = get_demo_image()
# Slice image
image1 = Z
image2 = Z[:, :10]
image3 = Z[:, 10:]
load_images_to_grid(grid, Z, image1, image2, image3)
plt.draw()
plt.show()
给定的代码将呈现以下图表:

它是如何工作的...
在函数get_demo_image中,我们从 matplotlib 附带的样本数据目录中加载数据。
列表grid保存了我们的axes网格(这里是ImageGrid)。
变量image1、image2和image3保存了我们在列表grid中的多个轴上分割的来自 Z 的切片数据。
在所有网格上循环,我们使用标准的imshow()调用绘制来自im1、im2和im3的数据,而 matplotlib 注意所有东西都被整齐地渲染和对齐。
创建等高线图
等高线图显示矩阵的等值线。等值线是曲线,其中两个变量的函数具有相同的值。
在本食谱中,我们将学习如何创建等高线图。
做好准备
等高线表示为矩阵 Z 的等高线图,其中 Z 解释为相对于 X-Y 平面的高度。z 的最小大小为 2,并且必须包含至少两个不同的值。
等高线图的问题在于,如果没有标注等值线就对其进行编码,它们会变得非常无用,因为我们无法将高点与低点进行解码,也无法找到局部最小值。
这里我们也需要标注轮廓。等值线的标注可以使用标注(clabel())或colormaps。如果您的输出媒体允许使用颜色,colormaps是首选,因为观众将能够更容易地解码数据。
等高线图的另一个风险是选择绘制等值线的数量。如果我们选择太多,绘图会变得太密集而无法解码,如果我们选择太少的等值线,我们会丢失信息,并且可以以不同的方式感知数据。
contour() 功能会自动猜测要绘制多少条等值线,但我们也有能力指定自己的编号。
在 matplotlib 中,我们使用matplotlib.pyplot.contour绘制等高线图。
有两个类似的功能:contour()绘制等高线,contourf()绘制填充等高线。我们将只演示contour(),但几乎所有内容都适用于contourf()。他们也理解几乎相同的论点。
函数contour()可以有不同的调用签名,这取决于我们有什么数据和/或我们想要可视化的属性是什么。
呼叫签名
|
描述
|
| --- | --- |
| contour(Z) | 绘制 Z(数组)的轮廓。级别值是自动选择的。 |
| contour(X,Y,Z) | 绘制 X、Y 和 z 的轮廓。阵列X和Y是(X,Y)表面坐标。 |
| contour(Z,N)``contour(X,Y,Z,N) | 绘制Z的轮廓,其中层级的数量由N定义。级别值是自动选择的。 |
| contour(Z,V) contour(X,Y,Z,V) | 用V中指定的值绘制等高线。 |
| contourf(..., V) | 依次填充级别值之间的len(V)-1区域V。 |
| contour(Z, **kwargs) | 使用关键字参数控制公共线条属性(颜色、线条宽度、原点、颜色映射等)。 |
X、Y、Z 的维度和形状都存在一定的约束,比如 X、Y 可以是二维的,和 Z 的形状相同,如果是一维的,比如 X 的长度等于 Z 的列数,那么 Y 的长度就等于 Z 的行数
怎么做...
在下面的代码示例中,我们将:
-
实现一个功能作为模拟信号处理器。
-
生成一些线性信号数据。
-
将数据转换成适合矩阵运算的矩阵。
-
绘制等高线。
-
添加等高线标签。
-
展示绘图。
-
将 numpy 导入为
np。 -
将 matplotlib 汇入为
mpl。 -
将
matplotlib.pyplot导入为plt。def process_signals(x,y): return (1 – (x ** 2 + y ** 2)) * np.exp(-y ** 3 / 3) x = np.arange(-1.5, 1.5, 0.1) y = np.arange(-1.5, 1.5, 0.1) # Make grids of points X,Y = np.meshgrid(x, y) Z = process_signals(X, Y) # Number of isolines N = np.arange(-1, 1.5, 0.3) # adding the Contour lines with labels CS = plt.contour(Z, N, linewidths=2, cmap=mpl.cm.jet) plt.clabel(CS, inline=True, fmt='%1.1f', fontsize=10) plt.colorbar(CS) plt.title('My function: $z=(1-x^2+y^2) e^{-(y^3)/3}$') plt.show()
这将为我们提供以下图表:

它是如何工作的...
我们从numpy开始寻找小帮手来创建我们的范围和矩阵。
在我们将my_function评估为Z之后,我们简单的称之为contour,提供Z和等值线的层数。
此时,尝试在N arange()调用中尝试第三个参数。例如,不要选择N = np.arange(-1, 1.5, 0.3),而是尝试将0.3更改为0.1或1来体验如何以不同的方式看待相同的数据,这取决于我们如何在等高线图中对数据进行编码。
我们还通过简单地给它CS(一个matplotlib.contour.QuadContourSet实例)添加了一个彩色地图。
填充绘图下区域
在 matplotlib 中绘制填充多边形的基本方式是使用matplotlib.pyplot.fill。该函数接受与matplotlib.pyplot.plot相似的参数——多个x和y对以及其他Line2D属性。该函数返回添加的Patch实例列表。
在本食谱中,我们将学习如何给绘图交叉点的特定区域着色。
做好准备
matplotlib 提供了几个功能来帮助我们绘制填充图形,当然,除了绘制本来就是绘制闭合填充多边形的功能,比如histogram ()。
我们已经提到了一个——matplotlib.pyplot.fill——但是还有matplotlib.pyplot.fill_between()和matplotlib.pyploy.fill_betweenx()功能。这些函数填充两条曲线之间的多边形。fill_between()和fill_betweenx()的主要区别在于后者填充在 x 轴值之间,而前者填充在 y 轴值之间。
函数fill_between 接受参数x—数据的 x 轴数组,以及y1和y2—数据的 y 轴数组。使用参数,我们可以指定填充区域的条件。该条件是布尔条件,通常指定 y 轴值范围。默认值是None——意思是,到处都要填。
怎么做...
从一个简单的例子开始,我们将填充一个简单函数下的区域:
import numpy as np
import matplotlib.pyplot as plt
from math import sqrt
t = range(1000)
y = [sqrt(i) for i in t]
plt.plot(t, y, color='red', lw=2)
plt.fill_between(t, y, color='silver')
plt.show()
前面的代码给出了下面的图:

这相当简单,并给出了fill_between()的工作原理。注意我们如何需要绘制实际的功能线(当然是使用plot(),其中fill_between()只是绘制了一个填充了颜色的多边形区域('silver')。
我们将在这里演示另一个食谱。这将涉及对fill功能的更多调节。以下是该示例的代码:
import matplotlib.pyplot as plt
import numpy as np
x = np.arange(0.0, 2, 0.01)
y1 = np.sin(np.pi*x)
y2 = 1.7*np.sin(4*np.pi*x)
fig = plt.figure()
axes1 = fig.add_subplot(211)
axes1.plot(x, y1, x, y2, color='grey')
axes1.fill_between(x, y1, y2, where=y2<=y1, facecolor='blue', interpolate=True)
axes1.fill_between(x, y1, y2, where=y2>=y1, facecolor='gold', interpolate=True)
axes1.set_title('Blue where y2 <= y1\. Gold-color where y2 >= y1.')
axes1.set_ylim(-2,2)
# Mask values in y2 with value greater than 1.0
y2 = np.ma.masked_greater(y2, 1.0)
axes2 = fig.add_subplot(212, sharex=axes1)
axes2.plot(x, y1, x, y2, color='black')
axes2.fill_between(x, y1, y2, where=y2<=y1, facecolor='blue', interpolate=True)
axes2.fill_between(x, y1, y2, where=y2>=y1, facecolor='gold', interpolate=True)
axes2.set_title('Same as above, but mask')
axes2.set_ylim(-2,2)
axes2.grid('on')
plt.show()
前面的代码将呈现如下图:

它是如何工作的...
对于这个例子,我们首先创建了两个在某些点重叠的正弦函数。
我们还创建了两个子场景来比较渲染填充区域的两个变体。
在这两种情况下,我们都将fill_between()与参数where一起使用,该参数接受一个 N 长度的布尔数组,并将填充where等于True的区域。
底部的子图显示了mask_greater,它用大于给定值的值屏蔽了一个数组。这是numpy.ma包中的一个函数,用于处理缺失或无效的值。我们在底部的轴上转动网格,以便更容易发现这一点。
绘制极坐标图
如果数据已经用极坐标表示,我们也可以用极坐标图形显示。即使数据不是极坐标,也要考虑转换成极坐标形式,在极坐标图上绘制。
要回答我们是否要这样做,我们需要了解数据代表什么,我们希望向最终用户显示什么。想象用户将从我们的图形中读取和解码什么,通常会引导我们达到最佳的可视化效果。
极坐标图通常用于显示本质上呈放射状的信息。例如,在太阳路径图中——我们在径向投影中看到天空,天线的辐射图在不同角度辐射不同。你可以在以下网站了解更多信息:T2。
在本食谱中,我们将学习如何更改绘图中使用的坐标系,并改用极坐标系统。
做好准备
要在极坐标中显示数据,我们必须有适当的数据值。在极坐标系统中,一个点用半径距离(通常用 r 表示)和角度(通常用θ表示)来描述。角度可以是弧度或度数,但 matplotlib 使用度数。
与函数plot()非常相似,为了绘制极坐标图,我们将使用函数polar() ,该函数接受两个相同长度的参数数组theta和r,分别用于角度数组和半径数组。该函数还接受其他格式参数,与plot()函数相同。
我们还需要告诉 matplotlib,我们想要极坐标系统中的轴。这是通过向add_axes或add_subplot函数提供polar=True参数来实现的。
此外,要在图形上设置其他属性,如半径或角度上的网格,我们需要使用matplotlib.pyplot.rgrids()切换径向网格可见性或设置标签。同样,我们使用matplotlib.pyplot.thetagrid()来配置角度记号和标签。
怎么做...
这里有一个演示如何绘制极坐标的配方:
import numpy as np
import matplotlib.cm as cm
import matplotlib.pyplot as plt
figsize = 7
colormap = lambda r: cm.Set2(r / 20.)
N = 18 # number of bars
fig = plt.figure(figsize=(figsize,figsize))
ax = fig.add_axes([0.2, 0.2, 0.7, 0.7], polar=True)
theta = np.arange(0.0, 2*np.pi, 2*np.pi/N)
radii = 20*np.random.rand(N)
width = np.pi/4*np.random.rand(N)
bars = ax.bar(theta, radii, width=width, bottom=0.0)
for r, bar in zip(radii, bars):
bar.set_facecolor(colormap(r))
bar.set_alpha(0.6)
plt.show()
前面的代码片段将给出下面的图:

它是如何工作的...
首先,我们创建一个正方形图形,并添加极轴。图形不一定是正方形,但这样我们的极坐标图就会是椭球形。
然后,我们为一组角度(θ)和一组极距(半径)生成随机值。因为我们画了条,我们也需要为每个条设置一组宽度,所以我们也生成了一组宽度。由于maplotlib.axes.bar接受一组值(就像 matplotlib 中几乎所有的绘图函数一样),我们不必循环这个生成的数据集;我们只需要把所有的论据传递给律师协会一次。
为了让每一个小节都容易区分,我们必须循环添加到ax(轴)的每一个小节,并自定义其外观(面色和透明度)。
使用极坐标条可视化文件系统树
我们想在这个食谱中展示如何解决一个“现实世界”的任务——如何使用 matplotlib 来可视化我们的目录占用率。
在这个食谱中,我们将学习如何可视化具有相对大小的文件系统树。
做好准备
我们都有很大的硬盘,里面有时会有我们通常会忘记的东西。如果能看到这样一个目录里面有什么,里面最大的文件是什么,那就太好了。
虽然有许多更复杂和精细的软件产品可以完成这项工作,但我们想展示如何使用 Python 和 matplotlib 来实现这一点。
怎么做...
让我们执行以下步骤:
-
实现一些助手函数来处理文件夹发现和内部数据结构。
-
实现主功能
draw(),进行绘图。 -
实现验证用户输入参数的主程序主体:
import os import sys import matplotlib.pyplot as plt import matplotlib.cm as cm import numpy as np def build_folders(start_path): folders = [] for each in get_directories(start_path): size = get_size(each) if size >= 25 * 1024 * 1024: folders.append({'size' : size, 'path' : each}) for each in folders: print "Path: " + os.path.basename(each['path']) print "Size: " + str(each['size'] / 1024 / 1024) + " MB" return folders def get_size(path): assert path is not None total_size = 0 for dirpath, dirnames, filenames in os.walk(path): for f in filenames: fp = os.path.join(dirpath, f) try: size = os.path.getsize(fp) total_size += size #print "Size of '{0}' is {1}".format(fp, size) except OSError as err: print str(err) pass return total_size def get_directories(path): dirs = set() for dirpath, dirnames, filenames in os.walk(path): dirs = set([os.path.join(dirpath, x) for x in dirnames]) break # we just want the first one return dirs def draw(folders): """ Draw folder size for given folder""" figsize = (8, 8) # keep the figure square ldo, rup = 0.1, 0.8 # leftdown and right up normalized fig = plt.figure(figsize=figsize) ax = fig.add_axes([ldo, ldo, rup, rup], polar=True) # transform data x = [os.path.basename(x['path']) for x in folders] y = [y['size'] / 1024 / 1024 for y in folders] theta = np.arange(0.0, 2 * np.pi, 2 * np.pi / len(x)) radii = y bars = ax.bar(theta, radii) middle = 90/len(x) theta_ticks = [t*(180/np.pi)+middle for t in theta] lines, labels = plt.thetagrids(theta_ticks, labels=x, frac=0.5) for step, each in enumerate(labels): each.set_rotation(theta[step]*(180/np.pi)+ middle) each.set_fontsize(8) # configure bars colormap = lambda r:cm.Set2(r / len(x)) for r, each in zip(radii, bars): each.set_facecolor(colormap(r)) each.set_alpha(0.5) plt.show() -
接下来,我们将实现主程序主体,在这里我们验证当从命令行调用程序时用户给出的输入参数:
if __name__ == '__main__': if len(sys.argv) is not 2: print "ERROR: Please supply path to folder." sys.exit(-1) start_path = sys.argv[1] if not os.path.exists(start_path): print "ERROR: Path must exits." sys.exit(-1) folders = build_folders(start_path) if len(folders) < 1: print "ERROR: Path does not contain any folders." sys.exit(-1) draw(folders)
您需要从命令行运行以下命令:
$ python ch04_rec11_filesystem.py /usr/lib/
它将产生一个类似于这个的绘图:

它是如何工作的...
我们将从代码的底部开始,在if __name__ == '__main__'之后,因为那是我们程序开始的地方。
使用模块sys,我们提取命令行参数;它们表示我们想要可视化的目录的路径。
函数build_folders建立字典列表,每个字典包含它在给定的start_path中找到的大小和路径。该函数调用get_directories,返回start_path中所有子目录的列表。之后,对于每个找到的目录,我们使用get_size函数计算字节大小。
为了调试的目的,我们打印我们的字典,这样我们就可以将数字与我们的数据进行比较。
在我们将文件夹构建为字典列表后,我们将它们传递给一个函数draw,该函数执行将数据转换为正确维度的所有工作(这里,我们使用极坐标系统),构建极坐标图形,并绘制所有的条、记号和标签。
严格来说,我们应该把这个工作分成更小的功能,尤其是如果这个代码要进一步开发的话。
五、制作三维可视化效果
我们将在本章中学习以下食谱:
- 创建三维条
- 创建三维直方图
- 在 matplotlib 中制作动画
- 用 OpenGL 制作动画
简介
三维可视化有时是有效的,有时是不可避免的。在这里,我们给出了一些例子来满足最常见的需求。
本章的内容将介绍和解释一些关于三维可视化的主题。
创建三维条
虽然 matplotlib 主要专注于绘图,主要是二维的,但是有不同的扩展,让我们可以在地理地图上绘图,与 Excel 进行更多的集成,进行 3D 绘图。这些扩展在 matplotlib 世界中被称为工具包。工具包是一个特定功能的集合,它专注于一个主题,比如三维绘图。
流行的工具包有底图、 GTK 工具、Excel 工具、Natgrid 、AxesGrid 和 mplot3d。
我们将在这个食谱中探索更多的 mplot3d。工具箱mpl_toolkits.mplot3d提供了一些基本的 3D 绘图。支持的绘图有散点、曲面、直线和网格。虽然这不是最好的三维绘图库,但它附带了 matplotlib,我们已经熟悉了界面。
做好准备
基本上,我们仍然需要创建一个图形,并向其中添加所需的轴。不同的是,我们为图形指定了三维投影,添加的轴是轴和轴。
现在,我们可以使用几乎相同的功能进行绘图。当然,不同的是论点,因为我们现在有三个轴,我们需要来提供数据。
例如,函数mpl_toolkits.mplot3d.Axes3D.plot指定xs、ys、zs和zdir参数。其他全部直接转入matplotlib.axes.Axes.plot。我们将解释这些具体的论点:
xs和ys:这是 x 轴和 y 轴的坐标zs:这是 z 轴的值。它可以是所有点的一个,也可以是每个点的一个zdir:这将选择 z 轴尺寸(通常这是zs,但可以是xs或ys)
注
模块mpl_toolkits.mplot3d.art3d中有一个方法rotate_axes,包含 3D 美工代码和功能,可以将 2D 美工转换成 3D 版本,可以添加到 Axes3D 中对坐标进行重新排序,使轴随zdir一起旋转。默认值为 z,在轴前面加上“-”进行逆变换,因此zdir可以是 x、-x、y、-y、z 或-z。
怎么做...
这是演示所解释概念的代码:
import random
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
from mpl_toolkits.mplot3d import Axes3D
mpl.rcParams['font.size'] = 10
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
for z in [2011, 2012, 2013, 2014]:
xs = xrange(1,13)
ys = 1000 * np.random.rand(12)
color = plt.cm.Set2(random.choice(xrange(plt.cm.Set2.N)))
ax.bar(xs, ys, zs=z, zdir='y', color=color, alpha=0.8)
ax.xaxis.set_major_locator(mpl.ticker.FixedLocator(xs))
ax.yaxis.set_major_locator(mpl.ticker.FixedLocator(ys))
ax.set_xlabel('Month')
ax.set_ylabel('Year')
ax.set_zlabel('Sales Net [usd]')
plt.show()
在代码之前的产生如下图:

它是如何工作的...
我们必须做和 2D 世界一样的准备工作。不同的是,我们需要指定后端的类型。然后我们生成一些随机数据,例如,4 年的销售(2011-2014 年)。
我们需要为三维轴指定相同的 Z 值。
我们从色图集中随机选择一种颜色,然后我们关联每一个 Z 顺序的xs、ys对集合,我们将渲染条形图系列。
还有更多...
其他来自 2D matplotlib 的绘图可在此获得;例如,界面与plot()相似的scatter() ,但是增加了点标记的大小。我们也熟悉contour``contourf和bar。
仅在 3D 中可用的新类型是线框、曲面和三曲面图。
下面的代码示例绘制了流行的普林格尔函数或更精确的双曲抛物面的三曲面图:
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
import matplotlib.pyplot as plt
import numpy as np
n_angles = 36
n_radii = 8
# An array of radii
# Does not include radius r=0, this is to eliminate duplicate points
radii = np.linspace(0.125, 1.0, n_radii)
# An array of angles
angles = np.linspace(0, 2*np.pi, n_angles, endpoint=False)
# Repeat all angles for each radius
angles = np.repeat(angles[...,np.newaxis], n_radii, axis=1)
# Convert polar (radii, angles) coords to cartesian (x, y) coords
# (0, 0) is added here. There are no duplicate points in the (x, y) plane
x = np.append(0, (radii*np.cos(angles)).flatten())
y = np.append(0, (radii*np.sin(angles)).flatten())
# Pringle surface
z = np.sin(-x*y)
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.plot_trisurf(x, y, z, cmap=cm.jet, linewidth=0.2)
plt.show()
在代码之前的将给出以下输出:

创建三维直方图
与 3D 条类似,我们可能希望创建 3D 直方图。它们用来很容易地发现三个独立变量之间的相关性。它们可用于从图像中提取信息,其中第三维可以是被分析图像的(x,y)空间中的通道强度。
在这个食谱中,我们将学习如何创建三维直方图。
做好准备
回想一下,直方图表示某个值在特定列(通常称为“bin”)中出现的次数。三维直方图代表网格中出现的次数。这个网格是矩形的,包含两个变量,这两个变量是两列中的数据。
怎么做...
对于该计算,我们将:
- 使用 NumPy,因为它具有计算两个变量直方图的功能。
- 从正态分布中生成 x 和 y,但使用不同的参数,以便能够在生成的直方图中区分相关性。
- 绘制同一数据集的散点图,以展示散点图与三维直方图的显示差异。
下面是实现所述步骤的代码示例:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
from mpl_toolkits.mplot3d import Axes3D
mpl.rcParams['font.size'] = 10
samples = 25
x = np.random.normal(5, 1, samples)
y = np.random.normal(3, .5, samples)
fig = plt.figure()
ax1 = fig.add_subplot(211, projection='3d')
# compute two-dimensional histogram
hist, xedges, yedges = np.histogram2d(x, y, bins=10)
# compute location of the x,y bar positions
elements = (len(xedges) - 1) * (len(yedges) - 1)
xpos, ypos = np.meshgrid(xedges[:-1]+.25, yedges[:-1]+.25)
xpos = xpos.flatten()
ypos = ypos.flatten()
zpos = np.zeros(elements)
# make every bar the same width in base
dx = .1 * np.ones_like(zpos)
dy = dx.copy()
# this defines the height of the bar
dz = hist.flatten()
ax1.bar3d(xpos, ypos, zpos, dx, dy, dz, color='b', alpha=0.4)
ax1.set_xlabel('X Axis')
ax1.set_ylabel('Y Axis')
ax1.set_zlabel('Z Axis')
# plot the same x,y correlation in scatter plot
# for comparison
ax2 = fig.add_subplot(212)
ax2.scatter(x, y)
ax2.set_xlabel('X Axis')
ax2.set_ylabel('Y Axis')
plt.show()
在代码之前的将给出以下输出:

它是如何工作的...
我们使用np.histogram2d准备一个计算机直方图,该直方图返回我们的直方图(hist)和 x 和 y 面元边缘。
因为对于bard3d函数我们需要 x,y 空间中的坐标,所以我们需要计算公共矩阵坐标,为此我们使用np.meshgrid,它将 x 和 y 位置向量组合到 2D 空间网格(矩阵)中。我们可以用它来绘制 xy 平面位置的条。
变量dx和dy表示每个条的底部宽度,我们希望使其恒定,因此我们给 xy 平面中每个位置一个 0.1 点的值。
z 轴(dz)中的值实际上是我们的计算机直方图(在变量hist中),它表示特定仓中常见 x 和 y 样本的计数。
下面的散点图(在前面的图中)显示了 2D 轴,该轴也显示了两个相似分布之间的相关性,但具有不同的起始参数集。
有时候,3D 给了我们更多的信息,并以更好的方式与数据中包含的内容产生了共鸣。由于 3D 可视化往往比 2D 更令人困惑,建议我们在选择它们而不是 2D 之前三思。
在 matplotlib 中制作动画
在这份食谱中,我们将探索如何使我们的图表栩栩如生。有时候让图片在动画中移动来解释当我们改变变量的值时发生了什么是更具描述性的。我们的主库有有限但通常足够的动画功能,我们将解释如何使用它们。
做好准备
从 1.1 版本开始,标准 matplotlib 增加了一个动画框架,它的主要类是matplotlib.animation.Animation。此类是基类;与已经提供的类TimedAnimation、ArtistAnimation和FuncAnimation的情况一样,它将针对特定行为被子类化。下表给出了这些类的描述:
类名(父类)
|
描述
|
| --- | --- |
| Animation ( object ) | 这个类使用 matplotlib 包装动画的创建。它只是一个基类,应该被子类化以提供所需的行为。 |
| TimedAnimation ( Animation ) | 这个动画子类支持基于时间的动画,并且每隔*毫秒绘制一个新的帧。 |
| ArtistAnimation ( TimedAnimation ) | 在调用这个函数之前,应该已经进行了所有的绘制,并且保存了相关的艺术家。 |
| FuncAnimation ( TimedAnimation ) | 这通过重复调用一个函数,传入(可选的)参数来制作动画。 |
为了能够将动画保存在视频文件中,我们必须有 ffmpeg 或 mencoder 安装程序。这些软件包的安装因所使用的操作系统而异,并且因不同版本而有所变化,因此我们必须将其留给我们亲爱的读者,以获得谷歌的有效信息。
怎么做...
下面的代码清单演示了一些 matplotlib 动画:
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
fig = plt.figure()
ax = plt.axes(xlim=(0, 2), ylim=(-2, 2))
line, = ax.plot([], [], lw=2)
def init():
"""Clears current frame."""
line.set_data([], [])
return line,
def animate(i):
"""Draw figure.
@param i: Frame counter
@type i: int
"""
x = np.linspace(0, 2, 1000)
y = np.sin(2 * np.pi * (x - 0.01 * i)) * np.cos(22 * np.pi * (x - 0.01 * i))
line.set_data(x, y)
return line,
# This call puts the work in motion
# connecting init and animate functions and figure we want to draw
animator = animation.FuncAnimation(fig, animate, init_func=init,
frames=200, interval=20, blit=True)
# This call creates the video file.
# Temporary, every frame is saved as PNG file
# and later processed by ffmpeg encoder into MPEG4 file
# we can pass various arguments to ffmpeg via extra_args
animator.save('basic_animation.mp4', fps=30,
extra_args=['-vcodec', 'libx264'],
writer='ffmpeg_file')
plt.show()
这将在开始处理该文件的文件夹中创建文件basic_animation.mp4,并显示一个带有运行动画的图形窗口。视频文件可以用大多数支持 MPEG-4 格式的现代视频播放器打开。图(框架)应该如下图所示:

它是如何工作的...
最重要的是功能init()、 animate()和 save()。我们首先通过传递两个回调函数init和animate来构造FuncAnimate。然后我们调用它的save()方法保存我们的视频文件。下表提供了每个功能的更多详细信息:
函数名
|
使用
|
| --- | --- |
| init | 通过参数init_func传递给matplotlib.animation.FuncAnimation构造器,在绘制下一帧之前清除帧。 |
| animate | 通过func参数传递给matplotlib.animation.FuncAnimation构造函数。我们要制作动画的图形通过fig参数传递,该参数在引擎盖下传递给matplotlib.animation.Animation构造器,将动画事件与我们要绘制的图形连接起来。这个函数从帧中获取(可选的)参数,通常是可迭代的,表示多个帧。 |
| matplotlib.animation.Animation.save | 通过绘制每一帧来保存电影文件。在通过编码器(ffmpeg 或 mencoder)处理临时图像文件以创建视频文件之前,它会创建临时图像文件。该功能还接受配置视频输出、元数据(作者...),要使用的编解码器,分辨率/大小等等。参数之一是定义使用什么视频编码器的参数。目前支持的有 ffmpeg、ffmpeg_file 和 mencoder。 |
还有更多...
matplotlib.animation.ArtistAnimation的用法不同于FuncAnimation的用法,因为我们必须事先画出每个艺术家,然后用所有艺术家不同的框架实例化ArtistAnimation类。艺术家动画是matplotlib.animation.TimedAnimation类的一种包装,每 N 毫秒绘制一帧,因此支持基于时间的动画。
注
不幸的是,对于 Mac OS X 用户来说,动画框架在这个平台上可能会很麻烦,有时甚至根本不起作用,这将随着 matplotlib 的未来发布而得到改善。
用 OpenGL 制作动画
使用 OpenGL 的动机来自于当我们面对一个任务,要可视化数百万个数据点并快速完成时(有时甚至是实时)CPU 处理能力的限制。
现代计算机有强大的图形处理器,用于快速可视化相关计算(如游戏),没有理由不能用于科学相关可视化。
实际上,编写硬件加速软件至少有一个缺点。就硬件依赖性而言,现代图形卡需要专有驱动程序,有时在目标平台/机器上不可用(例如,用户笔记本电脑);即使在可用的情况下,有时在站点上安装所需的依赖项并不是您想要花费时间的事情,而您想要的只是展示您的发现并展示您的研究结果。这不是表演的阻碍,但请记住这一点,并衡量在您的项目中引入这种复杂性的好处和成本。
解释了注意事项后,我们可以对硬件加速可视化说是,对加速图形的行业标准 OpenGL 说是。
我们将使用 OpenGL,因为它是跨平台的,所以如果您安装了所需的硬件和操作系统级别的驱动程序,这些示例应该可以在 Linux、Mac 或 Windows 上工作。
做好准备
如果你从未使用过 OpenGL,我们现在就试着简单介绍一下,虽然要真正了解 OpenGL,至少需要阅读和理解一整本书。OpenGL 是一个规范,而不是一个实现,所以 OpenGL 本身没有任何代码,这里的实现都是根据这个规范开发的库。这些是随您的操作系统一起提供的,或者是由显卡供应商提供的,如英伟达或 AMD/ATI。
此外,OpenGL 只关心图形渲染,而不关心动画、定时和其他复杂的事情,这些都留给额外的库来处理。
注
使用 OpenGL 制作动画的基础知识
因为 OpenGL 是一个渲染库,它不知道我们在屏幕上画什么对象。它不在乎我们画一只猫,一个球,一条线,或者所有这些物体。因此,要移动渲染对象,我们需要清除并再次绘制整个图像。为了制作动画,我们需要一个循环,它可以非常快速地绘制和重绘所有内容,并将其显示给用户,以便用户认为他/她正在观看动画。
在机器上安装 OpenGL 是一个平台相关的过程。在 Mac OS X 上,OpenGL 实现是操作系统升级的一部分,但是开发库(所谓的“头”)是 Xcode 开发包的一部分。
在 Windows 上,最好的方法是为您的显卡安装供应商最新的图形驱动程序。没有它们,OpenGL 可能会工作,但你可能会没有股票驱动程序的最新功能。
在 Linux 上,如果您不反对安装封闭源码软件,那么可以从发行版自己的软件管理器或供应商网站下载供应商特定的驱动程序作为可安装的二进制文件。标准实现几乎总是 Mesa3D,这是最著名的 OpenGL 实现,它使用 Xorg 为 Linux、FreeBSD 和类似的操作系统提供对 OpenGL 的支持。
基本上,在 Debian/Ubuntu 上,您应该安装以下包及其依赖项:
$ sudo apt-get install libgl1-mesa-dev libgl-mesa-dri
在此之后,您应该准备好使用一些开发库和/或框架来实际编写 OpenGL 支持的应用。
我们这里的重点是 Python,所以我们将概述一些构建在 OpenGL 之上的最常用的 Python 库和框架。我们将提到 matplotlib 及其当前和未来对 OpenGL 的支持:
- Mayavi :这是一个专门做 3D 的图书馆
- Pyglet :这是一个用于图形的纯 Python 库
- Glumpy :这个是一个建立在 NumPy 之上的快速渲染库
- Pyglet****OpenGL:这是用来可视化大数据(百万个数据点)
怎么做...
专业项目 Mayavi 是全功能 3D 图形库,主要用于高级 3D 渲染。它附带了已经提到的 Python 包,如 EPD(虽然不是免费许可),这是一种推荐的在 Windows 和 Mac OS X 上安装的方法。在 Linux 上;也可以使用 pip 轻松安装:
$ pip install mayavi
Mayavi 可以用作开发库/框架或应用。Mayavi 应用包括一个可视化编辑器,用于简单的数据探索和某种程度上的交互式可视化。
作为一个库,它可以像 matplotlib 一样使用,无论是从脚本接口还是作为一个完整的面向对象的库。该接口大部分在模块mlab内部,以便能够使用该接口。例如,使用 Mayavi 制作一个简单的动画可以如下进行:
import numpy
from mayavi.mlab import *
# Produce some nice data.
n_mer, n_long = 6, 11
pi = numpy.pi
dphi = pi/1000.0
phi = numpy.arange(0.0, 2*pi + 0.5*dphi, dphi, 'd')
mu = phi*n_mer
x = numpy.cos(mu)*(1+numpy.cos(n_long*mu/n_mer)*0.5)
y = numpy.sin(mu)*(1+numpy.cos(n_long*mu/n_mer)*0.5)
z = numpy.sin(n_long*mu/n_mer)*0.5
# View it.
l = plot3d(x, y, z, numpy.sin(mu), tube_radius=0.025, colormap='Spectral')
# Now animate the data.
ms = l.mlab_source
for i in range(100):
x = numpy.cos(mu)*(1+numpy.cos(n_long*mu/n_mer +
numpy.pi*(i+1)/5.)*0.5)
scalars = numpy.sin(mu + numpy.pi*(i+1)/5)
ms.set(x=x, scalars=scalars)
前面的代码将产生以下具有旋转图形的窗口:

它是如何工作的...
我们生成数据集,并为 x、y 和 z 创建一组函数,用于图形的起始位置的 plot3d 函数。
然后我们导入mlab_source对象,该对象使我们能够在点和标量的层次上操纵我们的图。然后,我们使用这个特性为循环设置特定的点和标量,以创建 100 帧的旋转动画。
还有更多...
如果想多实验一下,最简单的方法就是加载 IPython ,导入mayavi.mlab,运行一些test_*功能。
要了解发生了什么,您可以使用 IPython 检查和探索 Python 源代码的能力,如以下代码所示:
In [1]: import mayavi.mlab
In [2]: mayavi.mlab.test_simple_surf??
Type: function
String Form:<function test_simple_surf at 0x641b410>
File: /usr/lib/python2.7/dist-packages/mayavi/tools/helper_functions.py
Definition: mayavi.mlab.test_simple_surf()
Source:
def test_simple_surf():
"""Test Surf with a simple collection of points."""
x, y = numpy.mgrid[0:3:1,0:3:1]
return surf(x, y, numpy.asarray(x, 'd'))
我们在这里看到如何通过在函数名后面加上两个问号(“??"),IPython 找到了函数的来源,并展示给我们。这是一种真正的探索性计算,在可视化社区中经常使用,因为是了解您的数据和代码的快速方法。
使用皮格莱特快速启动
Pyglet 是另一个流行的 Python 库,它简化了图形和窗口相关的应用的编写。它通过它的模块pyglet.gl支持 OpenGL ,但是你不用直接和这个模块对话就可以使用 Pyglet 的力量。最方便的使用是通过pyglet.graphics。
皮格莱特对马亚维采取了不同的方法;没有可视化的 IDE,从创建一个窗口到发出一个低级别的 OpenGL 调用来配置 OpenGL 上下文,一切都由你负责。这有时比 Mayavi 慢,但您获得的是控制应用每一部分的能力。有时这也意味着更多的工作时间,但通常这意味着应用的质量和性能更好。
使用以下代码可以获得最简单的应用(图像查看器):
import pyglet
window = pyglet.window.Window()
image = pyglet.resource.image('kitten.jpg')
@window.event
def on_draw():
window.clear()
image.blit(0, 0)
pyglet.app.run()
这里可以看到我们创建了一个窗口,加载了一个图像,定义了当我们绘制一个窗口对象时将会发生什么(也就是说,我们为on_draw事件定义了一个事件处理程序)。最后,我们运行我们的应用(pyglet.app.run())。
在引擎盖下,OpenGL 用于在窗口中绘制。该界面可从pyglet.gl模块访问。然而,直接使用它并不高效,所以 pyglet 在pyglet.graphics有一个更简单的接口,在这个接口内部使用顶点数组和缓冲区。
使用咕咚快速启动
Glumpy 是一个 OpenGL 加 NumPy 库,使用 OpenGL 实现快速 NumPy 可视化。这是一个开源项目,由尼古拉斯·罗杰尔发起,旨在提高效率。要使用它,我们需要 Python OpenGL 绑定、SciPy,当然还有 Glumpy。为此,请使用以下命令:
sudo apt-get install python-opengl
sudo pip install scipy
sudo pip install glumpy
Glumpy 使用 OpenGL 纹理来表示数组,因为这可能是现代图形硬件上最快的可视化方法。
Pyprocessing 游戏攻略
Pyprocessing 的工作方式与 Processing(http://processing.org)非常相似。Pyprocessing 中的大部分功能等同于处理功能。如果您熟悉 Processing 和 Python,那么您已经知道编写 Pyprocessing 应用所需的几乎所有内容。要使用它,我们唯一需要做的就是导入pyprocessing包,使用 Pyprocessing 函数和数据结构编写剩下的代码,调用run()。
关于 OpenGL 以及如何从 C/C++或任何其他语言绑定中使用它,有很多免费教程。这里提供了一个列表,在官方的 OpenGL 维基上。
一般来说,还有许多处理 Python、OpenGL 和 3D 可视化的项目。有些是年轻的,有些是没有保养的,但是如果你发现一个需要提到的,让我们知道。
六、使用图像和地图绘制图表
本章包含的食谱将显示:
- 用 PIL 处理图像
- 用图像绘图
- 显示图中其他图的图像
- 使用底图在地图上绘制数据
- 使用谷歌地图应用编程接口在地图上绘制数据
- 生成验证码图像
简介
本章探讨如何使用图像和地图。Python 有一些著名的图像库,允许我们以美学和科学的方式处理图像。
我们将通过演示如何通过应用滤镜和调整大小来处理图像,来触及 PIL 的能力。
此外,我们将展示如何使用图像文件作为 matplotlibs 图表的标注。
为了处理地理空间数据集的数据可视化,我们将介绍 Python 可用库和公共 API 的功能,我们可以将它们用于基于地图的视觉表示。
最后的食谱展示了 Python 如何创建验证码测试图像。
用 PIL 处理图像
如果我们可以用 WIMP(http://en . Wikipedia . org/wiki/WIMP _(computing))或者所见即所得(http://en.wikipedia.org/wiki/WYSIWYG)来达到同样的目的,为什么还要用 Python 进行图像处理呢?这是因为我们希望创建一个自动化系统来在没有人工支持的情况下实时处理图像,从而优化图像管道。
做好准备
请注意,PIL 坐标系假设(0,0)坐标位于左上角。
Image模块有一个有用的类和实例方法来对加载的图像对象(im)执行基本操作:
im = Image.open(filename):这个打开一个文件,将图像加载到im对象中。im.crop(box):这个在box定义的坐标内裁剪图像。box定义左、上、右、下像素坐标(例如:box = (0, 100, 100,100))。im.filter(filter):这个在图像上应用一个滤镜,返回一个过滤后的图像。im.histogram():这个返回这个图像的直方图列表,其中每一项代表像素数。对于单通道图像,列表中的项目数是 256,但是如果图像不是单通道图像,列表中可以有更多的项目。对于 RGB 图像,列表包含 768 个项目(每个通道一组 256 个值)。im.resize(size, filter):这将调整图像大小,并使用过滤器进行重采样。可能的过滤器有NEAREST、BILINEAR、BICUBIC和ANTIALIAS。默认为NEAREST。im.rotate(angle, filter):逆时针方向旋转图像。im.split():这个分割图像的波段,并返回单个波段的元组。用于将一个 RGB 图像分割成三个单波段图像。im.transform(size, method, data, filter):这将使用数据和过滤器对给定图像进行变换。变身可以是AFFINE、EXTENT、QUAD、MESH。您可以在官方文档中阅读更多关于转换的内容。数据定义了原始图像中将应用变换的框。
ImageDraw模块允许我们在图像上绘制,在这里我们可以使用圆弧、椭圆、直线、切片、点和多边形等功能来修改加载图像的像素。
ImageChops模块包含多个图像通道操作(因此得名Chops),可用于图像合成、绘画、特殊效果和其他处理操作。通道操作仅允许用于 8 位图像。以下是一些有趣的频道操作:
ImageChops.duplicate(image):这将当前图像复制到新的图像对象中ImageChops.invert(image):这将反转图像并返回副本ImageChops.difference(image1, image2):这有助于验证图像是否相同,无需目视检查
ImageFilter模块包含内核类的实现,允许创建自定义卷积内核。该模块还包含一组健康的通用滤镜,允许将知名滤镜(BLUR和MedianFilter)应用到我们的图像中。
ImageFilter模块提供了两种类型的滤波器:固定图像增强滤波器和需要定义某些参数的图像滤波器;例如,要使用的内核大小。
型式
我们可以轻松获得 IPython 中所有固定过滤器名称的列表:
In [1]: import ImageFilter
In [2]: [ f for f in dir(ImageFilter) if f.isupper()]
Out[2]:
['BLUR',
'CONTOUR',
'DETAIL',
'EDGE_ENHANCE',
'EDGE_ENHANCE_MORE',
'EMBOSS',
'FIND_EDGES',
'SHARPEN',
'SMOOTH',
'SMOOTH_MORE']
下一个示例展示了我们如何在任何支持的图像上应用所有当前支持的固定滤镜:
import os
import sys
from PIL import Image, ImageChops, ImageFilter
class DemoPIL(object):
def __init__(self, image_file=None):
self.fixed_filters = [ff for ff in dir(ImageFilter) if ff.isupper()]
assert image_file is not None
assert os.path.isfile(image_file) is True
self.image_file = image_file
self.image = Image.open(self.image_file)
def _make_temp_dir(self):
from tempfile import mkdtemp
self.ff_tempdir = mkdtemp(prefix="ff_demo")
def _get_temp_name(self, filter_name):
name, ext = os.path.splitext(os.path.basename(self.image_file))
newimage_file = name + "-" + filter_name + ext
path = os.path.join(self.ff_tempdir, newimage_file)
return path
def _get_filter(self, filter_name):
# note the use Python's eval() builtin here to return function object
real_filter = eval("ImageFilter." + filter_name)
return real_filter
def apply_filter(self, filter_name):
print "Applying filter: " + filter_name
filter_callable = self._get_filter(filter_name)
# prevent calling non-fixed filters for now
if filter_name in self.fixed_filters:
temp_img = self.image.filter(filter_callable)
else:
print "Can't apply non-fixed filter now."
return temp_img
def run_fixed_filters_demo(self):
self._make_temp_dir()
for ffilter in self.fixed_filters:
temp_img = self.apply_filter(ffilter)
temp_img.save(self._get_temp_name(ffilter))
print "Images are in: {0}".format((self.ff_tempdir),)
if __name__ == "__main__":
assert len(sys.argv) == 2
demo_image = sys.argv[1]
demo = DemoPIL(demo_image)
# will create set of images in temporary folder
demo.run_fixed_filters_demo()
我们可以从命令提示符轻松运行:
$ pythonch06_rec01_01_pil_demo.py image.jpeg
我们在DemoPIL类中打包了我们的小演示,因此我们可以在围绕演示功能run_fixed_filters_demo共享通用代码的同时轻松扩展它。这里的常用代码包括打开图像文件,测试该文件是否真的是文件,创建临时目录来保存我们的过滤图像,建立过滤图像文件名,以及向用户打印有用的信息。这样,代码以更好的方式组织,我们可以轻松地专注于演示功能,而不需要接触代码的其他部分。
这个演示将打开我们的图像文件,并将ImageFilter中可用的每个固定过滤器应用到它,并将新过滤的图像保存在唯一的临时目录中。这个临时目录的位置被检索,所以我们可以用操作系统的文件资源管理器打开它并查看创建的图像。
作为一个可选的练习,尝试扩展这个演示类,在给定的图像上执行ImageFilter中可用的其他过滤器。
怎么做...
本节中的示例显示了我们如何处理特定文件夹中的所有图像。我们指定一个目标路径,程序读取该目标路径(images 文件夹)中的所有图像文件,并按照指定的比例(本例中为0.1)调整大小,并将每个文件保存在名为thumbnail_folder的目标文件夹中:
import os
import sys
from PIL import Image
class Thumbnailer(object):
def __init__(self, src_folder=None):
self.src_folder = src_folder
self.ratio = .3
self.thumbnail_folder = "thumbnails"
def _create_thumbnails_folder(self):
thumb_path = os.path.join(self.src_folder, self.thumbnail_folder)
if not os.path.isdir(thumb_path):
os.makedirs(thumb_path)
def _build_thumb_path(self, image_path):
root = os.path.dirname(image_path)
name, ext = os.path.splitext(os.path.basename(image_path))
suffix = ".thumbnail"
return os.path.join(root, self.thumbnail_folder, name + suffix + ext)
def _load_files(self):
files = set()
for each in os.listdir(self.src_folder):
each = os.path.abspath(self.src_folder + '/' + each)
if os.path.isfile(each):
files.add(each)
return files
def _thumb_size(self, size):
return (int(size[0] * self.ratio), int(size[1] * self.ratio))
def create_thumbnails(self):
self._create_thumbnails_folder()
files = self._load_files()
for each in files:
print "Processing: " + each
try:
img = Image.open(each)
thumb_size = self._thumb_size(img.size)
resized = img.resize(thumb_size, Image.ANTIALIAS)
savepath = self._build_thumb_path(each)
resized.save(savepath)
except IOError as ex:
print "Error: " + str(ex)
if __name__ == "__main__":
# Usage:
# ch06_rec01_02_pil_thumbnails.py my_images
assert len(sys.argv) == 2
src_folder = sys.argv[1]
if not os.path.isdir(src_folder):
print "Error: Path '{0}' does not exits.".format((src_folder))
sys.exit(-1)
thumbs = Thumbnailer(src_folder)
# optionally set the name of theachumbnail folder relative to *src_folder*.
thumbs.thumbnail_folder = "THUMBS"
# define ratio to resize image to
# 0.1 means the original image will be resized to 10% of its size
thumbs.ratio = 0.1
# will create set of images in temporary folder
thumbs.create_thumbnails()
它是如何工作的...
对于给定的src_folder文件夹,我们加载该文件夹中的所有文件,并尝试使用Image.open()加载每个文件;这就是create_thumbnails()功能的逻辑。如果我们试图加载的文件不是图像,IOError将被抛出,它将打印此错误并跳到序列中的下一个 fil e。
如果我们想对加载什么文件有更多的控制,我们应该将_load_files()功能更改为只包括具有特定扩展名(文件类型)的文件:
for each in os.listdir(self.src_folder):
if os.path.isfile(each) and os.path.splitext(each) is in ('.jpg','.png'):
self._files.add(each)
这并不是万无一失的,因为文件扩展名没有定义文件类型,它只是帮助操作系统将默认程序附加到文件上,但它在大多数情况下都是有效的,并且比读取文件头来确定文件内容更简单(这仍然不能保证文件真的是前几个字节,比如说它是)。
还有更多...
有了 PIL,虽然用得不多,但我们可以很容易地将图像从一种格式转换成另一种格式。这可以通过两个简单的操作来实现:首先使用 open()打开一个源格式的图像,然后使用save()以另一种格式保存该图像。格式或者通过文件扩展名(.png或.jpeg隐式定义,或者通过传递给 save()函数的参数的格式显式定义。
用图像绘图
除了纯数据值之外,图像还可以用来突出可视化的优势。许多例子已经证明,通过使用符号图像,我们可以更深入地映射到观看者的心理模型中,从而帮助观看者更好地、更长时间地记住可视化。一种方法是将图像放在数据所在的位置,将值映射到它们所代表的内容。matplotlib 库能够提供这一功能,因此我们将演示如何做到这一点。
做好准备
使用故事《飞来的意大利面怪物的福音》中的虚构例子,作者将海盗的数量与海面温度联系起来。为了突出这种相关性,我们将显示海盗船的大小与代表测量海面温度的年份中海盗数量的值成比例。
我们将使用 Python matplotlib 库的功能,使用带有高级位置设置的图像和文本进行标注,以及箭头功能。
以下配方中所需的所有文件都可以在ch06文件夹的源代码库中找到。
怎么做...
以下示例显示了如何使用图像和文本向图表添加批注:
import matplotlib.pyplot as plt
from matplotlib._png import read_png
from matplotlib.offsetbox import TextArea, OffsetImage, \
AnnotationBbox
def load_data():
import csv
with open('pirates_temperature.csv', 'r') as f:
reader = csv.reader(f)
header = reader.next()
datarows = []
for row in reader:
datarows.append(row)
return header, datarows
def format_data(datarows):
years, temps, pirates = [], [], []
for each in datarows:
years.append(each[0])
temps.append(each[1])
pirates.append(each[2])
return years, temps, pirates
在我们定义了辅助函数之后,我们可以接近图形对象的构造并添加子场景。我们将在每年的年份集合中使用船的图像对这些进行标注,将图像缩放到适当的大小:
if __name__ == "__main__":
fig = plt.figure(figsize=(16,8))
ax = plt.subplot(111) # add sub-plot
header, datarows = load_data()
xlabel, ylabel = header[0], header[1]
years, temperature, pirates = format_data(datarows)
title = "Global Average Temperature vs. Number of Pirates"
plt.plot(years, temperature, lw=2)
plt.xlabel(xlabel)
plt.ylabel(ylabel)
# for every data point annotate with image and number
for x in xrange(len(years)):
# current data coordinate
xy = years[x], temperature[x]
# add image
ax.plot(xy[0], xy[1], "ok")
# load pirate image
pirate = read_png('tall-ship.png')
# zoom coefficient (move image with size)
zoomc = int(pirates[x]) * (1 / 90000.)
# create OffsetImage
imagebox = OffsetImage(pirate, zoom=zoomc)
# create anotation bbox with image and setup properties
ab = AnnotationBbox(imagebox, xy,
xybox=(-200.*zoomc, 200.*zoomc),
xycoords='data',
boxcoords="offset points",
pad=0.1,
arrowprops=dict(arrowstyle="->",
connectionstyle="angle,angleA=0,angleB=-30,rad=3")
)
ax.add_artist(ab)
# add text
no_pirates = TextArea(pirates[x], minimumdescent=False)
ab = AnnotationBbox(no_pirates, xy,
xybox=(50., -25.),
xycoords='data',
boxcoords="offset points",
pad=0.3,
arrowprops=dict(arrowstyle="->",
connectionstyle="angle,angleA=0,angleB=-30,rad=3")
)
ax.add_artist(ab)
plt.grid(1)
plt.xlim(1800, 2020)
plt.ylim(14, 16)
plt.title(title)
plt.show()
前面的代码应该给出如下的图:

它是如何工作的...
我们首先创建一个大小合适的图形,即 16 x 8。我们需要这个尺寸来适合我们想要显示的图像。现在,我们使用csv模块从文件中加载数据。实例化csv读取器对象,我们可以逐行迭代文件中的数据。注意第一行是多么特别,它是描述我们的列的标题。由于我们在 x 轴上绘制了年,在 y 轴上绘制了温度,我们看到:
xlabel, ylabel, _ = header
并使用以下行:
plt.xlabel(xlabel)
plt.ylabel(ylabel)
注
我们在这里使用了整洁的 Python 约定将头解包为三个变量,其中通过使用_作为变量名,我们表明我们对该变量的值不感兴趣。
我们将 load_data功能中的header和datarows列表返回给main呼叫者。
使用format_data()功能,我们读取列表中的每一项,并将每个单独的实体(年份、温度和盗版数量)添加到该实体的相关 ID 列表中。
x 轴显示年份,y 轴显示温度。海盗数量显示为海盗船的图像,为了增加精度,会显示数值。
我们使用标准的plot()函数绘制年/温度值,除了使线变宽一点(2 点)之外,没有添加任何东西。
然后,我们为每个测量值添加一幅图像,并说明给定年份的盗版数量。为此,我们在长度值范围内循环(range(len(years))),在每年/温度坐标上绘制一个黑点:
ax.plot(xy[0], xy[1], "ok")
使用read_png助手功能将船的图像从文件加载到合适的数组格式中:
pirate = read_png('tall-ship.png')
然后计算缩放系数(zoomc)以使我们能够根据当前(pirates[x])测量的盗版数量来缩放图像的大小。我们还使用相同的系数来沿着图定位图像。
然后,实际图像在OffsetImage内部实例化,即相对于其父级(AnnotationBbox)具有相对位置的图像容器。
AnnotationBbox是一个类似标注的类,但是它可以显示其他OffsetBox实例,而不是像Axes.annotate函数那样只显示文本。这允许我们在标注中加载图像或文本对象,并将其定位在离数据点特定距离的位置,以及使用箭头功能(arrowprops)来精确指向标注的数据点。
我们为AnnotateBbox构造函数提供了某些参数:
Imagebox:这一定是OffsetBox的一个实例(比如OffsetImage);它是标注框的内容xy:这是标注涉及的数据点坐标xybox:定义标注框的位置xycoords:定义xy使用什么协调系统(例如数据坐标)boxcoords:定义xybox使用什么协调系统(例如,偏离xy位置)pad:指定填充量arrowprops:这是用于绘制从标注边界框到数据点的箭头连接的属性字典
我们使用来自pirates列表的相同数据项,以稍微不同的相对位置向该图添加文本标注。第二个AnnotationBbox的大部分论点都是一样的——我们调整xybox和pad以将文本定位到行的对面。文本在TextArea类实例中,这类似于我们对图像所做的,但是文本time.TextArea和OffsetImage继承自同一个父类OffsetBox。
我们将这个TextArea实例中的文本设置为no_pirates,并将其放入我们的AnnotationBbox中。
显示带有图中其他图的图像
这个食谱将展示我们如何简单而有效地使用 Python matplotlib 库来处理图像通道和显示外部图像的每个通道直方图。
做好准备
我们已经提供了一些示例图像,但是代码已经准备好加载任何图像文件,前提是它得到 matplotlib 的imread函数的支持。
在本食谱中,我们将学习如何组合不同的 matplotlib 图,以实现简单图像查看器的功能,该查看器显示红色、绿色和蓝色通道的图像直方图。
怎么做...
为了展示如何构建一个图像直方图查看器,我们将实现一个名为ImageViewer的简单类,该类将包含帮助器方法,用于:
- 加载图像。
- 从图像矩阵中分离出 RGB 通道。
- 配置图形和轴(子场景)。
- 绘制通道直方图。
- 绘制图像。
下面的代码展示了如何构建一个图像直方图查看器:
import matplotlib.pyplot as plt
import matplotlib.image as mplimage
import matplotlib as mpl
import os
class ImageViewer(object):
def __init__(self, imfile):
self._load_image(imfile)
self._configure()
self.figure = plt.gcf()
t = "Image: {0}".format(os.path.basename(imfile))
self.figure.suptitle(t, fontsize=20)
self.shape = (3, 2)
def _configure(self):
mpl.rcParams['font.size'] = 10
mpl.rcParams['figure.autolayout'] = False
mpl.rcParams['figure.figsize'] = (9, 6)
mpl.rcParams['figure.subplot.top'] = .9
def _load_image(self, imfile):
self.im = mplimage.imread(imfile)
@staticmethod
def _get_chno(ch):
chmap = {'R': 0, 'G': 1, 'B': 2}
return chmap.get(ch, -1)
def show_channel(self, ch):
bins = 256
ec = 'none'
chno = self._get_chno(ch)
loc = (chno, 1)
ax = plt.subplot2grid(self.shape, loc)
ax.hist(self.im[:, :, chno].flatten(), bins, color=ch, ec=ec,\
label=ch, alpha=.7)
ax.set_xlim(0, 255)
plt.setp(ax.get_xticklabels(), visible=True)
plt.setp(ax.get_yticklabels(), visible=False)
plt.setp(ax.get_xticklines(), visible=True)
plt.setp(ax.get_yticklines(), visible=False)
plt.legend()
plt.grid(True, axis='y')
return ax
def show(self):
loc = (0, 0)
axim = plt.subplot2grid(self.shape, loc, rowspan=3)
axim.imshow(self.im)
plt.setp(axim.get_xticklabels(), visible=False)
plt.setp(axim.get_yticklabels(), visible=False)
plt.setp(axim.get_xticklines(), visible=False)
plt.setp(axim.get_yticklines(), visible=False)
axr = self.show_channel('R')
axg = self.show_channel('G')
axb = self.show_channel('B')
plt.show()
if __name__ == '__main__':
im = 'img/yellow_flowers.jpg'
try:
iv = ImageViewer(im)
iv.show()
except Exception as ex:
print ex
它是如何工作的...
从代码的末尾,我们看到硬编码的文件名。这些可以通过从命令行加载参数并使用sys.argv序列将给定参数解析到im变量中来交换。
我们用提供的图像文件路径实例化ImageViewer类。在对象实例化过程中,我们尝试将图像文件加载到数组中,通过rcParams字典配置图形,设置图形大小和标题,并定义要在对象方法中使用的对象字段(self.shape)。
这里的主要方法是 show(),它为图形创建布局,并将图像数组加载到主(左列)子图中。我们隐藏任何刻度和刻度标签,因为这是实际的图像,我们不需要使用刻度。
然后,我们为每个红色、绿色和蓝色通道调用私有方法show_channel()。该方法还创建了新的子图轴,这次是在右侧列,每个轴都在单独的行中。我们在单独的子图中绘制每个通道的直方图。
我们还设置了一个小图,去除不必要的 x 记号,并添加一个图例,以防我们想要在非彩色环境下打印此图。因此,即使在这些环境中,我们也可以辨别通道表示。
运行此代码后,我们将获得以下截图:

还有更多...
直方图绘图类型的使用只是图像查看器示例的一种选择。我们可以使用 matplotlib 支持的任何绘图类型。另一个真实的例子是绘制脑电图或类似的医疗记录,其中我们希望将切片显示为图像,将脑电图的时间序列记录为线图,以及关于所显示数据的附加元信息,这些信息可能会进入matplotlib.text.Text artists。
matplotlib 具有与用户图形用户界面事件交互的能力,它还允许我们实现交互,如果我们只手动放大一个图,我们将希望放大所有图。这是另一种用法,我们希望显示图像并放大它,同时也放大当前活动图形中的其他显示图。一个想法是使用motion_notify_event来调用一个函数,该函数将更新当前图形中所有轴(子图)的 x 和 y 限制。
使用底图在地图上绘制数据
最好的地理空间可视化可能是通过将数据覆盖在地图上来实现的。无论是整个地球、一个大陆、一个州,还是甚至天空,对于一个观察者来说,理解它所显示的数据和地理之间的关系是最简单的方法之一。
在本食谱中,我们将学习如何使用 matplotlib 的Basemap工具包在地图上投影数据。
做好准备
由于我们已经熟悉 matplotlib 作为我们的绘图引擎,我们可以将其扩展到 matplotlib 的能力,以使用其他工具包,例如Basemap映射工具包。
Basemap本身不做任何的图谋。它只是将给定的地理空间坐标转换为地图投影,并将该数据提供给 matplotlib 进行绘图。
首先,我们需要安装Basemap工具包。如果您正在使用三元乙丙橡胶,则已经安装了Basemap。如果你在 Linux 上,最好使用原生包管理器来安装包含Basemap的包。比如在 Ubuntu 上,这个包叫做python-mpltoolkits.basemap,可以使用标准的包管理器进行安装:
$ sudo apt-get install python-mpltoolkits.basemap
在 Mac OS X 上,建议使用 EPD,尽管使用流行的软件包管理器(如 Homebrew、Fink 和 pip)安装也是可能的。
怎么做...
下面是一个如何使用Basemap工具包在特定区域内绘制简单墨卡托投影的示例,该区域由长的 lat 坐标对指定:
- 我们实例化
Basemap定义要使用的投影(merc为墨卡托)。 - 我们为地图的左下角和右上角定义(在同一个
Basemap构造函数中)经度和纬度。 - 我们设置
Basemap实例地图,来绘制海岸线和国家。 - 我们设置
Basemap实例地图来填充大陆并绘制地图边界。 - 我们指示
Basemap实例图绘制经线和纬线。
下面的代码展示了如何使用Basemap工具箱绘制一个简单的墨卡托投影:
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
import numpy as np
map = Basemap(projection='merc',
resolution = 'h',
area_thresh = 0.1,
llcrnrlon=-126.619875, llcrnrlat=31.354158,
urcrnrlon=-59.647219, urcrnrlat=47.517613)
map.drawcoastlines()
map.drawcountries()
map.fillcontinents(color='coral', lake_color='aqua')
map.drawmapboundary(fill_color='aqua')
map.drawmeridians(np.arange(0, 360, 30))
map.drawparallels(np.arange(-90, 90, 30))
plt.show()
这将给我们的地球一个可识别的部分:

既然我们知道如何绘制地图,我们就需要知道如何在地图上绘制数据。如果我们回想一下Basemap是当前地图投影中经度和纬度对的大代码转换器,我们会意识到我们所需要的是一个包含 long/lat 的数据集,在用 matplotlib 绘制之前,我们将其传递给Basemap进行投影。我们使用cities.shp和cities.shx文件加载美国城市的坐标,并将它们投影到地图上。该文件位于代码库的ch06文件夹中。下面是如何实现这一点的示例:
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
import numpy as np
map = Basemap(projection='merc',
resolution = 'h',
area_thresh = 100,
llcrnrlon=-126.619875, llcrnrlat=25,
urcrnrlon=-59.647219, urcrnrlat=55)
shapeinfo = map.readshapefile('cities','cities')
x, y = zip(*map.cities)
# build a list of US cities
city_names = []
for each in map.cities_info:
if each['COUNTRY'] != 'US':
city_names.append("")
else:
city_names.append(each['NAME'])
map.drawcoastlines()
map.drawcountries()
map.fillcontinents(color='coral', lake_color='aqua')
map.drawmapboundary(fill_color='aqua')
map.drawmeridians(np.arange(0, 360, 30))
map.drawparallels(np.arange(-90, 90, 30))
# draw city markers
map.scatter(x,y,25, marker='o',zorder=10)
# plot labels at City coords.
for city_label, city_x, city_y in zip(city_names, x, y):
plt.text(city_x, city_y, city_label)
plt.title('Cities in USA')
plt.show()
它是如何工作的...
Basemap使用的基础包括导入主模块和实例化具有所需属性的Basemap类。在实例化过程中,我们必须指定要使用的投影和我们要处理的地球部分。
在绘制地图并使用matplotlib.pyplot.show()显示图形窗口之前,可以应用附加的配置。
Basemap支持十几个(准确地说是 32 个)不同的投影。大多数都是面向狭义使用的,但有些更通用,适用于大多数常见的地图可视化。
型式
通过询问Basemap模块本身,我们可以很容易地看到有哪些预测可用:
In [5]: import mpl_toolkits.basemap
In [6]: print mpl_toolkits.basemap.supported_projections
mbtfpq McBryde-Thomas Flat-Polar Quartic
aeqd Azimuthal Equidistant
sinu Sinusoidal
poly Polyconic
omerc Oblique Mercator
gnom Gnomonic
moll Mollweide
lcc Lambert Conformal
tmerc Transverse Mercator
nplaea North-Polar Lambert Azimuthal
gall Gall Stereographic Cylindrical
npaeqd North-Polar Azimuthal Equidistant
mill Miller Cylindrical
merc Mercator
stere Stereographic
eqdc Equidistant Conic
cyl Cylindrical Equidistant
npstere North-Polar Stereographic
spstere South-Polar Stereographic
hammer Hammer
geos Geostationary
nsper Near-Sided Perspective
eck4 Eckert IV
aea Albers Equal Area
kav7 Kavrayskiy VII
spaeqd South-Polar Azimuthal Equidistant
ortho Orthographic
cass Cassini-Soldner
vandg van der Grinten
laea Lambert Azimuthal Equal Area
splaea South-Polar Lambert Azimuthal
robin Robinson
通常,我们将绘制整个投影,如果没有指定,则使用一些合理的默认值。
为了放大地图的特定区域,我们将指定您想要显示的区域的左下角和右上角的纬度和经度。对于这个例子,我们将使用墨卡托投影。
型式
这里我们看到参数名称是如何被缩短的描述:
llcrnrlon:这是左下角经度llcrnrlat:这是左下角纬度urcrnrlon:这是右上角经度urcrnrlat:这是右上角纬度
还有更多...
我们只是触及了Basemap工具包的功能表面,更多的例子可以在http://matplotlib.org/basemap/users/examples.html的官方文档中找到。
官方Basemap文档中示例中使用的大部分数据位于远程服务器上,并且采用特定的格式。为了有效地获取该数据,使用NetCDF数据格式。NetCDF 是一种考虑到网络效率而设计的通用数据格式。它允许程序获取所需的数据,即使整个数据集非常大,这使得使用这种格式非常实用。我们不必每次想使用大型数据集时以及每次数据集发生变化时都在本地下载和存储它们。
使用谷歌地图 API 在地图上绘制数据
在这个食谱中,我们将脱离桌面环境,展示如何为网络输出。虽然,web 前端的主要语言不是 Python,而是 HTML、CSS 和 JavaScript,但我们仍然可以使用 Python 进行繁重的工作:获取数据,处理数据,执行密集的计算,并以适合 web 输出的格式呈现数据,即创建具有所需 JavaScript 版本的 HTML 页面来呈现我们的可视化。
做好准备
我们将使用 Python 的 谷歌数据可视化库来帮助我们为前端界面准备数据,在这里我们将使用另一个谷歌可视化应用编程接口 来以期望的可视化方式渲染数据,即地图和表格。
在开始之前,我们需要安装google-visualization-python模块。从https://code . Google . com/p/Google-visualization-python/downloads/detail 下载最新稳定版?name = gviz _ API _ py-1 . 8 . 2 . tar . gz&can = 2&q =,打开档案并安装模块。以下操作演示了如何做到这一点:
$ tar xfv gviz_api_py-1.8.2.tar.gz
$ cd gviz_api_py
$ sudo python ./setup.py install
在 Windows 和 Mac 上,OS X 使用适当的软件打开tar.gz档案,而其他步骤应该保持不变。请注意,我们必须成为超级用户(即获得管理员权限)才能在我们的系统上安装该模块。
如果你不想污染你的操作系统包,一个更好的选择是创建一个 virtualenv 环境来安装这个配方的包。我们在第 1 章、准备你的工作环境中解释了如何应对病毒变异环境。
对于前端库,我们不需要安装任何东西,因为该库将直接从谷歌服务器从网页加载。
我们需要为这个食谱主动访问互联网,因为它的输出将是一个网页,当在网络浏览器中打开时,将直接从远程服务器拉 JavaScript 库。
在这个食谱中,我们将学习如何使用谷歌数据可视化库的 Python 和 JavaScript 来组合它们来创建网络可视化。
怎么做...
以下示例显示了如何使用谷歌地理地图和表格可视化在世界地图投影上可视化每个国家的可支配月工资中位数,使用 Python 和gdata_viz模块从 CSV 文件加载数据。我们将:
**1. 实现一个函数作为模板生成器。
2. 使用csv模块从本地 CSV 文件加载数据。
3. 使用DataTable描述数据,LoadData从 Python 字典加载数据。
4. 将输出呈现到网页。
这可以通过以下代码来实现:
import csv
import gviz_api
def get_page_template():
page_template = """
<html>
<script src="https://www.google.com/jsapi" type="text/javascript"></script>
<script>
google.load('visualization', '1', {packages:['geochart', 'table']});
google.setOnLoadCallback(drawMap);
function drawMap() {
var json_data = new google.visualization.DataTable(%s, 0.6);
var options = {colorAxis: {colors: ['#eee', 'green']}};
var mymap = new google.visualization.GeoChart(
document.getElementById('map_div'));
mymap.draw(json_data, options);
var mytable = new google.visualization.Table(
document.getElementById('table_div'));
mytable.draw(json_data, {showRowNumber: true})
}
</script>
<body>
<H1>Median Monthly Disposable Salary World Countries</H1>
<div id="map_div"></div>
<hr />
<div id="table_div"></div>
<div id="source">
<hr />
<small>
Source:
<a href="http://www.numbeo.com/cost-of-living/prices_by_country.jsp?displayCurrency=EUR&itemId=105">
http://www.numbeo.com/cost-of-living/prices_by_country.jsp?displayCurrency=EUR&itemId=105
</a>
</small>
</div>
</body>
</html>
"""
return page_template
def main():
# Load data from CVS file
afile = "median-dpi-countries.csv"
datarows = []
with open(afile, 'r') as f:
reader = csv.reader(f)
reader.next() # skip header
for row in reader:
datarows.append(row)
# Describe data
description = {"country": ("string", "Country"),
"dpi": ("number", "EUR"), }
# Build list of dictionaries from loaded data
data = []
for each in datarows:
data.append({"country": each[0],
"dpi": (float(each[1]), each[1])})
# Instantiate DataTable with structure defined in 'description'
data_table = gviz_api.DataTable(description)
# Load it into gviz_api.DataTable
data_table.LoadData(data)
# Creating a JSon string
json = data_table.ToJSon(columns_order=("country", "dpi"),
order_by="country", )
# Put JSON string into the template
# and save to output.html
with open('output.html', 'w') as out:
out.write(get_page_template() % (json,))
if __name__ == '__main__':
main()
这个会产生output.html文件,我们可以在自己喜欢的网页浏览器中打开。页面应该如下图所示:

它是如何工作的...
这里的主要切入点是我们的main()功能。首先我们使用csv模块加载我们的数据。本次数据来源于公共网站www.numbeo.com,数据以 CSV 格式放置。最终文件可在ch06文件夹的本章存储库中的获得。为了能够使用谷歌数据可视化库,我们需要向它描述数据。我们使用 Python 字典来描述数据,其中定义了列的标识、数据类型和可选标签。在以下示例中,数据是在此约束中定义的:
{"name": ("data_type", "Label")}:
description = {"country": ("string", "Country"),
"dpi": ("number", "EUR"), }
然后,我们需要将加载的 CSV 行调整为这种格式。我们将在data变量中建立一个字典列表。
现在我们有了所有的东西来用所描述的结构用gviz_data.DataTable实例化我们的data_table。然后我们将数据加载到其中,并以 JSON 格式输出到我们的page_template。
get_page_template()函数包含这个等式的另一部分。它包含一个客户端(前端)代码来生成一个 HTML 网页和一个 JavaScript 代码来从谷歌服务器加载谷歌数据可视化库。加载谷歌的 JavaScript 应用编程接口的行是:
<script src="https://www.google.com/jsapi" type="text/javascript"></script>
之后,跟随另一对包含附加设置的<script>...</script>标签。首先,我们加载谷歌数据可视化库和所需的包——地理图表和表格:
google.load('visualization', '1', {packages:['geochart', 'table']});
然后我们设置一个函数,当页面被加载时会被调用。这个事件在网络世界注册为onLoad,所以回拨是通过setOnLoadCallback功能设置的:
google.setOnLoadCallback(drawMap);
这定义了当加载一个页面时,google实例将调用我们定义的自定义函数 drawMap()。drawMap函数将一个 JSON 字符串加载到DataTable实例的 JavaScript 版本中:
var json_data = new google.visualization.DataTable(%s, 0.6);
接下来,我们在一个 HTML 元素中创建一个名为map_div的geochart实例:
var mymap = new google.visualization.GeoChart(
document.getElementById('map_div'));
使用json_data和提供的自定义options绘制地图:
mymap.draw(json_data, options);
同样,谷歌的 JavaScript 表呈现在地图下方:
var mytable = new google.visualization.Table(
document.getElementById('table_div'));
mytable.draw(json_data, {showRowNumber: true})
我们将这个输出保存为一个我们可以在浏览器中打开的 HTML 文件。这个对于 web 服务的动态呈现不是很有用。对此有一个更好的选择——直接从 Python 输出 HTTP 响应,从而构建一个后台服务,用客户机可以加载和呈现的 JSON 响应客户机的 web 请求。
注
如果您想了解更多关于阅读 HTTP 响应的信息,请在HTTP://en . Wikipedia . org/wiki/Hypertext _ Transfer _ Protocol # Response _ message上阅读更多关于 HTTP 协议和响应消息的信息。
我们通过将ToJson()调用替换为具有相同签名的ToJSonResponse()来实现。这个调用将以一个包含有效负载的正确的 HTTP 响应来响应——我们的 JSON 化的data_table准备好被我们的 JavaScript 客户端使用。
还有更多...
当然,这只是我们如何将 Python 作为后端语言结合起来的一个例子,我们坐在服务器上,进行数据提取和处理,而前端则留给通用的 HTML/JavaScript/CSS 语言集。这使我们能够向广大受众提供可视化的交互式动态界面,而不需要他们安装任何东西(除了网络浏览器,但通常安装在计算机或智能手机上)。话虽如此,我们必须注意到,这些输出的质量并没有 matplotlib 高,而 matplotlib 的实力在于高质量的输出。
为了更好地使用网络(和 Python),你必须了解更多的网络技术和所使用的语言。这本书没有涵盖这些主题,但是深入探讨了如何使用众所周知的第三方库来实现一个可能的解决方案,该库能够以尽可能少的网络编码产生令人愉悦的网络输出。
更多文档可在的谷歌开发者门户网站上获得。
生成验证码图片
虽然这不是我们通常所说的严格意义上的数据可视化,但是使用 Python 生成图像的能力在很多情况下都会派上用场,而就是其中之一。
在这个食谱中,我们将涵盖随机图像的生成,以区分人类和计算机——验证码图像。
做好准备
验证码代表完全自动化的公共图灵测试来区分计算机和人类,由卡内基梅隆大学注册商标。该测试用于挑战计算机程序(通常称为机器人)自动填写各种主要针对人类且不应自动化的网页表单。常见的例子有注册表单、登录表单、调查等。
验证码本身可以采取各种形式,但最常见的形式包括一个挑战,即人类应该读取带有扭曲字符和数字的图像,并在相关的响应字段中键入结果。
在这个食谱中,我们将学习如何利用 Python 的图像库来生成图像、渲染线和点,以及渲染文本。
怎么做...
我们将通过执行以下步骤来展示创建个人简单验证码生成器的过程:
- 定义大小、文本、字体大小、背景颜色和验证码长度。
- 从英语字母表中随机挑选字符。
- 使用定义的字体和颜色在图像上绘制。
- 以线条和弧线的形式添加一些噪点。
- 将图像对象和验证码一起返回给呼叫者。
- 向用户显示生成的图像。
下面的代码展示了如何创建一个个人和简单的验证码生成器:
from PIL import Image, ImageDraw, ImageFont
import random
import string
class SimpleCaptchaException(Exception):
pass
class SimpleCaptcha(object):
def __init__(self, length=5, size=(200, 100), fontsize=36,
random_text=None, random_bgcolor=None):
self.size = size
self.text = "CAPTCHA"
self.fontsize = fontsize
self.bgcolor = 255
self.length = length
self.image = None # current captcha image
if random_text:
self.text = self._random_text()
if not self.text:
raise SimpleCaptchaException("Field text must not be empty.")
if not self.size:
raise SimpleCaptchaException("Size must not be empty.")
if not self.fontsize:
raise SimpleCaptchaException("Font size must be defined.")
if random_bgcolor:
self.bgcolor = self._random_color()
def _center_coords(self, draw, font):
width, height = draw.textsize(self.text, font)
xy = (self.size[0] - width) / 2., (self.size[1] - height) / 2.
return xy
def _add_noise_dots(self, draw):
size = self.image.size
for _ in range(int(size[0] * size[1] * 0.1)):
draw.point((random.randint(0, size[0]),
random.randint(0, size[1])),
fill="white")
return draw
def _add_noise_lines(self, draw):
size = self.image.size
for _ in range(8):
width = random.randint(1, 2)
start = (0, random.randint(0, size[1] - 1))
end = (size[0], random.randint(0,size[1]-1))
draw.line([start, end], fill="white", width=width)
for _ in range(8):
start = (-50, -50)
end = (size[0] + 10, random.randint(0, size[1]+10))
draw.arc(start + end, 0, 360, fill="white")
return draw
def get_captcha(self, size=None, text=None, bgcolor=None):
if text is not None:
self.text = text
if size is not None:
self.size = size
if bgcolor is not None:
self.bgcolor = bgcolor
self.image = Image.new('RGB', self.size, self.bgcolor)
# Note that the font file must be present
# or point to your OS's system font
# Ex. on Mac the path should be '/Library/Fonts/Tahoma.ttf'
font = ImageFont.truetype('fonts/Vera.ttf', self.fontsize)
draw = ImageDraw.Draw(self.image)
xy = self._center_coords(draw, font)
draw.text(xy=xy, text=self.text, font=font)
# Add some dot noise
draw = self._add_noise_dots(draw)
# Add some random lines
draw = self._add_noise_lines(draw)
self.image.show()
return self.image, self.text
def _random_text(self):
letters = string.ascii_lowercase + string.ascii_uppercase
random_text = ""
for _ in range(self.length):
random_text += random.choice(letters)
return random_text
def _random_color(self):
r = random.randint(0, 255)
g = random.randint(0, 255)
b = random.randint(0, 255)
return (r, g, b)
if __name__ == "__main__":
sc = SimpleCaptcha(length=7, fontsize=36, random_text=True, random_bgcolor=True)
sc.get_captcha()
这将生成类似于以下内容的图像:

它是如何工作的...
这个例子给出了一个如何使用 Python 的图像库生成预定义图像的过程,以创建一个简单而有效的验证码生成器。
我们将功能打包成一个类SimpleCaptcha,因为它给了我们未来发展的安全空间。我们还创建了一个定制的SimpleCaptchaException来适应未来的异常层次结构。
注
如果您编写的不仅仅是琐碎、快速和肮脏的脚本,那么开始为您的域编写和设计自定义异常层次结构总是好的,而不是使用通用 Python 的标准异常。在软件的可读性和维护上,你会受益匪浅。
从代码清单末尾的主要部分开始阅读,在这里我们实例化一个类,给出我们未来图像的设置作为构造函数的参数。接下来,我们在sc对象上调用get_captcha方法。对于这个方法的目的,get_captcha显示图像对象作为结果,但是我们也将图像对象返回给这个方法的潜在调用者,所以它可以利用这个结果。用法可能会有所不同,调用者可以将图像保存在文件中,或者如果这是一个网络应用,则将图像流和书面质询返回给请求该验证码的客户端。
需要注意的重要一点是,为了完成验证码测试的挑战-响应过程,我们必须将图像上生成的验证码字符串作为文本返回,以便调用方可以将用户的响应与期望值进行比较。
get_captcha方法首先验证输入参数,以便在用户提供自定义值时覆盖类的默认值。之后,通过Image.new实例化新的图像对象。这个对象保存在self.image里,我们用它来画和写文字。将文本写入图像后,我们添加了随机放置的点和线的噪声,以及一些弧段。
这些任务通过_add_noise_points和_add_noise_lines方法执行。第一个循环几次,并在图像上的随机位置添加一个点,不要太靠近图像的边缘,后一个从图像的左侧到图像的右侧绘制线条。
还有更多...
我们使用一些关于它的使用的假设来构造这个类。我们假设用户只想接受我们的默认设置(也就是说,随机背景色上随机的七个字符)并从中接收结果。这就是在构造函数中放置辅助函数来设置随机文本和随机背景颜色的原因。如果最常见和最有效的用法是总是重写配置,那么我们希望从构造函数中移除这些操作,并将它们放在单独的调用中。
例如,可能用户希望总是使用英语单词作为验证码挑战。如果是这种情况,我们希望能够只调用一个方法来为我们提供这样的结果。这个方法可以是get_english_captcha,通过这个构造函数的随机逻辑,我们可以构造这个方法,从提供的英语字典中选择随机的单词。在 Unix 系统上,在/usr/share/dict/words中有一个通用的英语词典,我们可以用来做这个:
def get_english_captcha(self):
words = '/usr/share/dict/words'
with open(words, 'r') as wf:
words = wf.readlines()
aword = random.choice(words)
aword = aword.strip() # remove newline and spaces
return self.get_captcha(text=aword)
总的来说,验证码生成的例子不是生产质量,如果不增加更多的保护和随机性,比如字母轮换,就不应该使用。
如果您需要保护您的 web 表单免受僵尸工具的攻击,那么您应该重用第三方 Python 模块和库。甚至有专门为现有 web 框架构建的模块。
还有reCAPTCHA(http://www.google.com/recaptcha)等事件 web 服务,有已经验证的 Python 模块reCAPTCHA-client(https://pypi.python.org/pypi/recaptcha-client)可以注册使用。它不需要任何图像库,因为图像是直接从 reCAPTCHA web 服务中提取的,但它有其他依赖项,如pycrypto。通过使用这个网络服务和图书馆,您还可以帮助使用来自谷歌图书项目或旧版本《纽约时报》的光学字符识别(OCR)扫描的书籍。在 reCAPTCHA 网站上阅读更多内容。**
七、使用正确的绘图来理解数据
在这一章中,我们将涵盖以下食谱:
- 理解对数图
- 理解光谱图
- 创建主干图
- 绘制矢量流的流线
- 使用彩色地图
- 使用散点图和直方图
- 绘制两个变量之间的互相关
- 自相关的重要性
简介
在这一章中,我们将更加专注于理解我们想用我们呈现的数据说什么,以及如何有效地说出来。我们将展示一些新的技术和绘图,但所有这些都将通过理解我们想要传达给用户的信息来强调。让我们问一个问题,“为什么我们要在这种状态下呈现信息?”。这是在数据探索阶段应该问的最重要的问题。如果我们错过了理解数据并以某种方式呈现数据的机会,那么观众肯定无法正确理解数据。
理解对数图
更多时候不是,看日报和类似的文章,一就能找到被媒体机构用来歪曲事实的图表。一个常见的例子是使用线性标度来创建所谓的恐慌图,其中一个不断增长的值跟随很长一段时间(年),并且起始值比最近的值小几个数量级。当这些值被正确地可视化时,将会(并且通常应该)产生线性或几乎线性的图表,从它们所展示的文章中消除一些恐慌。
做好准备
对于对数刻度,连续值的比率是恒定的。当我们试图读取日志图时,这一点很重要。对于线性(算术)标度,常数是连续值之间的距离。换句话说,对数图在数量级上具有恒定的距离。我们将在下面的图中看到这一点。接下来解释用于产生这个数字的代码。
作为一般经验法则,对数刻度应在以下情况下使用:
- 当呈现的数据具有跨越几个数量级的值时
- 当呈现的数据向大值倾斜时(某些点比其余数据大得多)
- 当你想显示变化率(增长率),而不是变化值时
不要盲目遵循这些规则;它们更像是暗示,而不是规则。对于手头的数据和项目或客户向您提出的要求,始终使用您自己的判断。
根据数据范围,应使用不同的日志库。日志的标准基数是 10,但是如果数据范围较小,基数为 2 会更有用,因为它会在较小的范围内显示更多的分辨率。
如果我们有适合在对数标度上显示的数据范围,我们会注意到,以前过于接近而无法判断任何差异的值现在相距甚远。这使我们能够比以线性比例呈现数据更容易地阅读图表。
在收集长期时间序列数据的增长率图表中,我们希望看到的不是在某个时间点测量的绝对值,而是时间上的增长。我们仍将获得绝对值信息,但该信息的优先级较低。
此外,如果数据分布具有正偏斜,例如薪水,取值(薪水)的对数将有助于我们将数据拟合到模型中,因为对数变换将给出更正态的数据分布。
怎么做...
我们将用一个示例代码来举例说明这一点,该代码使用不同的比例(线性和对数)在两个不同的图上显示相同的两个数据集(一个是线性的,一个是对数的)。
我们将借助这些步骤后提到的代码来执行以下步骤:
- 生成两个简单的数据集:y-指数/对数性质和 z-线性。
- 创建一个包含四个子绘图网格的图形。
- 创建两个包含 y 数据集的子图:一个是对数标度,一个是线性标度。
- 创建另外两个包含 z 数据集的子图,一个是对数的,另一个是线性的。
以下是代码:
from matplotlib import pyplot as plt
import numpy as np
x = np.linspace(1, 10)
y = [10 ** el for el in x]
z = [2 * el for el in x]
fig = plt.figure(figsize=(10, 8))
ax1 = fig.add_subplot(2, 2, 1)
ax1.plot(x, y, color='blue')
ax1.set_yscale('log')
ax1.set_title(r'Logarithmic plot of $ {10}^{x} $ ')
ax1.set_ylabel(r'$ {y} = {10}^{x} $')
plt.grid(b=True, which='both', axis='both')
ax2 = fig.add_subplot(2, 2, 2)
ax2.plot(x, y, color='red')
ax2.set_yscale('linear')
ax2.set_title(r'Linear plot of $ {10}^{x} $ ')
ax2.set_ylabel(r'$ {y} = {10}^{x} $')
plt.grid(b=True, which='both', axis='both')
ax3 = fig.add_subplot(2, 2, 3)
ax3.plot(x, z, color='green')
ax3.set_yscale('log')
ax3.set_title(r'Logarithmic plot of $ {2}*{x} $ ')
ax3.set_ylabel(r'$ {y} = {2}*{x} $')
plt.grid(b=True, which='both', axis='both')
ax4 = fig.add_subplot(2, 2, 4)
ax4.plot(x, z, color='magenta')
ax4.set_yscale('linear')
ax4.set_title(r'Linear plot of $ {2}*{x} $ ')
ax4.set_ylabel(r'$ {y} = {2}*{x} $')
plt.grid(b=True, which='both', axis='both')
plt.show()
该代码将产生以下输出:

它是如何工作的...
我们生成一些样本数据和两个因变量:y 和 z。变量 y 表示为 x(数据)的指数函数,变量 z 是 x 的简单线性函数。这有助于我们说明线性和指数图表的不同外观。
然后我们创建一个由四个子图组成的网格,其中顶行子图是数据(x,y)对,底行是数据(x,z)对。
从左侧看,这些列在 y 轴上具有对数刻度,而从右侧看,这些列处于线性刻度。我们使用set_yscale('log')为每个轴分别设置这个。
对于每个子图,我们设置标题和标签,其中标签也描述了绘制的功能。
通过plt.grid(b=True, which='both', axis='both'),我们为轴和大刻度与小刻度打开网格。
我们观察线性函数如何在线性图上是直线,而对数函数如何在线性图上是直线。
理解光谱图
频谱图是一种随时间变化的频谱表示,显示了信号的频谱密度如何随时间变化。
频谱图以视觉方式表示声音或其他信号的频谱。它被用于各种科学领域,从声音指纹如语音识别,到雷达工程和地震学。
通常一个谱图布局是这样的:x 轴代表时间,y 轴代表频率,第三维度是一个频率-时间对的幅度,用颜色编码。这是三维数据;因此,我们也可以创建 3D 图,其中强度表示为 z 轴上的高度。3D 图表的问题在于人类不善于理解和比较它们。此外,它们往往比 2D 图表占据更多的空间。
做好准备
对于严肃的信号处理,我们将深入到低层次的细节,以便能够检测模式并自动识别某些细节;但是对于这个数据可视化食谱,我们将利用几个众所周知的 Python 库来读取音频文件,对其进行采样,并绘制声谱图。
为了读取 WAV 文件来可视化声音,我们需要做一些准备工作。我们需要安装libsndfile1系统库来读写音频文件。这是通过您最喜欢的包管理器完成的。对于 Ubuntu,使用:
$ sudo apt-get install libsndfile1-dev
安装dev包很重要,里面包含头文件,所以pip可以构建scikits.audiolab模块。
我们还可以安装libasound和 ALSA ( 高级 Linux 声音架构)头文件到避免运行时警告。这是可选的,因为我们不会使用 ALSA 图书馆提供的功能。对于 Ubuntu Linux,发出以下命令:
$ sudo apt-get install libasound2-dev
要安装scikits.audiolab,我们将使用它来读取 WAV 文件,我们将使用pip:
$ pip install scikits.audiolab
注
始终记得进入当前项目的虚拟环境,因为您不想弄脏系统库。
怎么做...
对于这个食谱,我们将使用预先录制的声音文件test.wav,它可以在本书的文件库中找到。但是我们也可以生成一个样本,稍后我们会尝试。
在本例中,我们按顺序执行以下步骤:
-
读取包含录音样本的 WAV 文件。
-
使用
NFFT定义傅里叶变换所用窗口的长度。 -
Define the overlapping data points using
noverlapwhile sampling.import os from math import floor, log from scikits.audiolab import Sndfile import numpy as np from matplotlib import pyplot as plt # Load the sound file in Sndfile instance soundfile = Sndfile("test.wav") # define start/stop seconds and compute start/stop frames start_sec = 0 stop_sec = 5 start_frame = start_sec * soundfile.samplerate stop_frame = stop_sec * soundfile.samplerate # go to the start frame of the sound object soundfile.seek(start_frame) # read number of frames from start to stop delta_frames = stop_frame - start_frame sample = soundfile.read_frames(delta_frames) map = 'CMRmap' fig = plt.figure(figsize=(10, 6), ) ax = fig.add_subplot(111) # define number of data points for FT NFFT = 128 # define number of data points to overlap for each block noverlap = 65 pxx, freq, t, cax = ax.specgram(sample, Fs=soundfile.samplerate, NFFT=NFFT, noverlap=noverlap, cmap=plt.get_cmap(map)) plt.colorbar(cax) plt.xlabel("Times [sec]") plt.ylabel("Frequency [Hz]") plt.show()该生成以下声谱图,每个音符都有类似白色的可见痕迹:
![How to do it...]()
注
NFFT定义每个块中用于计算离散傅里叶变换的数据点数。最有效的计算是当NFFT是 2 的幂的值时。窗口可以重叠,重叠(即重复)的数据点数量由noverlap参数定义。
它是如何工作的...
我们需要先加载一个声音文件。为此,我们使用scikits.audiolab.SndFile方法并为其提供一个文件名。这将实例化一个声音对象,然后我们可以在其上查询数据并调用函数。
为了读取声谱图所需的数据,我们需要从声音对象中读取所需的数据帧。这是由read_frames()完成的,它接受开始和结束帧。我们通过将采样率乘以我们想要可视化的时间点(start、end)来计算帧数。
还有更多...
如果找不到音频(波),可以轻松生成一个。以下是如何生成它:
import numpy
def _get_mask(t, t1, t2, lvl_pos, lvl_neg):
if t1 >= t2:
raise ValueError("t1 must be less than t2")
return numpy.where(numpy.logical_and(t > t1, t < t2), lvl_pos, lvl_neg)
def generate_signal(t):
sin1 = numpy.sin(2 * numpy.pi * 100 * t)
sin2 = 2 * numpy.sin(2 * numpy.pi * 200 * t)
# add interval of high pitched signal
sin2 = sin2 * _get_mask(t, 2, 5, 1.0, 0.0)
noise = 0.02 * numpy.random.randn(len(t))
final_signal = sin1 + sin2 + noise
return final_signal
if __name__ == '__main__':
step = 0.001
sampling_freq=1000
t = numpy.arange(0.0, 20.0, step)
y = generate_signal(t)
# we can visualize this now
# in time
ax1 = plt.subplot(211)
plt.plot(t, y)
# and in frequency
plt.subplot(212)
plt.specgram(y, NFFT=1024, noverlap=900,
Fs=sampling_freq, cmap=plt.cm.gist_heat)
plt.show()
这会给你下面的信号,上面的子图代表我们生成的信号。这里, x 轴代表时间,y 轴代表信号的幅度。底部子图表示频域中的相同信号。这里,x 轴表示时间,如顶部子图所示(我们通过选择采样率来匹配时间),y 轴表示信号的频率。

创建主干图
二维茎图将数据显示为从基线沿 x 轴延伸的线。一个圆(默认)或其他标记,其 y 轴代表数据值,终止每个茎。
在这个食谱中,我们将讨论如何创建一个主干图。
不要将茎图与茎叶图混淆,茎叶图是一种通过将值的最后一个重要数字分离为叶,将较高阶的值分离为茎来表示数据的方法。

做好准备
对于这种图,我们希望使用一系列离散数据,而普通的线图无论如何都没有意义。
将离散序列绘制为主干,其中数据值表示为每个主干末端的标记。主干从基线(通常在 y = 0)延伸到数据点值。
怎么做...
我们将使用 matplotlib 使用stem()函数绘制主干图。当 x 值作为从 0 到 len(y) - 1 的简单序列生成时,该函数可以仅使用一系列 y 值。如果我们为stem()函数提供 x 和 y 序列,它们将用于两个轴。
我们要用主干图配置的是几个格式化程序:
linefmt:这是茎线的线格式器markerfmt:行尾的茎用这个参数格式化basefmt:这会格式化基线的外观label:这定义了干图图例的标签hold:保存当前坐标轴上的当前图形bottom:设置基线位置在 y 轴上的位置,默认值为 0
参数保持被用作图的常用特征。如果打开(True),以下所有图都将添加到当前轴。否则,每个图将创建新的图形和轴。
要创建主干图,请执行以下步骤:
- 生成随机噪声数据。
- 配置阀杆选项。
- 画出主干。
下面是实现它的代码:
import matplotlib.pyplot as plt
import numpy as np
# time domain in which we sample
x = np.linspace(0, 20, 50)
# random function to simulate sampled signal
y = np.sin(x + 1) + np.cos(x ** 2)
# here we can setup baseline position
bottom = -0.1
# True -- hold current axes for further plotting
# False -- opposite. clear and use new figure/plot
hold = False
# set label for legend.
label = "delta"
markerline, stemlines, baseline = plt.stem(x, y, bottom=bottom,
label=label, hold=hold)
# we use setp() here to setup
# multiple properties of lines generated by stem()
plt.setp(markerline, color='red', marker='o')
plt.setp(stemlines, color='blue', linestyle=':')
plt.setp(baseline, color='grey', linewidth=2, linestyle='-')
# draw a legend
plt.legend()
plt.show()
先前的代码产生了如下图:

它是如何工作的...
首先,我们需要一些数据。对于这个配方,生成的采样伪信号就足够了。在现实世界中,任何离散的顺序数据都可以使用主干图进行适当的可视化。我们使用 Numpy 的numpy.linspace、numpy.cos和numpy.sin功能生成该信号。
然后我们为主干图和基线位置设置一个标签,默认为 0.0 。
如果我们想要绘制多个主干图,我们会将hold设置为True,并且生成的图调用将在同一组轴上渲染。
对matplotlib.stem的调用返回三个对象。首先是markerline,一个Line2D的例子。这保存了对表示茎本身的线的引用,只呈现标记,而不呈现连接标记的线。通过编辑Line2D实例的属性,可以使该行可见;这个过程将很快解释。最后一个也是一个Line2D实例,baseline,引用了一条代表stemlines来源的水平线。返回的第二个对象是stemlines,这当然是表示茎线的Line2D实例的集合(此时是 Python 列表)。
我们使用这些返回的对象来操纵主干图的视觉吸引力,使用setp函数将属性应用于这些对象中的所有线条(T1)实例,或者对象的集合。
尝试想要的设置,直到你理解setp如何改变你的绘图风格。
绘制矢量流的流线
流图用于可视化矢量场中的流。《科学与自然》中的例子包括磁力和重力领域以及液体物质的运动。
矢量场可以这样可视化,我们给每个点指定一条线和一个或多个箭头。强度可以用线的长度来表示,方向可以用指向特定方向的箭头来表示。
通常,力的强度随着特定流线的长度而可视化,但是密度也可以用于相同的目的。
做好准备
为了可视化矢量场,我们将使用 matplotlib 的matplotlib.pyplot.streamplot函数。这个函数从一个流的流线创建图,均匀地填充区域。速度场被插值,流线被积分。该功能的原始来源是可视化风模式或液体流动;因此,我们不需要严格的向量线,而是向量场的统一表示。
这个函数最重要的参数是(X、Y),它们是一维 NumPy 阵列的均匀间隔网格,以及(U、V),它们匹配(X、Y)速度的二维 NumPy 阵列。矩阵U和V的尺寸必须保证行数与Y的长度相等,列数必须与X的长度匹配。
如果给linewidth参数一个匹配 u 和 v 速度形状的二维数组,或者它可以只是一个所有行都接受的整数值,则可以控制每一行的流图的线宽。
颜色也可以只是所有流线的一个值和一个类似linewidth参数的矩阵。
箭头(类别FancyArrowPatch)用于指示矢量方向,我们可以使用两个参数来控制它们:arrowsize更改箭头的大小,arrowstyle更改箭头的格式(例如"simple", "->"...).
怎么做...
我们将从一个简单的例子开始,只是为了了解这里发生了什么。请执行以下步骤:
- 创建数据向量。
- 打印中间值。
- 绘制溪流图。
- 用流线显示我们的矢量。
以下是代码示例:
import matplotlib.pyplot as plt
import numpy as np
Y, X = np.mgrid[0:5:100j, 0:5:100j]
U = X
V = Y
from pprint import pprint
print "X"
pprint(X)
print "Y"
pprint(Y)
plt.streamplot(X, Y, U, V)
plt.show()
前面的代码将给出以下文本输出:
X
array([[ 0\. , 0.05050505, 0.1010101 , ..., 4.8989899 ,
4.94949495, 5\. ],
[ 0\. , 0.05050505, 0.1010101 , ..., 4.8989899 ,
4.94949495, 5\. ],
[ 0\. , 0.05050505, 0.1010101 , ..., 4.8989899 ,
4.94949495, 5\. ],
...,
[ 0\. , 0.05050505, 0.1010101 , ..., 4.8989899 ,
4.94949495, 5\. ],
[ 0\. , 0.05050505, 0.1010101 , ..., 4.8989899 ,
4.94949495, 5\. ],
[ 0\. , 0.05050505, 0.1010101 , ..., 4.8989899 ,
4.94949495, 5\. ]])
Y
array([[ 0\. , 0\. , 0\. , ..., 0\. ,
0\. , 0\. ],
[ 0.05050505, 0.05050505, 0.05050505, ..., 0.05050505,
0.05050505, 0.05050505],
[ 0.1010101 , 0.1010101 , 0.1010101 , ..., 0.1010101 ,
0.1010101 , 0.1010101 ],
...,
[ 4.8989899 , 4.8989899 , 4.8989899 , ..., 4.8989899 ,
4.8989899 , 4.8989899 ],
[ 4.94949495, 4.94949495, 4.94949495, ..., 4.94949495,
4.94949495, 4.94949495],
[ 5\. , 5\. , 5\. , ..., 5\. ,
5\. , 5\. ]])
而它也生成了如下流线流程图:

它是如何工作的...
我们通过使用 NumPy 的mgrid实例索引二维网格来创建一个 X 和 Y 的矢量场。我们将网格的范围指定为开始和停止(分别为-2 和 2)。第三个索引代表步长。步长表示起点和终点之间包含的点数。如果我们想包含停止值,我们使用一个复数作为步长,其中幅度用于开始和停止之间所需的点的数量,停止是包含的。
网格,像这样充实,然后用来计算矢量速度。这里,为了举例,我们只使用相同的 meshgrid属性作为矢量速度。这将生成一个图,清楚地显示简单的线性相关性和所表示的矢量场的流动。
玩转U和V的值,了解一下U和V的值是如何影响溪流绘图的。比如做U = np.sin(X)或者V = np.sin(Y)。接下来,尝试更改开始和停止值。查看U = np.sin(X)如下图:

记住,绘图是一组生成的线条和箭头补丁;因此,没有办法(至少目前)更新现有的图,因为线和箭头对向量和场一无所知。未来的实现可能会包括这一点,但目前这是 matplotlib 当前版本的一个已知限制。
还有更多...
当然,这个例子只是提供了一个了解和理解 matplotlib 的流图特性和功能的机会。
真正的力量来自于你手头有真正的数据可以玩。在理解了这个方法之后,你将能够认识到你有什么工具,这样当你得到数据并且你知道它的领域时,你将能够为这项工作选择最好的工具。
使用彩色地图
对数据进行颜色编码会对观看者如何感知你的视觉效果产生很大的影响,因为它们带有关于颜色和颜色代表什么的假设。
显而易见,如果颜色被用来给数据增加额外的信息,它总是好的。知道何时以及如何在可视化中使用颜色更好。
做好准备
如果您的数据不是自然颜色编码的(如地球/地形高度或物体温度),最好不要进行任何自然颜色的人工映射。我们希望正确理解数据,因此选择颜色来帮助读者轻松解码数据。如果我们表示的金融数据与凯尔温斯或摄氏温度无关,我们不希望读者不断试图压制学习的温度颜色映射。
如果可能的话,如果数据中没有很强的相关性,避免通常的红色/绿色关联,将它们与那些颜色关联起来。
为了帮助您选择正确的颜色映射,我们将解释matplotlib包中提供的一些颜色映射,如果您知道它们用于什么以及如何找到它们,这些颜色映射可以节省大量时间并帮助您。
彩色地图通常可分为以下几类:
- 顺序:这个代表同一个颜色从低到高饱和度的两个色调的单色色图,比如从白色到亮蓝色。这在大多数情况下是理想的,因为它们清楚地显示了从低值到高值的变化。
- 发散:这代表中心点,是中间值(通常是一些浅色),但是范围在高值和低值的方向上有两个不同的色调。这对于具有显著中值的数据可能是理想的;例如,当中位数为 0 时,它清楚地显示了负值和正值之间的差异。
- 定性:对于的情况,数据没有固有的排序,你所要做的就是保证不同的类别之间容易辨别,这就是要选择的色图。
- 循环:这个使用起来很方便,数据可以环绕端点值,例如,表示一天中的时间、风向或相位角。
matplotlib 附带了很多预定义的地图,我们可以将它们分为几类。我们将建议何时使用这些颜色图。最常见的基础色图是autumn、bone、cool、copper、flag、gray、hot、hsv、jet、pink、prism、sprint、summer、winter和spectral。
我们还有一组来自约克科学可视化软件包的彩色地图。这是从 GIST 包演变而来的,所以这个集合中的所有颜色图都有gist_作为它们名称的前缀。
注
Yorick 科学可视化包也是用 C 语言编写的解释语言,最近不太活跃。你可以在它的官方网站http://yorick.sourceforge.net/index.php找到更多信息。
这些颜色映射集包含以下映射:gist_earth、gist_heat、gist_ncar、gist_rainbow和gist_stern。
然后,我们有了基于 color brewer(http://colorbrewer.org)的颜色图,在这里我们可以将它们分类为以下几类:
- 发散:这个是亮度在中点最高,向不同端点降低的地方
- 连续:这是亮度单调下降的地方
- 定性:这是使用不同颜色集合来区分数据类别的地方
此外,还有一些其他颜色图可供使用:
|Colormap(颜色映射)
|
描述
|
| --- | --- |
| brg | 这将代表一个发散的蓝-红-绿颜色图。 |
| bwr | 这将代表一个发散的蓝-白-红颜色图。 |
| coolwarm | 这对于三维着色、色盲和颜色排序非常有用。 |
| rainbow | 这表示具有发散亮度的光谱紫-蓝-绿-黄-橙-红颜色图。 |
| seismic | 这代表了一个发散的蓝-白-红颜色图。 |
| terrain | 这代表了 Mapmaker 的颜色——蓝色、绿色、黄色、棕色和白色——最初来自 IGOR Pro 软件。 |
这里展示的大部分地图可以通过将_r后缀放在颜色地图的名称后来反转,例如hot_r是hot的反转循环颜色地图。
怎么做...
我们可以在 matplotlib 中的许多项目上设置颜色映射。例如,可以在image、pcolor和scatter上设置色图。这通常通过函数调用的参数cmap来实现。这个论点接受了colors.Colormap的例子。
我们也可以使用matplotlib.pyplot.set_cmap为轴上绘制的最新对象设置cmap。
您可以通过matplotlib.pyplot.colormaps轻松获得所有可用的彩色地图。启动 IPython 并键入以下内容:
In [1]: import matplotlib.pyplot as plt
In [2]: plt.colormaps()
Out[2]:
['Accent',
'Accent_r',
'Blues',
'Blues_r',
...
'winter',
'winter_r']
请注意,我们已经缩短了前面的列表,因为它包含大约 140 个项目,并将在这里跨越几页。
这将导入pyplot函数界面,并允许我们调用 colormaps函数,该函数返回所有已注册颜色图的列表。
最后,我们想告诉你如何制作一个好看的彩色地图。在以下示例中,我们需要:
-
导航到 ColorBrewer 网站,以十六进制格式获取不同的颜色映射颜色值。
-
生成 x 和 y 的随机样本,其中 y 为数值的累计和(模拟股价变化)。
-
将自定义应用于 matplotlib 的散点图功能。
-
调整散点图标记线的颜色和宽度,使图对观察者来说更加易读和令人愉快。
import matplotlib as mpl import matplotlib.pyplot as plt import numpy as np # Red Yellow Green divergent colormap red_yellow_green = ['#d73027', '#f46d43', '#fdae61', '#fee08b', '#ffffbf', '#d9ef8b', '#a6d96a', '#66bd63', '#1a9850'] sample_size = 1000 fig, ax = plt.subplots(1) for i in range(9): y = np.random.normal(size=sample_size).cumsum() x = np.arange(sample_size) ax.scatter(x, y, label=str(i), linewidth=0.1, edgecolors='grey', facecolor=red_yellow_green[i]) ax.legend() plt.show()
前面的代码将呈现一个漂亮的图形:

它是如何工作的...
我们使用 ColorBrewer 网站找出红-黄-绿发散色图中的颜色。然后,我们在代码中列出了这些颜色,并将其应用到散点图中。
注
ColorBrewer 是由辛西娅·布鲁尔、马克·哈罗威和宾夕法尼亚州立大学为探索彩色地图而开发的网络工具。这是一个非常方便的工具,可以提取不同范围的彩色地图,并使用微小的变化将它们应用到地图上,这样您就可以立即感觉到它们在图表上的样子。该特定地图位于 http://colorbrewer2.org/index.php?type=diverging&方案=RdYlGn & n=9 。
有时,我们必须在matplotlib.rcParams上进行定制,这是我们在创建图形或任何轴之前首先要做的事情。
例如,matplotlib.rcParams['axes.cycle_color']是我们想要更改的配置设置,以便为大多数 matplotlib 函数设置默认的 colormap。
还有更多...
使用matplotlib.pyplot.register_cmap,我们可以向 matplotlib 注册一个新的颜色映射,这样就可以使用get_cmap功能找到它。我们可以用两种不同的方式来使用它。这是两个签名:
register_cmap(name='swirly', cmap=swirly_cmap)register_cmap(name='choppy', data=choppydata, lut=128)
第一个签名允许我们指定一个颜色映射作为colors.Colormap的实例,并使用name参数注册它。参数name可以省略,在这种情况下,它将从提供的cmap实例的name属性继承。
对于后一种情况,我们将三个参数传递给线性分段的 colormap 构造函数,然后注册该 colormap。
使用maplotlib.pyplot.get_cmap我们可以使用name参数得到colors.Colormap实例。
以下是如何使用matplotlib.colors.LinearSegmentedColormap制作自己的地图:
from pylab import *
cdict = {'red': ((0.0, 0.0, 0.0),
(0.5, 1.0, 0.7),
(1.0, 1.0, 1.0)),
'green': ((0.0, 0.0, 0.0),
(0.5, 1.0, 0.0),
(1.0, 1.0, 1.0)),
'blue': ((0.0, 0.0, 0.0),
(0.5, 1.0, 0.0),
(1.0, 0.5, 1.0))}
my_cmap = matplotlib.colors.LinearSegmentedColormap('my_colormap',cdict,256)
pcolor(rand(10,10),cmap=my_cmap)
colorbar()
执行这种方法是最简单的部分,而最困难的部分是实际上想出一个信息丰富的颜色组合,不会从我们想要可视化的数据中带走任何信息,并且对观察者来说也是令人愉快的。
对于基础图列表(前面表格中列出的颜色图),我们可以使用pylab快捷方式来设置颜色图。例如:
imshow(X)
hot()
这将把图像 X 的颜色映射设置为cmap='hot'。
使用散点图和直方图
散点图是经常遇到的,因为它们是可视化两个变量之间关系的最常见的图。如果我们想快速看一下这两个变量的数据,看看它们之间有没有关系(即相关性),我们就画一个快速散点图。为了散点图的存在,我们必须有一个变量可以被系统地改变,例如,实验者,所以我们可以检查影响另一个变量的可能性。
这就是为什么,在这个食谱中,我们将学习如何理解散点图。
做好准备
例如,我们想看看两个事件是如何相互影响的,或者它们是否受到影响。这种可视化在大数据集上特别有用,当数据只是数字时,我们不能通过查看原始形式的数据来得出任何结论。
值之间的相关性,如果有的话,可以是正的也可以是负的。正相关是为了增加 X 值,我们也有 Y 值增加。在增加 X 值的负相关中,Y 值在减少。在理想情况下,正相关是从轴的左下角到右上角的一条线。理想的负相关是从轴的左上角开始到右下角的线。
两个数据点之间的理想正相关值为 1,理想负相关值为-1。这个区间内的所有东西都表示这两个值之间的相关性较弱。通常情况下,从两个变量存在实际联系的角度来看,0.5 到 0.5 之间的一切都不被认为是有价值的。
正相关的一个例子是,慈善罐子里的钱与看到罐子的人数直接正相关。负相关是从地点 A 到达地点 B 所需的时间,取决于地点 A 和地点 B 之间的距离,距离越大,我们完成旅行所需的时间就越多。
我们在这里给出的例子是正相关的,但是这并不完美,因为不同的人每次访问可能会投入不同的金额。但总的来说,我们可以假设看到罐子的人越多,里面剩下的钱就会越多。
但是请记住,即使散点图显示了两个变量之间的相关性,这种相关性也可能不是直接的。可能有第三个变量影响两个绘制的变量,所以相关性只是绘制的值与第三个变量相关的一种情况。最终,这种关联可能只是表面的,背后并不存在真正的联系。
怎么做...
通过下面的代码示例,我们将演示散点图如何解释变量之间的关系。
我们使用的数据是使用谷歌趋势门户网站获得的,在那里可以下载包含给定参数的相对搜索量的归一化值的 CSV 文件。
我们将我们的数据存储在ch07_search_data.py Python 模块中,这样我们就可以在后续的代码菜谱中导入它。以下是它的内容:
# ch07_search_data
# daily search trend for keyword 'flowers' for a year
DATA = [
1.04, 1.04, 1.16, 1.22, 1.46, 2.34, 1.16, 1.12, 1.24, 1.30, 1.44, 1.22, 1.26,
1.34, 1.26, 1.40, 1.52, 2.56, 1.36, 1.30, 1.20, 1.12, 1.12, 1.12, 1.06, 1.06,
1.00, 1.02, 1.04, 1.02, 1.06, 1.02, 1.04, 0.98, 0.98, 0.98, 1.00, 1.02, 1.02,
1.00, 1.02, 0.96, 0.94, 0.94, 0.94, 0.96, 0.86, 0.92, 0.98, 1.08, 1.04, 0.74,
0.98, 1.02, 1.02, 1.12, 1.34, 2.02, 1.68, 1.12, 1.38, 1.14, 1.16, 1.22, 1.10,
1.14, 1.16, 1.28, 1.44, 2.58, 1.30, 1.20, 1.16, 1.06, 1.06, 1.08, 1.00, 1.00,
0.92, 1.00, 1.02, 1.00, 1.06, 1.10, 1.14, 1.08, 1.00, 1.04, 1.10, 1.06, 1.06,
1.06, 1.02, 1.04, 0.96, 0.96, 0.96, 0.92, 0.84, 0.88, 0.90, 1.00, 1.08, 0.80,
0.90, 0.98, 1.00, 1.10, 1.24, 1.66, 1.94, 1.02, 1.06, 1.08, 1.10, 1.30, 1.10,
1.12, 1.20, 1.16, 1.26, 1.42, 2.18, 1.26, 1.06, 1.00, 1.04, 1.00, 0.98, 0.94,
0.88, 0.98, 0.96, 0.92, 0.94, 0.96, 0.96, 0.94, 0.90, 0.92, 0.96, 0.96, 0.96,
0.98, 0.90, 0.90, 0.88, 0.88, 0.88, 0.90, 0.78, 0.84, 0.86, 0.92, 1.00, 0.68,
0.82, 0.90, 0.88, 0.98, 1.08, 1.36, 2.04, 0.98, 0.96, 1.02, 1.20, 0.98, 1.00,
1.08, 0.98, 1.02, 1.14, 1.28, 2.04, 1.16, 1.04, 0.96, 0.98, 0.92, 0.86, 0.88,
0.82, 0.92, 0.90, 0.86, 0.84, 0.86, 0.90, 0.84, 0.82, 0.82, 0.86, 0.86, 0.84,
0.84, 0.82, 0.80, 0.78, 0.78, 0.76, 0.74, 0.68, 0.74, 0.80, 0.80, 0.90, 0.60,
0.72, 0.80, 0.82, 0.86, 0.94, 1.24, 1.92, 0.92, 1.12, 0.90, 0.90, 0.94, 0.90,
0.90, 0.94, 0.98, 1.08, 1.24, 2.04, 1.04, 0.94, 0.86, 0.86, 0.86, 0.82, 0.84,
0.76, 0.80, 0.80, 0.80, 0.78, 0.80, 0.82, 0.76, 0.76, 0.76, 0.76, 0.78, 0.78,
0.76, 0.76, 0.72, 0.74, 0.70, 0.68, 0.72, 0.70, 0.64, 0.70, 0.72, 0.74, 0.64,
0.62, 0.74, 0.80, 0.82, 0.88, 1.02, 1.66, 0.94, 0.94, 0.96, 1.00, 1.16, 1.02,
1.04, 1.06, 1.02, 1.10, 1.22, 1.94, 1.18, 1.12, 1.06, 1.06, 1.04, 1.02, 0.94,
0.94, 0.98, 0.96, 0.96, 0.98, 1.00, 0.96, 0.92, 0.90, 0.86, 0.82, 0.90, 0.84,
0.84, 0.82, 0.80, 0.80, 0.76, 0.80, 0.82, 0.80, 0.72, 0.72, 0.76, 0.80, 0.76,
0.70, 0.74, 0.82, 0.84, 0.88, 0.98, 1.44, 0.96, 0.88, 0.92, 1.08, 0.90, 0.92,
0.96, 0.94, 1.04, 1.08, 1.14, 1.66, 1.08, 0.96, 0.90, 0.86, 0.84, 0.86, 0.82,
0.84, 0.82, 0.84, 0.84, 0.84, 0.84, 0.82, 0.86, 0.82, 0.82, 0.86, 0.90, 0.84,
0.82, 0.78, 0.80, 0.78, 0.74, 0.78, 0.76, 0.76, 0.70, 0.72, 0.76, 0.72, 0.70,
0.64]
我们需要执行以下步骤:
- 关键字
flowers使用谷歌趋势搜索量一年的干净数据集;我们将这个数据集导入变量d。 - 使用与我们的谷歌趋势数据集相同长度(365 个数据点)的随机正态分布;这将是数据集
d1。 - 创建一个包含四个子绘图的绘图。
- 在第一个子绘图中,绘制
d和d1的散点图。 - 在第二个子绘图中,用
d1绘制d1的散点图。 - 在第三个子图中,用反转的
d1渲染d1的散点图。 - 在第四个子图中,使用由
d1和d组合构建的相似数据集渲染d1的散点图。
下面的代码将说明这个配方前面解释的关系:
import matplotlib.pyplot as plt
import numpy as np
# import the data
from ch07_search_data import DATA
d = DATA
# Now let's generate random data for the same period
d1 = np.random.random(365)
assert len(d) == len(d1)
fig = plt.figure()
ax1 = fig.add_subplot(221)
ax1.scatter(d, d1, alpha=0.5)
ax1.set_title('No correlation')
ax1.grid(True)
ax2 = fig.add_subplot(222)
ax2.scatter(d1, d1, alpha=0.5)
ax2.set_title('Ideal positive correlation')
ax2.grid(True)
ax3 = fig.add_subplot(223)
ax3.scatter(d1, d1*-1, alpha=0.5)
ax3.set_title('Ideal negative correlation')
ax3.grid(True)
ax4 = fig.add_subplot(224)
ax4.scatter(d1, d1+d, alpha=0.5)
ax4.set_title('Non ideal positive correlation')
ax4.grid(True)
plt.tight_layout()
plt.show()
下面的是我们在执行前面的代码时应该得到的输出:

它是如何工作的...
我们在前面的输出中看到的示例清楚地显示了不同数据集之间是否存在任何相关性。虽然第二个子图(右上)显示了数据集d1与d1本身的理想或完美的正相关(很明显),但我们可以看到第四个子图(右下)暗示了正相关,尽管不理想。我们从d1和d(随机)构建了这个数据集来模拟两个相似的信号(事件),而使用d和d1绘制的第二个子图中有一定的随机性(或噪声),但仍然可以与原始(d)信号进行比较。
还有更多...
我们还可以将直方图添加到散点图中,这样它们可以告诉我们更多关于绘制的数据。我们可以添加水平和垂直直方图来显示 x 轴和 y 轴上数据点的频率。利用这个,我们可以同时看到整个数据集的概要(直方图)和单个数据点(散点图)。
以下是使用我们在本食谱中介绍的相同的两个数据集生成散点图-直方图组合的代码示例。代码的核心是这里给出的函数scatterhist(),用于在不同的数据集上重用,试图基于提供的数据集(直方图中的箱数、轴的限制等)设置一些变量。
我们从通常的进口开始:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
下面的代码是我们的函数的定义,给定一个(x,y)数据集和一个figsize参数,我们可以生成散点图:
def scatterhist(x, y, figsize=(8,8)):
"""
Create simple scatter & histograms of data x, y inside given plot
@param figsize: Figure size to create figure
@type figsize: Tuple of two floats representing size in inches
@param x: X axis data set
@type x: np.array
@param y: Y axis data set
@type y: np.array
"""
_, scatter_axes = plt.subplots(figsize=figsize)
# the scatter plot:
scatter_axes.scatter(x, y, alpha=0.5)
scatter_axes.set_aspect(1.)
divider = make_axes_locatable(scatter_axes)
axes_hist_x = divider.append_axes(position="top", sharex=scatter_axes, size=1, pad=0.1)
axes_hist_y = divider.append_axes(position="right",
harey=scatter_axes,
size=1, pad=0.1)
# compute bins accordingly
binwidth = 0.25
# global max value in both data sets
xymax = np.max([np.max(np.fabs(x)), np.max(np.fabs(y))])
# number of bins
bincap = int(xymax / binwidth) * binwidth
bins = np.arange(-bincap, bincap, binwidth)
nx, binsx, _ = axes_hist_x.hist(x, bins=bins, histtype='stepfilled',
orientation='vertical')
ny, binsy, _ = axes_hist_y.hist(y, bins=bins, histtype='stepfilled',
orientation='horizontal')
tickstep = 50
ticksmax = np.max([np.max(nx), np.max(ny)])
xyticks = np.arange(0, ticksmax + tickstep, tickstep)
# hide x and y ticklabels on histograms
for tl in axes_hist_x.get_xticklabels():
tl.set_visible(False)
axes_hist_x.set_yticks(xyticks)
for tl in axes_hist_y.get_yticklabels():
tl.set_visible(False)
axes_hist_y.set_xticks(xyticks)
plt.show()
现在我们继续加载数据和函数调用,以生成并渲染所需的图表。
if __name__ == '__main__': # import the data
from ch07_search_data import DATA as d
# Now let's generate random data for the same period
d1 = np.random.random(365)
assert len(d) == len(d1)
# try with the random data
# d = np.random.randn(1000)
# d1 = np.random.randn(1000)
scatterhist(d, d1)
先前的代码应产生以下输出:

绘制两个变量之间的互相关
如果我们有来自两个不同观测的两个不同数据集,我们想知道这两个事件集是否相关。我们想交叉关联它们,看看它们是否有任何匹配。我们正在寻找一种在更大的数据样本中包含更小的数据样本的模式。模式不一定是明显或琐碎的模式。
做好准备
我们可以使用 pyplot 实验室的 matplot libmatplotlib.pyplot.xcorr函数。该函数可以绘制两个数据集之间的相关性,这样我们就可以看到绘制的值之间是否有任何显著的模式。假设x和y长度相同。
如果我们将参数normed作为True传递,我们可以在第 0 个滞后时(即没有时间延迟或时间滞后时)通过互相关进行归一化。
在幕后,关联是使用 NumPy 的numpy.correlate功能完成的。
使用参数usevlines(将其设置为True),我们可以指示 matplotlib 使用vlines()而不是plot()来绘制相关图的线条。主要区别在于,如果我们使用的是plot(),我们可以使用在**kwargs参数中传递给matplotlib.pyplot.xcorr函数的标准Line2D属性来设置线条的样式。
怎么做...
在以下示例中,我们需要执行以下步骤:
- 导入
matplotlib.pyplot模块。 - 导入
numpy包。 - 对关键词花使用一年谷歌搜索量趋势的干净数据集。
- 绘制数据集(真实的和人工的)和互相关图。
- 收紧布局,以便更好地了解标签和刻度。
- 添加适当的标签和网格,以便于理解绘图。
以下是将执行上述步骤的代码:
import matplotlib.pyplot as plt
import numpy as np
# import the data
from ch07_search_data import DATA as d
total = sum(d)
av = total / len(d)
z = [i - av for i in d]
# Now let's generate random data for the same period
d1 = np.random.random(365)
assert len(d) == len(d1)
total1 = sum(d1)
av1 = total1 / len(d1)
z1 = [i - av1 for i in d1]
fig = plt.figure()
# Search trend volume
ax1 = fig.add_subplot(311)
ax1.plot(d)
ax1.set_xlabel('Google Trends data for "flowers"')
# Random: "search trend volume"
ax2 = fig.add_subplot(312)
ax2.plot(d1)
ax2.set_xlabel('Random data')
# Is there a pattern in search trend for this keyword?
ax3 = fig.add_subplot(313)
ax3.set_xlabel('Cross correlation of random data')
ax3.xcorr(z, z1, usevlines=True, maxlags=None, normed=True, lw=2)
ax3.grid(True)
plt.ylim(-1, 1)
plt.tight_layout()
plt.show()
之前的代码将呈现以下输出:

它是如何工作的...
我们使用了一个真实数据集,其中有一个可识别的模式(两个峰值以相似的方式在整个数据集上重复;参考前面的图)。另一个数据集只是一些随机的正态分布数据,其长度与公共服务谷歌趋势的实际累积数据相同。
我们将两个数据集绘制在输出的上半部分,以可视化数据。
使用 matplotlib 的xcorr,反过来使用 NumPy 的correlate()函数,我们计算互相关,并将其绘制在屏幕的下半部分。
NumPy 中的互相关计算返回一个相关系数数组,该数组表示两个数据集(或信号,如果用于信号处理领域,通常称为信号)的相似程度。
互相关图(correlogram)告诉我们,这两个信号是不相关的,这由相关值的高度(出现在特定时间滞后的垂直线)来表示。我们可以看到不止一条垂直线(时滞 n 时的相关系数)在 0.5 以上。
例如,如果两个数据集在 100 的时间延迟时具有相关性(即,由两个不同传感器观察到的同一对象之间的 100 秒移动),我们将在前面的输出中看到 x = 100 的垂直线(代表相关系数)。
自相关的重要性
自相关表示在连续的时间间隔内,给定的时间序列和自身的滞后(即时间延迟)版本之间的相似程度。它发生在时间序列研究中,当与给定时间周期相关的误差延续到未来的时间周期。例如,如果我们预测股票股息的增长,一年的高估很可能导致未来几年的高估。
时间序列分析数据出现在许多不同的科学应用和金融过程中。一些例子包括:生成的金融业绩报告、一段时间内的价格、计算波动性等。
如果我们在分析未知数据,自相关可以帮助我们检测数据是否随机。为此,我们可以使用相关图。它可以帮助提供问题的答案,例如:数据是随机的吗?这个时间序列数据是白噪声信号吗?是正弦曲线吗?是自回归吗?这个时间序列数据的模型是什么?
做好准备
我们将使用 matplotlib 来比较两组数据。一个是某个关键词一年(365 天)搜索量的谷歌日趋势。另一组是正态分布的 365 个随机测量值(生成的随机数据)。
我们将自动校正两个数据集,并比较相关图如何可视化数据中的模式。
怎么做...
在本节中,我们将执行以下步骤:
- 导入
matplotlib.pyplot模块。 - 导入
numpy包。 - 使用一年谷歌搜索量的干净数据集。
- 绘制数据集并绘制其自相关图。
- 使用 NumPy 生成相同长度的随机数据集。
- 在同一图上绘制随机数据集,并绘制其自相关图。
- 添加适当的标签和网格,以便于理解绘图。
以下是代码:
import matplotlib.pyplot as plt
import numpy as np
# import the data
from ch07_search_data import DATA as d
total = sum(d)
av = total / len(d)
z = [i - av for i in d]
fig = plt.figure()
# plt.title('Comparing autocorrelations')
# Search trend volume
ax1 = fig.add_subplot(221)
ax1.plot(d)
ax1.set_xlabel('Google Trends data for "flowers"')
# Is there a pattern in search trend for this keyword?
ax2 = fig.add_subplot(222)
ax2.acorr(z, usevlines=True, maxlags=None, normed=True, lw=2)
ax2.grid(True)
ax2.set_xlabel('Autocorrelation')
# Now let's generate random data for the same period
d1 = np.random.random(365)
assert len(d) == len(d1)
total = sum(d1)
av = total / len(d1)
z = [i - av for i in d1]
# Random: "search trend volume"
ax3 = fig.add_subplot(223)
ax3.plot(d1)
ax3.set_xlabel('Random data')
# Is there a pattern in search trend for this keyword?
ax4 = fig.add_subplot(224)
ax4.set_xlabel('Autocorrelation of random data')
ax4.acorr( z, usevlines=True, maxlags=None, normed=True, lw=2)
ax4.grid(True)
plt.show()
先前的代码将呈现以下输出:

它是如何工作的...
看左边的图,很容易发现搜索量数据中的模式,其中左下方的图具有模式不明显但仍然可能存在的正态分布随机数据。
计算和绘制随机数据的自相关,我们可以看到在 0 处有很高的相关性,这是预期的,数据与自身相关,没有任何时滞。但走前走后没有时间滞后,信号几乎为 0。因此,我们可以有把握地得出结论,原始时间的信号与所检查的任何时间滞后之间没有相关性。
看看真实的数据,谷歌搜索量的趋势,我们可以看到相同的行为在零时滞,仍然是我们可以期待的任何自相关信号。但是在零时差后的 30、60 和 110 天左右,我们有强烈的信号。这表明这个特定的搜索词和人们在谷歌搜索引擎上搜索它的方式有一种模式。
我们将把解释为什么这是一个非常不同的故事的练习留给读者。记住相关性和因果关系是两个截然不同的东西。
还有更多...
当我们想要识别未知数据的模型时,自相关经常被使用。当我们试图将数据拟合到模型中时,数据如何与其自身相关有时是为我们所看到的数据集确定合适模型的第一步。这需要的不仅仅是 Python 它需要数学建模和各种统计测试(Ljung-Box 测试、Box-Pierce 测试等)的知识,这些知识将帮助我们回答我们可能遇到的任何问题。
八、关于 matplotlib 绘图的更多信息
在本章中,我们将介绍:
- 画倒刺
- 制作方块和触须图
- 制作甘特图
- 制作误差线
- 利用文本和字体属性
- 用 LaTeX 渲染文本
- 理解 pyplot 和 OO API 的区别
简介
在本章中,我们将探索 matplotlib 包中一些不常用的特性。其中一些例子扩展了 matplotlib 最初的目标,但是它们展示了只要有一点创造力就可以完成的事情,并且证明了 matplotlib 是全功能的和通用的。
画倒刺
倒钩是风速和风向的表示,主要是气象科学家部署的。理论上,它们可以用来可视化任何类型的二维矢量。它们类似于箭头(颤动),但不同之处在于箭头用箭头的长度来表示向量的大小,而倒钩通过使用线或三角形作为大小的增量来给出关于向量大小的更多信息。
我们将解释什么是倒钩,如何阅读它们,以及如何使用 Python 和 matplotlib 可视化它们。这里有一组典型的倒钩:

在上图中,三角形,也称为标志,代表最大增量。实线或倒钩代表较小的增量;半行是最小的增量。
对于半直线、直线和三角形,增量分别为 5、10 和 65。至少对气象学家来说,这里的数值代表了以海里/小时(节)为单位的风速。
我们从左到右排列倒刺,以表示以下数值:0、5、10、15、30、40、50、60 和 100 节。这里的方向对于每个倒钩都是相同的,并且是从北到南,因为东西方向的速度分量对于每个倒钩都是 0。
做好准备
可以从matplotlib.pyplot.barbs使用 matplotlib 函数创建倒刺。
倒钩函数接受各种参数,但是主要的用例是我们指定 X 和 Y 坐标,表示观测数据点的位置。第二对参数——U,V——表示矢量在南北和东西方向的大小,单位为节。
其他有用的参数是枢轴、大小和各种颜色参数。
透视参数(pivot)表示网格点上表示的箭头部分。当箭头围绕这个点旋转时,我们得到一个枢轴参数。箭头可以围绕顶端或中间旋转,这是透视参数的有效值。
因为倒刺由几个部分组成,我们可以设置这些部分的颜色。因此,我们可以设置一些与颜色相关的参数:
barbcolor:这定义了倒钩所有部分的颜色,除了旗帜flagcolor这定义了倒钩上任何旗帜的颜色facecolor:如果没有指定前面的颜色参数(或者从rcParams读取默认值),则使用该参数
如果指定了任何前面的颜色相关参数,参数facecolor将被覆盖。参数facecolor是用于多边形着色的参数。
尺寸参数(sizes)指定特征与倒钩长度的比例。这是系数的集合,可以通过使用以下任意或所有键来指定:
spacing:这定义了标志/倒钩的特征之间的空间height:这定义了从轴到旗帜或倒钩顶部的距离width:这定义了旗帜的宽度emptybarb:这定义了用于低量值的圆半径
怎么做...
让我们通过执行以下步骤来演示如何使用倒钩函数:
- 生成坐标网格来模拟观测。
- 模拟风速的观测值。
- 绘制倒钩图。
- 绘图抖动,以展示不同的外观。
以下代码将生成下图:
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(-20, 20, 8)
y = np.linspace( 0, 20, 8)
# make 2D coordinates
X, Y = np.meshgrid(x, y)
U, V = X+25, Y-35
# plot the barbs
plt.subplot(1,2,1)
plt.barbs(X, Y, U, V, flagcolor='green', alpha=0.75)
plt.grid(True, color='gray')
# compare that with quiver / arrows
plt.subplot(1,2,2)
plt.quiver(X, Y, U, V, facecolor='red', alpha=0.75)
# misc settings
plt.grid(True, color='grey')
plt.show()
上面的代码呈现了两个子场景,如下图所示:

它是如何工作的...
为了说明相同的数据如何带来不同的信息,我们使用 matplotlib 中的倒钩和颤动图来可视化模拟的观测风数据。
首先,我们使用 NumPy 为 x 和 y 数组生成变量样本。然后,我们使用 NumPy 的meshgrid()函数创建一个 2D 坐标网格,在该网格中,我们的观测数据在特定坐标下被采样。最后,U 和 V 是 NS(南北)和 EW(东西)方向的风速值,单位为节(海里/小时)。为了配方的目的,我们从已经可用的 X 和 Y 矩阵中调整了一些值。
然后我们将图分成两个子图,在最左边的图中绘制倒钩,在最右边的图中绘制箭头补丁。我们稍微调整了两个子图的颜色和透明度,并在两个子图上打开了网格。
还有更多...
在北半球,这一切都很好,那里的风以逆时针方向旋转,羽毛(三角形、倒刺的实线和半线)指向低压方向。在南半球,这是颠倒的,所以我们的风倒刺图不会正确地表示我们正在可视化的数据。
我们必须颠倒羽毛的这个方向。幸运的是,倒钩函数有参数flip_barb。当序列中的每个项目为每个倒钩指定翻转决策时,该参数可以是单个布尔值(True或False)或一系列布尔值,如其他数据数组的形状。
制作一个盒子和一个触须图
是否希望可视化一系列数据测量(或观察)以在一个图中显示数据系列的几个属性(如中值、数据的分布和数据的分布)?你想通过一种可以直观比较几个相似数据系列的方式来做到这一点吗?你会如何想象它们?欢迎来到方块触须绘图!如果你和习惯于信息密度的人交谈,这可能是比较分布的最好的图表类型。
框须图的使用示例范围从比较学校之间的考试成绩到比较变更(优化)前后的过程参数。
做好准备
方块图和触须图的要素是什么?正如我们在下图中看到的,我们有几个重要的元素在方块-触须图中携带信息。第一个组件是一个框,其中包含从下四分位数到上四分位数的四分位数区间信息。数据的中值由方框中的一条线表示。

络腮胡子从数据的第一个四分位数(25 百分位)到最后一个四分位数(75 百分位)从两边的方框中延伸出来。换句话说,晶须从四分位数范围的底部延伸 1.5 倍。在正态分布的情况下,晶须将覆盖总数据范围的 99.3%。
如果有超出胡须范围的值,它们将显示为传单。否则,胡须将覆盖数据的总范围。
可选地,框也可以携带关于中位数周围的置信区间的信息。这是由盒子上的一个缺口来表示的。该信息可用于指示两个系列中的数据是否具有相似的分布。然而,这并不严格,只是一个可以目视检查的指示。
怎么做...
在下面的食谱中,我们将学习如何使用 matplotlib 创建一个方块和触须图。我们将执行以下步骤:
- 对一些比较过程数据进行采样,其中单个整数表示在运行过程的观察期间出现的错误。
- 将
PROCESSES字典中的数据读入DATA。 - 将
PROCESSES字典中的标签读入LABELS。 - 使用
matplotlib.pyplot.boxplot渲染方块触须图。 - 从图中删除一些图表垃圾。
- 添加轴标签。
- 显示图形。
下面的代码实现了这些步骤:
import matplotlib.pyplot as plt
# define data
PROCESSES = {
"A": [12, 15, 23, 24, 30, 31, 33, 36, 50, 73],
"B": [6, 22, 26, 33, 35, 47, 54, 55, 62, 63],
"C": [2, 3, 6, 8, 13, 14, 19, 23, 60, 69],
"D": [1, 22, 36, 37, 45, 47, 48, 51, 52, 69],
}
DATA = PROCESSES.values()
LABELS = PROCESSES.keys()
plt.boxplot(DATA, notch=False, widths=0.3)
# set ticklabel to process name
plt.gca().xaxis.set_ticklabels(LABELS)
# some clean up(removing chartjunk)
# turn the spine off
for spine in plt.gca().spines.values():
spine.set_visible(False)
# turn all ticks for x-axis off
plt.gca().xaxis.set_ticks_position('none')
# leave left ticks for y-axis on
plt.gca().yaxis.set_ticks_position('left')
# set axes labels
plt.ylabel("Errors observed over defined period.")
plt.xlabel("Process observed over defined period.")
plt.show()
代码前面的生成下图:

它是如何工作的...
通过首先计算DATA中给定数据的四分位数来绘制方块和触须图。
这些四分位数值用于计算绘制方框和须的线条。
我们对绘图进行了调整,使其在视觉上更加赏心悦目,不包含任何不必要的线条(指“图表垃圾”等多余的线条,如爱德华·r·图菲特的名著《定量信息的视觉展示》中所述)。这些线没有携带信息,只是给观看者大脑中的心智模型施加了更大的压力,要求他们在发现真正有价值的信息之前解码所有的线。
制作甘特图
一种非常广泛使用的基于时间的数据可视化形式是甘特图。它以 1910 年代发明它的机械工程师亨利·甘特·T4 的名字命名,几乎专门用于在项目管理中可视化工作分解结构。这个图表因其描述性的价值而受到管理者的喜爱,但却没有受到员工的喜爱,尤其是在项目截止日期临近的时候。
因为它非常普遍,几乎每个人都可以理解和阅读它,即使它超载了额外的(相关和不相关的)信息。
基本甘特图在 x 轴上有一个时间序列,在 y 轴上有一组表示任务或子任务的标签。任务持续时间通常被可视化为从给定任务的开始时间到结束时间的直线或条形图。
如果存在子任务,则一个或多个子任务有一个父任务,在这种情况下,任务的总时间是从子任务中聚合出来的,这样就考虑到了重叠和间隔时间。这对于执行关键路径分析非常有用。
关键路径分析是一种数学分析,它计算包含所有所需任务的路径,考虑任务的相互依赖性,以便可以计算项目的总开始到完成时间。这是项目管理领域的一个非常重要的工具,可以普遍应用于任何类型的项目进行调度和资源规划。
因此,在本食谱中,我们将介绍使用 Python 创建甘特图。
做好准备
有许多成熟的软件应用和服务可以让您制作非常灵活和复杂的甘特图。我们将尝试演示如何在纯 Python 中做到这一点,不依赖于外部应用,但实现整洁的外观和信息丰富的甘特图。
示例中显示的甘特图不支持嵌套任务,但对于简单的工作分解结构就足够了。
怎么做...
下面的代码示例将允许我们演示 Python 如何与 matplotlib 一起使用来呈现甘特图。我们将执行以下步骤:
- 加载包含一组任务的
TEST_DATA,并用TEST_DATA实例化甘特图类。 - 每个任务都包含一个标签以及开始和结束时间。
- 通过在轴上绘制水平条来处理所有任务。
- 为我们渲染的数据设置 x 轴和 y 轴的格式。
- 拧紧布局。
- 显示甘特图。
以下是示例代码:
from datetime import datetime
import sys
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.font_manager as font_manager
import matplotlib.dates as mdates
import logging
class Gantt(object):
'''
Simple Gantt renderer.
Uses *matplotlib* rendering capabilities.
'''
# Red Yellow Green diverging colormap
# from http://colorbrewer2.org/
RdYlGr = ['#d73027', '#f46d43', '#fdae61',
'#fee08b', '#ffffbf', '#d9ef8b',
'#a6d96a', '#66bd63', '#1a9850']
POS_START = 1.0
POS_STEP = 0.5
def __init__(self, tasks):
self._fig = plt.figure()
self._ax = self._fig.add_axes([0.1, 0.1, .75, .5])
self.tasks = tasks[::-1]
def _format_date(self, date_string):
'''
Formats string representation of *date_string* into *matplotlib.dates*
instance.
'''
try:
date = datetime.strptime(date_string, '%Y-%m-%d %H:%M:%S')
except ValueError as err:
logging.error("String '{0}' can not be converted to datetime object: {1}"
.format(date_string, err))
sys.exit(-1)
mpl_date = mdates.date2num(date)
return mpl_date
def _plot_bars(self):
'''
Processes each task and adds *barh* to the current *self._ax* (*axes*).
'''
i = 0
for task in self.tasks:
start = self._format_date(task['start'])
end = self._format_date(task['end'])
bottom = (i * Gantt.POS_STEP) + Gantt.POS_START
width = end - start
self._ax.barh(bottom, width, left=start, height=0.3,
align='center', label=task['label'],
color = Gantt.RdYlGr[i])
i += 1
def _configure_yaxis(self):
'''y axis'''
task_labels = [t['label'] for t in self.tasks]
pos = self._positions(len(task_labels))
ylocs = self._ax.set_yticks(pos)
ylabels = self._ax.set_yticklabels(task_labels)
plt.setp(ylabels, size='medium')
def _configure_xaxis(self):
''''x axis'''
# make x axis date axis
self._ax.xaxis_date()
# format date to ticks on every 7 days
rule = mdates.rrulewrapper(mdates.DAILY, interval=7)
loc = mdates.RRuleLocator(rule)
formatter = mdates.DateFormatter("%d %b")
self._ax.xaxis.set_major_locator(loc)
self._ax.xaxis.set_major_formatter(formatter)
xlabels = self._ax.get_xticklabels()
plt.setp(xlabels, rotation=30, fontsize=9)
def _configure_figure(self):
self._configure_xaxis()
self._configure_yaxis()
self._ax.grid(True, color='gray')
self._set_legend()
self._fig.autofmt_xdate()
def _set_legend(self):
'''
Tweak font to be small and place *legend*
in the upper right corner of the figure
'''
font = font_manager.FontProperties(size='small')
self._ax.legend(loc='upper right', prop=font)
def _positions(self, count):
'''
For given *count* number of positions, get array for the positions.
'''
end = count * Gantt.POS_STEP + Gantt.POS_START
pos = np.arange(Gantt.POS_START, end, Gantt.POS_STEP)
return pos
下面的代码定义了驱动甘特图生成的主要功能。在这个函数中,我们将数据加载到一个实例中,相应地绘制条形图,为时间轴(x 轴)设置日期格式化程序,并为 y 轴(项目的任务)设置值。
def show(self):
self._plot_bars()
self._configure_figure()
plt.show()
if __name__ == '__main__':
TEST_DATA = (
{ 'label': 'Research', 'start':'2013-10-01 12:00:00', 'end': '2013-10-02 18:00:00'}, # @IgnorePep8
{ 'label': 'Compilation', 'start':'2013-10-02 09:00:00', 'end': '2013-10-02 12:00:00'}, # @IgnorePep8
{ 'label': 'Meeting #1', 'start':'2013-10-03 12:00:00', 'end': '2013-10-03 18:00:00'}, # @IgnorePep8
{ 'label': 'Design', 'start':'2013-10-04 09:00:00', 'end': '2013-10-10 13:00:00'}, # @IgnorePep8
{ 'label': 'Meeting #2', 'start':'2013-10-11 09:00:00', 'end': '2013-10-11 13:00:00'}, # @IgnorePep8
{ 'label': 'Implementation', 'start':'2013-10-12 09:00:00', 'end': '2013-10-22 13:00:00'}, # @IgnorePep8
{ 'label': 'Demo', 'start':'2013-10-23 09:00:00', 'end': '2013-10-23 13:00:00'}, # @IgnorePep8
)
gantt = Gantt(TEST_DATA)
gantt.show()
这个代码将呈现一个简单、整洁的甘特图,如下图所示:

它是如何工作的...
在检查我们是否处于"__main__"的条件之后,我们可以从底部开始读取前面的代码。
在我们实例化赋予它TEST_DATA的甘特图类之后,我们设置实例的必要字段。我们将TASK_DATA保存在self.tasks字段中,并创建我们的图形和轴来保存我们将来创建的图表。
然后我们调用实例上的show(),引导我们完成渲染甘特图所需的步骤:
def show(self):
self._plot_bars()
self._configure_figure()
plt.show()
绘制条形图需要一次迭代,我们将关于每个任务的名称和持续时间的数据应用到matplotlib.pyplot.barh函数,并将其添加到self._ax处的坐标轴。我们通过给每个任务一个不同的(递增的)底部参数值,把它放在一个单独的通道中。
此外,为了便于将任务映射到它们的名称,我们循环使用我们使用colorbrewer2.org工具生成的不同颜色的地图。
下一步是配置图形,这意味着我们在 x 轴上设置格式日期,在 y 轴上设置 tickers 的位置和标签,以匹配matplotlib.pyplot.barh绘制的任务。
我们对grid和legend进行最后的调整。
最后,我们调用plt.show()来显示图形。
制作误差线
误差线有助于显示图中数据的离散程度。作为可视化的一种形式,它们相对简单;然而,它们也有点问题,因为显示为错误的内容在不同的科学和出版物中有所不同。这并没有降低误差线的有用性,它只是强加了一种需要,那就是要始终保持谨慎,并明确说明误差线的性质。
做好准备
为了能够在原始观测数据中绘制误差线,我们需要计算平均值和要显示的误差。
我们计算的误差代表 95%的置信区间,即我们从观察中得到的平均值是稳定的,这意味着我们的观察是对整个人口的良好估计。
matplotlib 通过matplotlib.pyplot.errorbar function支持这些类型的绘图。
它提供了几个围绕误差线的功能。它们可以是垂直的(yerr)或水平的(xerr),也可以是对称的或不对称的。
怎么做...
在下面的代码中,我们将:
- 使用由四组观察值组成的样本数据。
- 对于每组观测值,计算平均值。
- 对于每组观察,计算 95%的置信区间。
- 用垂直对称误差线渲染条。
这是代码:
import matplotlib.pyplot as plt
import numpy as np
import scipy.stats as sc
TEST_DATA = np.array([[1,2,3,2,1,2,3,4,2,3,2,1,2,3,4,4,3,2,3,2,3,2,1],
[5,6,5,4,5,6,7,7,6,7,7,2,8,7,6,5,5,6,7,7,7,6,5],
[9,8,7,8,8,7,4,6,6,5,4,3,2,2,2,3,3,4,5,5,5,6,1],
[3,2,3,2,2,2,2,3,3,3,3,4,4,4,4,5,6,6,7,8,9,8,5],
])
# find mean for each of our observations
y = np.mean(TEST_DATA, axis=1, dtype=np.float64)
# and the 95% confidence interval
ci95 = np.abs(y - 1.96 * sc.sem(TEST_DATA, axis=1))
# each set is one try
tries = np.arange(0, len(y), 1.0)
# tweak grid and setup labels, limits
plt.grid(True, alpha=0.5)
plt.gca().set_xlabel('Observation #')
plt.gca().set_ylabel('Mean (+- 95% CI)')
plt.title("Observations with corresponding 95% CI as error bar.")
plt.bar(tries, y, align='center', alpha=0.2)
plt.errorbar(tries, y, yerr=ci95, fmt=None)
plt.show()
前面的代码将绘制一个带有误差线的图,误差线显示 95%的置信区间为沿 y 轴延伸的须状。记住,胡须越宽,观察到的平均值为真的概率就越小。下图是之前代码的输出:

它是如何工作的...
为了避免迭代每组观测值,我们使用 NumPy 的矢量化方法来计算平均值和标准误差,用于绘制和计算误差值。
使用用 C 语言编写的 NumPy 的矢量化实现(从 Python 中调用)可以让我们将计算速度提高几个数量级。
对于少数数据点来说,这并不十分重要,但对于数百万数据点来说,它可以成就或破坏我们创建响应应用的努力。
另外,您可能注意到我们在np.mean函数调用中明确指定了dtype=np.float 64。根据官方 NumPy 文档参考(http://docs . scipy . org/doc/NumPy/reference/generated/NumPy . mean . html),np.mean单精度使用可能不准确;最好用np.float32计算,或者如果性能不是问题,用np.float 64。
还有更多...
误差线显示的内容一直是个问题。有人建议使用 SD 、 2SD 、 SE 或 95%CI 。我们必须了解所有这些价值之间的区别以及它们的用途,以便能够对何时使用什么进行推理。
标准差告诉我们平均值周围的单个数据点的分布。如果我们假设正态分布,那么我们知道 68.2% (~2/3) 的数据值将落在 SD 之间, 95.4% 的数据值将落在 2SD* 之间。
标准误差按 SD 除以 N ( SD/√N )的平方根计算,其中 N 为数据点数。标准误差(SE)告诉我们平均值的可变性,如果我们能够不止一次地执行相同的采样(就像执行相同的研究数百次)。
置信区间由标准差计算,类似于数值范围由标准差计算。要计算 95%的置信区间,我们必须在平均值上加/减 1.96 * SE 或使用适当的符号: 95%置信区间= M (1.96 * SE) 。置信区间越宽,我们就越不确定自己是对的。
我们看到,为了确保我们的估计是正确的,并且我们正在向我们的读者给出它的证明,我们应该显示置信区间,而置信区间又带有标准误差;这一点,如果小,证明我们的手段是稳定的。
利用文本和字体属性
我们已经学会了如何通过添加传说来标注绘图,但是有时候我们想要更多的文字。这个食谱将解释和演示 matplotlib 中文本操作的更多特性,为更高级的排版需求提供一个强大的工具包。
我们不会在这个食谱中涉及 LaTeX 支持,因为在本章中有一个名为的食谱,用 LaTeX 渲染文本。
做好准备
我们首先列出 matplotlib 提供的最有用的函数集。大多数功能都可以通过pyplot模块的界面获得,但是我们将它们的原始功能映射到这里,以便在特定的文本功能没有包含在本食谱中的情况下,您可以进行更多的探索。
matplotlib OO API 中的基本文本操作及其映射如下表所示:
|matplotlib.pyplot
|
Matplotlib API
|
描述
|
| --- | --- | --- |
| text | matplotlib.axes.Axes.text | 在(x,y)指定的位置向轴添加文本。参数fontdict允许我们覆盖通用字体属性,或者我们可以使用kwargs覆盖特定属性。 |
| xlabel | matplotlib.axes.Axes.set_xlabel | 设置 x 轴的标签。根据labelpad指定标签和 x 轴之间的间距。 |
| ylabel | matplotlib.axes.Axes.set_ylabel | 类似于xlabel,但用于 y 轴。 |
| title | matplotlib.axes.Axes.set_title | 设置轴的标题。接受所有常用的文本属性,如fontdict和kwargs。 |
| suptitle | matplotlib.figure.Figure.suptitle | 向图形添加居中的标题。通过kwargs接受所有常用的文本属性。使用图形坐标。 |
| figtext | matplotlib.figure.Figure.text | 将文本放在图上的任何位置。使用图形的归一化坐标,使用x,y定义位置。使用fontdict覆盖字体属性,但也支持kwargs覆盖任何与文本相关的属性。 |
在窗口或数据坐标内存储和绘制文本的基类是matplotlib.text.Text类。它支持文本对象位置的定义以及我们可以定义的属性范围,来调整我们的字符串将如何出现在图形或窗口上。
matplotlib.text.Text实例支持的字体属性有:
财产
|
价值观念
|
描述
|
| --- | --- | --- |
| family | 'serif', 'sans-serif', 'cursive', 'fantasy', 'monospace' | 指定字体名称或字体系列。如果这是一个列表,那么它是按优先级排序的,所以将使用第一个匹配的名称。 |
| size 或fontsize | 12, 10,... or 'xx-small', 'x-small', 'small', 'medium', 'large', 'x-large', 'xx-large' | 以相对点或绝对点指定大小,或将相对大小指定为大小字符串。 |
| style 或fontstyle | 'normal', 'italic', 'oblique' | 将字体样式指定为字符串。 |
| variant | 'normal', 'small-caps' | 指定字体变体。 |
| weight 或fontweigh t | 0-1000 or 'ultralight', 'light', 'normal', 'regular', 'book', 'medium', 'roman', 'semibold', 'demibold', 'demi', 'bold', 'heavy', 'extra bold', 'black' | 指定字体粗细或使用特定的粗细字符串。字体粗细定义为字符轮廓相对于其高度的粗细。 |
| stretch 或fontstretch | 0-1000 or 'ultra-condensed', 'extra-condensed', 'condensed', 'semi-condensed', 'normal', 'semi-expanded', 'expanded', 'extra-expanded', 'ultra-expanded' | 指定字体的长度。拉伸被定义为水平凝结或膨胀。此属性当前未实现。 |
| fontproperties | | 默认为matplotlib.font_manager.FontProperties实例。此类存储和管理字体属性,如 http://www.w3.org/TR/1998/REC-CSS2-19980512/W3C CSS Level 1 规范中所述。 |
我们还可以指定将包含文本的背景框,并且可以在颜色、边框和透明度方面进一步指定。
基本的文本颜色是从rcParams['text.color']读取的,当然如果当前实例上没有指定的话。
指定的文本也可以根据视觉需求进行对齐。有以下对齐属性:
horizontalalignment或ha:此允许文本水平对齐center、left和right。verticalalignment或va:允许的值有center、top、bottom和baseline。multialignment:这个允许跨多行的文本串对齐。允许值为:left、right和center。
怎么做...
到目前为止一切都很好,但是我们很难想象我们可以创建的字体中的所有这些变化。这将说明我们能做什么。在接下来的代码中,我们将执行以下步骤:
- 列出所有我们想要改变字体的可能属性。
- 迭代第一组变化:字体系列和大小。
- 迭代第二组变量:重量和样式。
- 为两个迭代渲染文本样本,并将变体组合作为文本打印在绘图上。
- 从图形中移除轴,因为它们没有任何用途。
以下是代码:
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties
# properties:
families = ['serif', 'sans-serif', 'cursive', 'fantasy', 'monospace']
sizes = ['xx-small', 'x-small', 'small', 'medium', 'large',
'x-large', 'xx-large']
styles = ['normal', 'italic', 'oblique']
weights = ['light', 'normal', 'medium', 'semibold', 'bold', 'heavy', 'black']
variants = ['normal', 'small-caps']
fig = plt.figure(figsize=(9,17))
ax = fig.add_subplot(111)
ax.set_xlim(0,9)
ax.set_ylim(0,17)
# VAR: FAMILY, SIZE
y = 0
size = sizes[0]
style = styles[0]
weight = weights[0]
variant = variants[0]
for family in families:
x = 0
y = y + .5
for size in sizes:
y = y + .4
sample = family + " " + size
ax.text(x, y, sample, family=family, size=size,
style=style, weight=weight, variant=variant)
# VAR: STYLE, WEIGHT
y = 0
family = families[0]
size = sizes[4]
variant = variants[0]
for weight in weights:
x = 5
y = y + .5
for style in styles:
y = y + .4
sample = weight + " " + style
ax.text(x, y, sample, family=family, size=size,
style=style, weight=weight, variant=variant)
ax.set_axis_off()
plt.show()
在代码之前的将产生以下截图:

它是如何工作的...
代码非常简单,因为我们只需在打印其值的属性元组上迭代两次。
这里唯一采用的技巧是文本在图形画布上的定位,因为这允许我们有很好的文本样本布局,我们可以很容易地进行比较。
请记住,matplotlib 将使用的默认字体取决于您正在运行的操作系统,因此前面的截图可能看起来略有不同。这个截图是使用标准的 Ubuntu 13.04 安装字体渲染的。
用 LaTeX 渲染文本
如果我们想绘制更多的科学图形,并解释数学,因为它应该使用科学符号和复杂的方程在数字上,我们需要最好的支持。
虽然 matplotlib 支持数学文本渲染,但最好的支持来自 LaTeX 社区,这在使用了几十年的任务中得到了证明。
LaTeX 是生产科学和技术文件的高质量排版系统,是科学排版或出版的事实上的标准。它是一个免费软件,可在当今大多数桌面平台上作为预打包二进制安装使用;因此,很容易安装。
LaTeX 的基本语法类似于标记语言;因此,为了产生令人满意的内容,人们在写作时会更多地关注结构,而不是外观和风格。例如:
\documentclass{article}
\title{This here is a title of my document}
\author{Peter J. S. Smith}
\date{September 2013}
\begin{document}
\maketitle
Hello world, from LaTeX!
\end{document}
我们看到这与通常的文字处理器有什么不同,在文字处理器中,您有所见即所得的编辑器环境,并且样式已经应用于您的文本。有时候这很好,但是对于科学出版物来说,风格是次要的考虑因素;主要重点是拥有正确、正确和有效的内容。这里,我们所说的内容也指数学符号(通常很多),包括图表。
除此之外,还有许多功能,如自动生成书目和索引,这对大中型出版物非常重要。这些是 LaTeX 系统的主要焦点。
因为这不是一本关于 LaTeX 的书,我们就在这里简单介绍一下。更多的文档可以在位于http://latex-project.org/的项目网站上找到。
做好准备
在我们开始演示 matplotlib 对使用 LaTeX 呈现文本的支持之前,我们需要在系统上安装以下软件包:
- 乳胶系统:最常见的是特克斯活预包装配送
- DVI 到 PNG 转换器:通过产生抗锯齿的屏幕分辨率图像,使 DVI 文件中的 PNG 图形成为从 TeX 获得的
- 幽灵脚本:这个是必须的,除非已经由 TeX Live 发行版安装
不同的操作系统有不同的 LaTeX 环境预打包系统。对于基于 Linux 的系统, TeX Live 是一个完整的 TeX 系统。对于 Mac OS,推荐的环境是 MacTeX 发行版;对于 Windows 环境来说, proTeX 系统将会安装所有的 TeX 支持,包括 LaTeX。
无论您安装哪个软件包,请确保它附带了字体库和程序,用于排版、预览和打印许多不同语言的 TeX 文档。
我们将使用 Ubuntu 的texlive和dvipng软件包安装我们的 Linux 软件包。我们可以使用以下命令来安装它:
$ sudo apt-get install texlive dvipng
下一步是通过将text.usetex设置为True来告诉我们的 matplotlib 使用 LaTeX。我们可以在基于 Unix 系统的主目录(T3)中的自定义.matplotlibrc中,或者通过rcParams['text']在C:\Documents and Settings\<user>\.matplotlibrc中,或者通过使用以下代码来实现:
matplotlib.pyplot.rc('text', usetex=True)
代码的开始将告诉 matplotlib 返回到 LaTeX 进行所有文本渲染。在我们添加任何图形和轴之前,这样做很重要。
并非所有后端都支持 LaTeX 渲染。只有 Agg、PS 和 PDF 后端支持通过 LaTeX 进行文本渲染。
怎么做...
我们在这里要做的是演示 LaTeX 的基本使用属性。我们将执行以下步骤:
- 生成一些样本数据。
- 设置 matplotlib 使用 LaTeX 进行绘图会话。
- 设置要使用的字体和字体属性。
- 写出等式语法。
- 演示希腊符号语法的用法。
- 画分数和分形的数学符号。
- 写一些极限和指数表达式。
- 写出可能的范围表达式。
- 编写包含文本和格式化文本的表达式。
- 在 x 和 y 标签上写一些数学表达式作为图形标题。
以下代码将执行这些步骤:
import numpy as np
import matplotlib.pyplot as plt
# Example data
t = np.arange(0.0, 1.0 + 0.01, 0.01)
s = np.cos(4 * np.pi * t) * np.sin(np.pi*t/4) + 2
plt.rc('text', usetex=True)
plt.rc('font',**{'family':'sans-serif','sans-serif':['Helvetica'], 'size':16})
plt.plot(t, s, alpha=0.25)
# first, the equation for 's'
# note the usage of Python's raw strings
plt.annotate(r'$\cos(4 \times \pi \times {t}) \times \sin(\pi \times \frac {t} 4) + 2$', xy=(.9,2.2), xytext=(.5, 2.6), color='red', arrowprops={'arrowstyle':'->'})
# some math alphabet
plt.text(.01, 2.7, r'$\alpha, \beta, \gamma, \Gamma, \pi, \Pi, \phi, \varphi, \Phi$')
# some equation
plt.text(.01, 2.5, r'some equations $\frac{n!}{k!(n-k)!} = {n \choose k}$')
# more equations
plt.text(.01, 2.3, r'EQ1 $\lim_{x \to \infty} \exp(-x) = 0$')
# some ranges...
plt.text(.01, 2.1, r'Ranges: $( a ), [ b ], \{ c \}, | d |, \| e \|, \langle f \rangle, \lfloor g \rfloor, \lceil h \rceil$')
# you can multiply apples and oranges
plt.text(.01, 1.9, r'Text: $50 apples \times 100 oranges = lots of juice$')
plt.text(.01, 1.7, r'More text formatting: $50 \textrm{ apples} \times 100 \textbf{ apples} = \textit{lots of juice}$')
plt.text(.01, 1.5, r'Some indexing: $\beta = (\beta_1,\beta_2,\dotsc,\beta_n)$')
# we can also write on labels
plt.xlabel(r'\textbf{time} (s)')
plt.ylabel(r'\textit{y values} (W)')
# and write titles using LaTeX
plt.title(r"\TeX\ is Number "
r"$\displaystyle\sum_{n=1}^\infty\frac{-e^{i\pi}}{2^n}$!",
fontsize=16, color='gray')
# Make room for the ridiculously large title.
plt.subplots_adjust(top=0.8)
plt.savefig('tex_demo')
plt.show()
前面的代码将呈现以下文本饱和的图,该图演示了 LaTeX 呈现:

它是如何工作的...
设置渲染引擎和字体属性后,基本上使用标准 matplotlib 调用进行文本渲染,如matplotlib.pyplot.annotate、matplotlib.pyplot.text、matplotlib.pyplot.xlabel、matplotlib.pyplot.ylabel、matplotlib.pyplot.title。
这里的区别是,所有的字符串都是所谓的原始字符串,这意味着 Python 不会解释它们,也不会发生字符串替换;因此,LaTeX 引擎将接收与要执行的命令完全相同的字符串。
关于 TeX 语法以及如何在 matplotlib 中使用它的更多示例,可以在 matplotlib 官方文档中找到,网址为http://matplotlib . org/users/mathtext . html # writing-数学表达式。
请注意,这个网址不在 LaTeX 上,而是在 matplotlib 自己的集成 TeX 解析器上。这个解析器支持几乎相同的语法,甚至可以满足您的需求。
还有更多...
如果您在设置此环境时遇到问题,或者字体有不同的问题要么看起来不好,要么无法产生 LaTeX 渲染,请确保您已经安装了所有必需的软件包,您的$PATH环境变量(如果在 Windows 上)被设置为包括所有必需的二进制文件,matplotlib 被设置为使用 LaTeX 进行文本渲染。
如果遵循了所有给出的说明,结果无法复制,请参考位于http://matplotlib.org/users/usetex.html#possible-hangups的 matplotlib 官方网站和位于http://tex.stackexchange.com/的 LaTeX 社区,以获得进一步的帮助。
众所周知,这种设置并不像它应该的那样精简,并且由于各种原因可能会出现一些怪癖。
了解 pyplot 和 OO API 的区别
本食谱将尝试解释 matplotlib 中的一些编程接口,并对 pyplot 和面向对象的 API(应用编程接口)进行比较。根据手头的任务,这将允许我们决定为什么以及何时使用这些接口中的任何一个。
做好准备
matplotlib 库启动时,它类似于许多开源项目——一个人遇到的问题没有合适的(免费的)解决方案,所以他写了一个。MATLAB 遇到的问题是手头任务的性能(http://www.aosabook.org/en/matplotlib.html),原作者已经具备了 MATLAB 和 Python 的知识,所以他开始编写 matplotlib 作为当前项目需求的解决方案。
这就是 matplotlib 有一个类似于 MATLAB 的界面的主要原因,它允许人们快速绘制数据,而不用担心背景细节,matplotlib 运行在哪个平台上,底层的渲染库是什么(是在 Linux 或 Windows 上带有 GTK、Qt、TK 或 wxWidgets 的 Linux),还是我们在可可工具包的帮助下运行在 Mac OS 上。这些都隐藏在 matplotlib 中matplotlib.pyplot模块中一个漂亮的过程接口下,这是一个有状态的接口处理逻辑,用于创建图形和轴,以将它们与配置的后端连接起来。它还保存了当前图形和轴的数据结构,这些数据结构通过plot命令调用。
这是我们在这本书的大部分时间里使用的界面(matplotlib.pyplot),因为它简单、直接,并且足以完成我们试图完成的大部分任务。matplotlib 库就是根据这一理念设计的。我们必须能够用尽可能少的命令画出图,甚至只有一个命令(例如,plt.plot([1,2,3,4,5]); plt.show()起作用了!).对于这些任务,我们不想被迫考虑对象、实例、方法、属性、渲染后端、图形、画布、线条和其他图形元素。
如果你从一开始就在读这本书,你可能会注意到一些类开始出现在各种例子中;例如,FontProperties或AxesGrid,我们需要的不仅仅是matplotlib.pyplot模块提供的。
这是一个面向对象的编程接口,它实现了所有隐藏的硬东西,比如渲染图形元素,将它们渲染到平台的图形工具包,以及处理用户输入(鼠标和击键)。没有什么能阻止我们使用 OO API,这就是我们要做的。
如果我们把 matplotlib 看作一个软件,它由三部分组成:
- matplotlib.pylab 界面:这个是一组功能,用户可以像在 MATLAB 中一样创建图
- matplotlib API(也叫 matplotlib 前端):这是一套用于创建和管理图形、文字、线条、绘图等的类
- 后端:这些是画图司机;他们将前面的抽象表示转换成文件或显示设备
这个后端层包含抽象接口类的具体实现。有诸如FigureCanvas(在纸上绘图的表面)、Renderer(在画布上绘图的画笔)和Event(处理用户击键和鼠标事件的类)之类的类。
代码也是分开的。基础抽象类在matplotlib.backend_bases中,每个具体的实现都在一个单独的模块中。例如,GTK 3 后端位于matplotlib.backends.backend_gkt3agg。
在这个堆栈中,有一个Artist类的层次结构,大部分硬东西都在这里完成。Artist了解Renderer以及如何使用它在FigureCanvas上绘制图像。我们感兴趣的大多数东西(文本、线条、记号、记号标签、图像等等)都是Artist或者Artist类的子类(位于matplotlib.artist模块中)。
类matplotlib.artist.Artist包含其子类的所有共享属性:坐标变换、剪辑框、标签、用户事件处理程序和可见性。

在这个图中Artist是其他大部分职业的基数。从Artist继承的类有两个基本类别。第一类是原始艺术家,是Line2D、Rectangle、Circle、Text等可见的物体。第二类是合成艺术家,是其他Artists的集合,如Axis、Tick、Axes、Figure。比如Figure有原始艺术家Rectangle的背景,但也至少包含一个合成艺术家,Axes。
大部分的策划都发生在Axes级(matplotlib.axes.Axes)。图背景元素如记号、轴线以及背景斑块的网格和颜色包含在Axes中。Axes的另一个重要特点是,所有的辅助方法都创建其他原始艺术家,并将其添加到Axes实例中;例如plot、hist和imshow。
例如,Axes.hist创建许多matplotlib.patch.Rectangle实例,并将它们存储在Axes.patches集合中。
Axes.plot创建一个或多个matplotlib.lines.Line2D并将它们存储在Axes.lines集合中。
怎么做...
举例来说,我们将:
- 实例化 matplotlib
Path对象进行自定义绘制。 - 构建我们对象的顶点。
- 构造路径的命令代码来连接这些顶点。
- 创建补丁。
- 将其添加到
figure的Axes实例中。
以下代码实现了我们的意图:
import matplotlib.pyplot as plt
from matplotlib.path import Path
import matplotlib.patches as patches
# add figure and axes
fig = plt.figure()
ax = fig.add_subplot(111)
coords = [
(1., 0.), # start position
(0., 1.),
(0., 2.), # left side
(1., 3.),
(2., 3.),
(3., 2.), # top right corner
(3., 1.), # right side
(2., 0.),
(0., 0.), # ignored
]
line_cmds = [Path.MOVETO,
Path.LINETO,
Path.LINETO,
Path.LINETO,
Path.LINETO,
Path.LINETO,
Path.LINETO,
Path.LINETO,
Path.CLOSEPOLY,
]
# construct path
path = Path(coords, line_cmds)
# construct path patch
patch = patches.PathPatch(path, lw=1,
facecolor='#A1D99B', edgecolor='#31A354')
# add it to *ax* axes
ax.add_patch(patch)
ax.text(1.1, 1.4, 'Python', fontsize=24)
ax.set_xlim(-1, 4)
ax.set_ylim(-1, 4)
plt.show()
在代码之前的将产生以下内容:

它是如何工作的...
对于这个八边形,我们使用了基础面片matplotlib.path.Path,它支持绘制直线和曲线的基本图元集(moveto和lineto)。这些可以用来使用贝塞尔曲线绘制简单且更高级的多边形。
首先,我们在数据坐标中指定了一组坐标,我们用一组路径命令来匹配这些坐标(或者顶点,如果你喜欢的话)。我们以此为例matplotlib.path.Path。然后我们用该路径构建补丁实例matplotlib.patched.PathPatch,这是一个通用的多曲线路径补丁。
这个补丁现在可以添加到图形的轴上(T0 集合),我们可以渲染图形来显示多边形。
在这个例子中我们不想做的是直接用matplotlib.figure.Figure代替matplotlib.pyplot.figure()调用。原因是pyplot.figure()调用在后台做了很多工作,比如从matplotlibrc文件中读取rc参数(加载默认的figsize、dpi和图形颜色设置),设置图形管理器类(Gcf)等等。我们可以做所有这些,但是在我们真正知道我们在做什么之前,这是创建数字的推荐方法。
作为一般的经验法则,除非我们不能通过pyplot界面实现某件事,否则我们不应该去接触直接类,比如Figure、Axes、Axis,因为后台有很多状态管理;因此,除非我们正在开发 matplotlib,否则我们应该避免为此烦恼。
还有更多...
如果你想要交互性和探索性,最好通过 Python 交互外壳使用 matplotlib。为此,最著名的可能是 IPython pylab 模式。这为您提供了强大且内省的外壳中的所有 matplotlib 功能,具有丰富的功能集,例如历史、内嵌绘图,如果您使用 IPython Notebook,还可以共享您的工作。
IPython Notebook 是 IPython shell 的一个基于 web 的界面,在这里可以共享作品并将其转换为 HTML 或 PDF。Matplotlib 图被嵌入和内联,因此它们也可以被保存和共享。




浙公网安备 33010602011771号