Python 数据可视化基础指南
前言
最好的数据是我们能看到和理解的数据。作为开发人员,我们希望创建和构建最全面、最容易理解的可视化。它并不总是简单的;我们需要找到数据,阅读它,清洗它,然后使用正确的工具来可视化它。这本书解释了如何用简单明了的方法来阅读、清理数据并将其可视化为信息的过程。
如何读取本地数据、远程数据、CSV、JSON 以及关系数据库中的数据,本书都有讲解。
使用 matplotlib 可以在 Python 中用简单的一行代码绘制一些简单的图表,但是做更高级的图表需要的知识不仅仅是 Python。我们需要理解信息论和人类感知美学来产生最吸引人的视觉化。
本书将解释 Python 中 matplotlib 绘图背后的一些实践,使用的统计数据,以及我们应该以最佳方式使用的不同图表功能的使用示例。
第一章:准备你的工作环境
在本章中,我们将介绍以下食谱:
- 安装 matplotlib、NumPy 和 SciPy
- 安装 virtualenv 和 virtualenvwrapper
- 在 Mac OS X 上安装 matplotlib
- 在 Windows 上安装 matplotlib
- 安装用于图像处理的 Python 图像库 (PIL)
- 安装请求模块
- 在代码中自定义 matplotlib 的参数
- 为每个项目定制 matplotlib 的参数
安装 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。
执行以下步骤安装 NumPy:
- 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'
检查安装版本:
$ python -c 'import numpy; print numpy.__version__'
安装 Python-NumPy 包:
$ sudo apt-get install python-numpy
怎么做
可以通过多种方式安装 matplotlib 及其依赖项:从源代码、预编译的二进制文件、从 OS 包管理器,以及使用预打包的 python 发行版和内置的 matplotlib。
最有可能的最简单的方法是使用你的发行包管理器。对于 Ubuntu,应该是:
# in your terminal, type: $ sudo apt-get install python-numpy python-matplotlib python-scipy
如果你想在流血的边缘,最好的选择是从源头安装。这条路径包括几个步骤:获取源代码、构建需求以及配置、编译和安装。
按照以下步骤,通过从代码主机下载最新源代码:
$ 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
安装 virtualenv 和 virtualenvwrapper
如果你同时在多个项目上工作,或者甚至只是频繁地在它们之间切换,你会发现在系统范围内安装所有的东西并不是最好的选择,并且会在未来在你想要运行软件的不同系统 (生产) 上带来问题。这不是发现您缺少某个包或者已经安装在生产系统上的包之间存在版本冲突的好时机;因此,virtualenv。
virtualenv 是由 Ian Bicking 启动的一个开源项目,它使开发者能够隔离每个项目的工作环境,以便于不同包版本的维护。
怎么做
通过执行以下步骤,您可以安装 virtualenv 和 virtualenvwrapper 工具:
您可能想要将以下行添加到您的 ~/.bashrc文件中:
source /usr/local/bin/virtualenvwrapper.sh
现在可以在 virt1 里面安装我们喜欢的包了:
(virt1)user1:~$ pip install matplotlib
安装 virtualenv 和 virtualenvwrapper:
$ sudo pip install virtualenv
$ sudo pip install 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
少数有用的和最常用的命令如下:
mkvirtualenv ENV:此创建名为 ENV 的虚拟环境并激活workon ENV:这激活之前创建的 ENVdeactivate:这个让我们脱离了当前的虚拟环境
在 Mac OS X 上安装 matplotlib
在 Mac OS X 上获取 matplotlib 最简单的方法是使用预打包的 python 发行版,如 EPD。去 EPD 网站下载安装最新稳定版你的 OS 就行了。
如果您对 EPD 不满意或者因为其他原因无法使用,例如随其分发的版本,有一种手动 (阅读:hard) 方式安装 Python、matplotlib 及其依赖项。
怎么做
- 要验证安装是否有效,请在命令行中键入
python --version,您应该会在响应中看到2.7.3作为版本号。 - 安装 matplotlib:
确认一切正常。调用 Python 并执行以下命令:
import numpy
print numpy.__version__
import scipy
print scipy.__version__
quit()
Next step is what we really wanted to do all along—install matplotlib:
pip install numpy
brew install gfortran
pip install scipy
现在,安装任何所需的软件包都很容易;例如,virtualenv 和 virtualenvwrapper 都很有用:
pip install virtualenv
pip install virtualenvwrapper
你现在应该已经安装了 pip。如果没有安装,使用 easy_install添加 pip:
$ easy_install pip
现在,您需要更新您的路径 (添加到同一行):
export PATH=/usr/local/share/python:/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 所需的任何先决条件。
接下来,将自制程序目录添加到您的系统路径中,这样您使用自制程序安装的软件包比其他版本具有更高的优先级。打开 ~/.bash_profile(或 /Users/[your-user-name]/.bash_profile) 并在文件末尾添加以下一行:
export PATH=/usr/local/bin:$PATH
In your Terminal paste and execute the following command:
ruby <(curl -fsSkL raw.github.com/mxcl/homebrew/go)
命令完成后,尝试运行 brew update 或 brew doctor 来验证安装是否正常工作。
在 Windows 上安装 matplotlib
在这个食谱中,我们将演示如何安装 Python 并开始使用 matplotlib 安装。我们假设 Python 不是之前安装的。
怎么做
安装免费或商业 Python 科学发行版的建议方法就像遵循项目网站上提供的步骤一样简单。
如果您只是想开始使用 matplotlib,不想被 Python 版本和依赖关系所困扰,那么您可能想考虑使用 entsure Python 发行版 (EPD)。EPD 包含使用 matplotlib 所需的预打包库以及所有必需的依赖项 (SciPy、NumPy、IPython 等)。
像往常一样,我们下载 Windows Installer (*.exe),它将安装我们开始使用 matplotlib 所需的所有代码以及本书中的所有食谱。
还有一个免费的科学项目 Python(x,y)针对 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 的最新稳定版本二进制文件,并按照官方说明进行安装。
安装 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
在 RedHat/SciLinux 上:
# yum install python-imaging
# yum install freetype-devel
# pip install PIL
安装请求模块
我们现在需要的大多数数据都可以通过 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
在代码中自定义 matplotlib 的参数
我们在本书中最常用的图书馆是 matplotlib 它提供绘图功能。大多数属性的默认值已经在 matplotlib 的配置文件中设置,称为 .rc 文件。这个配方描述了如何从我们的应用代码中修改 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 参数
这个食谱解释了 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 轴的大刻度和小刻度的颜色、大小、方向和标签大小。
第二章:了解您的数据
在这一章中,我们将涵盖以下食谱:
- 从 CSV 导入数据
- 从微软 Excel 文件导入数据
- 从固定宽度数据文件导入数据
- 从制表符分隔的文件导入数据
- 从 JSON 资源导入数据
- 将数据导出到 JSON、CSV 和 Excel
- 从数据库导入数据
- 从异常值中清除数据
- 分块读取文件
- 读取流数据源
- 将图像数据导入 NumPy 数组
- 生成受控随机数据集
- 平滑真实数据中的噪声
从 CSV 导入数据
在这个食谱中,我们将使用在数据的狂野世界中会遇到的最常见的文件格式,CSV。它代表逗号分隔值,几乎解释了所有的格式。(文件还有一个标题部分,但是那些值也是逗号分隔的。)。
Python 有一个叫 csv 的模块,支持各种方言的 CSV 文件的读写。方言很重要,因为没有标准的 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
从微软 Excel 文件导入数据
虽然微软 Excel 支持一些图表制作,但有时候你需要更灵活更强大的可视化,需要将数据从现有的电子表格导出到 Python 中进一步使用。
从 Excel 文件中导入数据的一种常见方法是将数据从 Excel 导出到 CSV 格式的文件中,并使用前面配方中描述的工具使用 Python 从 CSV 文件中导入数据。如果我们有一个或两个文件 (并且安装了微软 Excel 或 OpenOffice.org),这是一个相当简单的过程,但是如果我们正在自动化许多文件的数据管道 (作为正在进行的数据处理工作的一部分),我们不能手动将每个 Excel 文件转换成 CSV。所以,我们需要一种读取任何 Excel 文件的方法。
Python 对通过项目读写 Excel 文件有不错的支持。这种支持是以读写不同模块的形式提供的,是平台无关的;换句话说,我们不必为了读取 Excel 文件而在 Windows 上运行。
微软 Excel 文件格式随着时间的推移而改变,不同的 Python 库支持不同的版本。XLRD 的最新稳定版本在撰写本文时是 0.90,它支持阅读 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)
从固定宽度的数据文件导入数据
事件日志文件和时间序列数据文件是数据可视化的常见来源。有时,我们可以用 CSV 方言读取制表符分隔的数据,但有时它们没有被任何特定的字符分隔。相反,字段的宽度是固定的,我们可以推断出匹配和提取数据的格式。
实现这一点的一种方法是逐行读取文件,然后使用字符串操作函数将字符串拆分成单独的部分。这种方法看起来很简单,如果性能不是问题,应该首先尝试。
如果性能更重要或者要解析的文件很大 (几百兆字节),使用 Python 模块 struct 可以加快速度,因为该模块是用 C 实现的,而不是用 Python 实现的。
怎么做
我们将使用具有一百万行固定宽度记录的预生成数据集。以下是示例数据:
… 207152670398435680411695324270531801466959270421533831670088597261315325444920138359697328651524421074004769531360921567802830421421342037064593625911780546 …
现在我们可以读取数据了。我们可以使用下面的代码示例。我们将:
- 定义要读取的数据文件。
- 定义如何读取数据的掩码。
- 使用掩码逐行读取,将每行解包到单独的数据字段中。
将每行打印为单独的字段。
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]
从制表符分隔的文件导入数据
平面数据文件的另一种非常常见的格式是制表符分隔的文件。这也可以来自 Excel 导出,但也可以是一些自定义软件的输出,我们必须从这些软件中获取输入。
好的一点是,通常这种格式可以以几乎与 CSV 文件相同的方式读取,因为 Python 模块 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
从 JSON 资源导入数据
这个食谱将告诉我们如何读取 JSON 数据格式。此外,我们将在本食谱中使用远程资源。它会给食谱增加一点点复杂性,但它会让它变得更有用,因为在现实生活中,我们会遇到比本地更多的远程资源。
JavaScript 对象符号 (JSON) 作为一种独立于平台的格式被广泛用于系统或应用之间的数据交换。
在这种情况下,资源是我们可以读取的任何东西,无论是文件还是网址端点 (可以是远程进程/程序的输出,也可以只是远程静态文件)。简而言之,我们不在乎谁生产了一种资源,如何生产;我们只需要它采用已知的格式,比如 JSON。
怎么做
下面的代码示例从 GitHub 站点读取并解析最近的活动时间线。我们将为此执行以下步骤:
- 定义 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)
将数据导出到 JSON、CSV、Excel
而作为数据可视化的生产者,我们大多使用别人的数据;导入和读取数据是主要活动。我们确实需要编写或导出我们生产或处理的数据,无论数据是供我们或他人当前或未来使用。
我们将演示如何使用前面提到的 Python 模块来导入、导出和将数据写入各种格式,如 JSON、CSV 和 XLSX。
出于演示目的,我们使用从固定宽度数据文件导入数据的 预生成数据集。
怎么做
我们将展示一个包含我们想要演示的所有格式的代码示例:CSV、JSON 和 XLSX。程序的主要部分接受输入并调用适当的函数来转换数据。我们将遍历代码的不同部分,解释它的用途。
最后,我们有主代码入口点,在这里我们从命令行解析类似参数的文件,以导入数据并将其导出为所需的格式。
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 must 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)
我们单独为每个数据格式 (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()
从数据库导入数据
很多时候,我们在数据分析和可视化方面的工作是在数据管道的消费者端。我们通常使用已经产生的数据,而不是自己产生数据。例如,现代应用在关系数据库 (或其他数据库) 中保存不同的数据集,我们使用这些数据库并生成漂亮的图形。
这个食谱将向您展示如何使用 Python 中的 SQL 驱动程序来访问数据。
我们将使用一个 SQLite 数据库演示这个方法,因为它需要最少的设置工作,但是界面类似于大多数其他基于 SQL 的数据库引擎 (MySQL 和 PostgreSQL)。然而,这些数据库引擎支持的 SQL 方言存在差异。此示例使用简单的 SQL 语言,并且应该可以在大多数常见的 SQL 数据库引擎上重现。
怎么做
为了能够读取数据库,我们需要:
- 连接到数据库引擎 (或者在 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
将数据导入数据库后,我们就可以查询数据并进行一些处理。下面是从数据库文件中读取数据的代码:
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
从异常值中清除数据
这个食谱描述了如何处理来自现实世界的数据集,以及如何在可视化之前清理它们。
我们将展示几种技术,本质上不同,但是有相同的目标,那就是清理数据。
然而,清洁不应该是全自动的。在我们应用任何强大的现代算法来清理数据之前,我们需要理解给定的数据,并且能够理解什么是异常值以及数据点代表什么。这不是一个可以在食谱中定义的东西,因为它依赖于大量的领域,如统计数据、领域知识和良好的眼光 (然后是一些运气)。
怎么做
这里有一个例子,展示了如何使用 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()
plt.show()
识别异常值的另一种方法是目视检查您的数据。为了做到这一点,我们可以创建散点图,在散点图中,我们可以很容易地发现中央群体之外的值。我们也可以做一个方框图,里面会显示的中位数,中位数上下的四分位数,以及距离这个方框较远的点。
该框从数据的下四分位数延伸到上四分位数,中间有一条线。触须从方框中延伸出来,显示数据的范围。飞人点是那些超过胡须末端的点。
这里有一个例子来证明:
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 轴的所有值都在 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 字符串操作函数来执行操作。
分块读取文件
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
读取流数据源
如果来源的数据是连续的呢?如果我们需要读取连续数据怎么办?这个方法将展示一个简单的解决方案,它将适用于许多常见的现实场景,尽管它不是通用的,如果您在应用中遇到特殊情况,您将需要修改它。
怎么做
在这个食谱中,我们将向您展示如何读取一个总是变化的文件并打印输出。我们将使用常见的 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,
将图像数据导入 NumPy 数组
我们将在演示如何使用 Python 的库如 NumPy 和 SciPy 来进行图像处理。
在科学的计算中,图像通常被视为 n 维数组。它们通常是二维数组;在我们的示例中,它们被表示为 NumPy 数组数据结构。因此,在这些结构上执行的功能和操作被视为矩阵操作。
从这个意义上说,图像并不总是二维的。对于医学或生物科学,图像是更高维度的数据结构,例如 3D(以 z 轴作为深度或时间轴) 或 4D(以三个空间维度和一个时间维度作为第四维度)。我们不会在这个食谱中使用这些。
我们可以使用各种技术导入图像;它们都取决于你想用图像做什么。此外,这还取决于你所使用的工具的更大的生态系统,以及你运行你的项目的平台。
在这个食谱中,我们将演示几种在 Python 中使用图像处理的方法,主要与科学处理有关,较少涉及图像处理的艺术方面。
怎么做
无论谁在数字信号处理领域工作过,甚至上过这方面或相关学科的大学课程,都一定遇到过 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()
此外,我们可以用下面的代码来检查这个对象:
print lena.shape
print lena.max()
print lena.dtype
这里我们看到的图像是:
- 宽 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()
生成受控随机数据集
在这个食谱中,我们将展示生成随机数序列和单词序列的不同方法。有些示例使用标准 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,您可以使用从各种免费来源构建的文件 (古登堡计划、维基词典、英国国家语料库或彼得·诺维格博士的网站)。
平滑真实数据中的噪声
在这个食谱中,我们引入了一些高级算法来帮助清理来自真实世界来源的数据。这些算法在信号处理领域是众所周知的,我们不会深入数学,而只是举例说明它们是如何以及为什么工作的,以及它们可以用于什么目的。
怎么做
基本算法基于使用滚动窗口 (例如卷积)。该窗口滚动数据,用于计算该窗口的平均值。
对于我们的离散数据,我们使用 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 的例子。
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 的大部分可能性。我们将涵盖:
- 定义绘图类型 - 条形图、折线图和堆叠图
- 绘制简单的正弦和余弦曲线
- 定义轴长度和限制
- 定义绘图线样式、特性和格式字符串
- 设置记号、标签和网格
- 添加图例和标注
- 将脊椎移向中心
- 制作直方图
- 用误差线制作条形图
- 让饼图有价值
- 用填充区域绘图
- 用彩色标记绘制散点图
定义绘图类型–条形图、折线图和堆叠图
在这个食谱中,我们将展示不同的基本绘图以及它们的用途。这里描述的大部分图都是日常使用的,其中一些图为理解更多数据可视化高级概念提供了基础。
怎么做
我们从在 IPython 中创建一个简单的图开始。IPython 非常棒,因为它允许我们交互式地更改绘图,并立即看到结果。
然后输入 matplotlib plot 代码:
In [1]: plot([1,2,3,2,3,2,2,1])
Out[1]: [<matplotlib.lines.Line2D at 0x412fb50>]
通过在命令提示符下键入以下命令启动 IPython:
$ ipython --pylab
该图应在新窗口中打开,显示图的默认外观和一些支持信息。
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()
这就是应该如何将从变成的图形。
绘制简单的正弦和余弦图
本食谱将复习绘制数学函数的基础知识以及与数学图形相关的几件事,例如在标签和曲线上写希腊符号。
怎么做
我们从开始,计算同一线性区间内的正弦和余弦函数——从π到π,中间有 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 中进行配置。
怎么做
开始尝试轴的各种属性。只需调用一个空的 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 来定义垂直跨度的宽度。
定义绘图线样式、特性和格式字符串
这个食谱展示了我们如何改变各种线条属性,例如样式、颜色或宽度。根据所呈现的信息,适当设置线条,并使其足够清晰以适合目标观众 (如果观众是较年轻的人群,我们可能希望用更鲜艳的颜色来瞄准他们;如果他们年龄较大,我们可能希望使用更多对比色) 可以在几乎不引人注意和给观众留下很大影响之间产生差异。
怎么做
让我们学习如何更改线条属性。我们可以用不同的方法改变我们的绘图。
首先也是最常见的是通过将关键字参数传递给函数来定义行,如 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.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 章、制作三维可视化 中,我们有处理三维直方图的食谱。
怎么做
我们用一组参数创建一个名为 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 格式字符串混合在一起。
用误差线制作条形图
在本食谱中,我们将展示如何创建条形图以及如何绘制误差线。
怎么做
即使只有两个参数是强制的— 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 轴开始。
这是生成的饼图。
绘制填充区域
在本食谱中,我们将向您展示如何填充曲线下或两条不同曲线之间的区域。
怎么做
下面是一个如何填充两个轮廓之间区域的示例:
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() 提供了用颜色或阴影填充任何多边形的能力。
用彩色标记绘制散点图
如果你有两个变量,并想找出它们之间的相关性,散点图可能是点模式的解决方案。
这种类型的绘图也非常适合作为多维数据的更高级可视化的起点,例如,绘制散点图矩阵。
怎么做
下面是一个代码示例,绘制了两个图:一个具有不相关的数据,另一个具有强正相关:
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 一维数组,一维数组的长度与散点图相同。

