Python-数据可视化秘籍-二-

102 阅读1小时+

Python 数据可视化秘籍(二)

原文:Python Data Visualization Cookbook

协议:CC BY-NC-SA 4.0

四、更多绘图和自定义

在本章中,我们将了解:

  • 设置轴标签的透明度和大小
  • 给图表线条添加阴影
  • 向图中添加数据表
  • 使用子图
  • 自定义网格
  • 创建等高线图
  • 填充地下区域
  • 绘制极坐标图
  • 使用极坐标条可视化文件系统树

简介

在本章中,我们将探索 matplolib 库的更高级的属性。我们将介绍更多的选择,并将研究如何获得某些视觉上令人愉悦的结果。

在这一章中,我们将寻求在简单的图表还不够时,用数据表示一些非平凡问题的解决方案。我们将尝试使用多种类型的图形或创建混合图形来覆盖一些高级数据结构和所需的表示。

设置轴标签的透明度和大小

Axes标签描述了图中的数据所代表的内容,对于观众理解图本身非常重要。通过为axes背景提供标签,我们帮助观众以适当的方式理解中的信息。

做好准备

在我们深入研究代码之前,了解 matplotlib 如何组织我们的数据是很重要的。

在顶层,有一个Figure实例,它包含了我们看到的一切和更多的东西(我们没有看到的)。该图包含了Axes类作为字段Figure.axes的实例。Axes实例包含我们关心的几乎所有东西:所有的线、点、记号和标签。所以,当我们呼叫plot()时,我们正在Axes.lines列表中添加一行(matplotlib.lines.Line2D)。如果我们绘制直方图(hist()),我们将在Axes.patches列表中添加矩形(“面片”是从 MATLAB 继承而来的术语,代表“颜色面片”的概念)。

Axes的一个实例还包含对XAxisYAxis实例的引用,它们又分别引用 x 轴和 y 轴。XAxisYAxis管理轴、标签、刻度、刻度标签、定位器和格式化器的绘制。我们可以分别通过Axes.xaxisAxes.yaxis来参考。我们不必一直到XAxisYAxis实例才能到达标签,因为 matplotlib 为我们提供了一个辅助方法(实际上是一个快捷方式),可以通过这些标签实现迭代:matplotlib.pyplot.xlabel()matplotlib.pyplot.ylabel()

怎么做...

我们现在将创建一个新的数字,我们将:

  1. 用一些随机生成的数据创建一个图。

  2. 添加titleaxes标签。

  3. 添加 alpha 设置。

  4. titleaxes标签添加阴影效果。

    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=Nonepatch_alpha=0.7。其他数值有centertopbaseline但是我们选择bottom作为文字会有一些阴影。下一行我们添加阴影效果。路径效果是支持matplotlib.text.Textmatplotlib.patches.Patchmatplotlib模块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,所以坐标相对于您的显示坐标系总是以像素为单位。这不是很有用,大多数情况下,我们希望将它们归一化到FigureAxesData坐标系中。

这个框架使我们能够将当前对象转换为偏移对象,也就是说,将该对象放置在与原始对象偏移一定距离的位置。

我们将使用这个框架在绘制的正弦波上创建我们想要的效果。

怎么做...

以下是为绘制的图表添加阴影的代码配方。代码将在下一节中解释。

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

dxdy的值是以点定义的,由于点是 1/72 英寸,我们将偏移对象向右移动 2pt,向下移动 2pt。

如果你想了解更多关于我们如何将该点转换为 1/71 英寸的信息,请阅读维基百科的这篇文章:en.wikipedia.org/wiki/Point_…

我们可以用matplotlib.transforms.ScaledTransformation(xtr, ytr, scaletr);这里,xtrytr是平移偏移,scaletr是变换时间和显示前可调用缩放xtrytr的变换。这方面最常见的使用情形是从点转换到显示空间:例如,转换到 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()

先前的代码片段给出了如下图:

How to do it...

它是如何工作的...

使用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,其中tablematplotlib.table.Table的实例。

使用子图

如果你从一开始就阅读这本书,你可能对subplot类很熟悉,它是axes的后代,生活在subplot实例的规则网格上。我们将解释并演示如何以高级方式使用子图。

在这个食谱中,我们将学习如何在我们的绘图中创建自定义的子绘图配置。

做好准备

子图的基类是matplotlib.axes.SubplotBase。这些子绘图是matplotlib.axes.Axes实例,但提供了在图形中生成和操纵一组Axes的辅助方法。

有一个类matplotlib.figure.SubplotParams,保存subplot的所有参数。尺寸标准化为图形的宽度或高度。正如我们已经知道的,如果我们不指定任何自定义值,它们将从rc参数中读取。

脚本层(matplotlib.pyplot)包含一些辅助方法来操作子场景。

matplotlib.pyplot.subplots用于轻松创建子图的通用布局。我们可以指定网格的大小——子图网格的行数和列数。

我们可以创建共享 x 轴或 y 轴的子场景。这是使用sharexsharey关键字参数实现的。参数sharex可以有值True,在这种情况下,x 轴在所有子绘图中共享。除了最后一行图,刻度标签在所有图上都不可见。也可以定义为字符串,枚举值为rowcolallnone。数值allTrue相同,数值noneFalse相同。如果指定了值row,则每个子图行共享 x 轴。如果指定了值col,则每个子图列共享 x 轴。该助手返回元组fig, ax,其中ax或者是轴实例,或者,如果创建了多个子图,则是轴实例的数组。

matplotlib.pyplot.subplots_adjust用于调整子图布局。关键字参数指定图形内的子图坐标(leftrightbottomtop)归一化为图形大小。分别使用宽度和高度的wspacehspace参数,可以指定在子绘图之间留出空白。

怎么做...

  1. 我们将向您展示在 matplotlib 工具包中使用另一个助手函数的示例— subplot2grid。我们定义网格的几何形状和子图的位置。请注意,这个位置是从 0 开始的,而不是像我们在plot.subplot()中习惯的从 1 开始。我们还可以使用colspanrowspan来允许子绘图跨越给定网格中的多列和多行。例如,我们将:创建一个图形;使用subplot2grid添加各种子图布局;重新配置刻度标签大小。

  2. 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)以及可选的rowspancolspan。这里重要的区别是位置是从 0 开始索引的,而不是像在figure.add_subplot那样从 1 开始。

还有更多...

举一个另一种方式的例子,你可以自定义当前的axessubplot:

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>]

Getting ready

现在我们可以在同一个图上切换网格:

In [2]: plt.grid()

我们重新打开网格,如下图所示:

Getting ready

然后我们再次关闭 if:

In [3]: plt.grid()

Getting ready

除了打开和关闭它们,我们还可以进一步定制网格外观。

我们可以仅使用主要刻度或次要刻度或两者来操纵网格;因此,函数参数which的值可以是'major''minor''both'。与此类似,我们可以使用参数axis分别控制水平和垂直刻度,该参数可以有值'x''y''both'

所有其他属性都通过kwargs传递,并表示一个matplotlib.lines.Line2D实例可以接受的标准属性集,例如colorlinestylelinewidth;这里有一个例子:

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()

给定的代码将呈现以下图表:

How to do it...

它是如何工作的...

在函数get_demo_image中,我们从 matplotlib 附带的样本数据目录中加载数据。

列表grid保存了我们的axes网格(这里是ImageGrid)。

变量image1image2image3保存了我们在列表grid中的多个轴上分割的来自 Z 的切片数据。

在所有网格上循环,我们使用标准的imshow()调用绘制来自im1im2im3的数据,而 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 的轮廓。阵列XY是(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 的行数

怎么做...

在下面的代码示例中,我们将:

  1. 实现一个功能作为模拟信号处理器。

  2. 生成一些线性信号数据。

  3. 将数据转换成适合矩阵运算的矩阵。

  4. 绘制等高线。

  5. 添加等高线标签。

  6. 展示绘图。

  7. 将 numpy 导入为np

  8. 将 matplotlib 汇入为mpl

  9. 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()
    

这将为我们提供以下图表:

How to do it...

它是如何工作的...

我们从numpy开始寻找小帮手来创建我们的范围和矩阵。

在我们将my_function评估为Z之后,我们简单的称之为contour,提供Z和等值线的层数。

此时,尝试在N arange()调用中尝试第三个参数。例如,不要选择N = np.arange(-1, 1.5, 0.3),而是尝试将0.3更改为0.11来体验如何以不同的方式看待相同的数据,这取决于我们如何在等高线图中对数据进行编码。

我们还通过简单地给它CS(一个matplotlib.contour.QuadContourSet实例)添加了一个彩色地图。

填充绘图下区域

在 matplotlib 中绘制填充多边形的基本方式是使用matplotlib.pyplot.fill。该函数接受与matplotlib.pyplot.plot相似的参数——多个xy对以及其他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 轴数组,以及y1y2—数据的 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()

前面的代码给出了下面的图:

How to do it...

这相当简单,并给出了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()

前面的代码将呈现如下图:

How to do it...

它是如何工作的...

对于这个例子,我们首先创建了两个在某些点重叠的正弦函数。

我们还创建了两个子场景来比较渲染填充区域的两个变体。

在这两种情况下,我们都将fill_between()与参数where一起使用,该参数接受一个 N 长度的布尔数组,并将填充where等于True的区域。

底部的子图显示了mask_greater,它用大于给定值的值屏蔽了一个数组。这是numpy.ma包中的一个函数,用于处理缺失或无效的值。我们在底部的轴上转动网格,以便更容易发现这一点。

绘制极坐标图

如果数据已经用极坐标表示,我们也可以用极坐标图形显示。即使数据不是极坐标,也要考虑转换成极坐标形式,在极坐标图上绘制。

要回答我们是否要这样做,我们需要了解数据代表什么,我们希望向最终用户显示什么。想象用户将从我们的图形中读取和解码什么,通常会引导我们达到最佳的可视化效果。

极坐标图通常用于显示本质上呈放射状的信息。例如,在太阳路径图中——我们在径向投影中看到天空,天线的辐射图在不同角度辐射不同。你可以在以下网站了解更多信息:T2。

在本食谱中,我们将学习如何更改绘图中使用的坐标系,并改用极坐标系统。

做好准备

要在极坐标中显示数据,我们必须有适当的数据值。在极坐标系统中,一个点用半径距离(通常用 r 表示)和角度(通常用θ表示)来描述。角度可以是弧度或度数,但 matplotlib 使用度数。

与函数plot()非常相似,为了绘制极坐标图,我们将使用函数polar() ,该函数接受两个相同长度的参数数组thetar,分别用于角度数组和半径数组。该函数还接受其他格式参数,与plot()函数相同。

我们还需要告诉 matplotlib,我们想要极坐标系统中的轴。这是通过向add_axesadd_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()

前面的代码片段将给出下面的图:

How to do it...

它是如何工作的...

首先,我们创建一个正方形图形,并添加极轴。图形不一定是正方形,但这样我们的极坐标图就会是椭球形。

然后,我们为一组角度(θ)和一组极距(半径)生成随机值。因为我们画了条,我们也需要为每个条设置一组宽度,所以我们也生成了一组宽度。由于maplotlib.axes.bar接受一组值(就像 matplotlib 中几乎所有的绘图函数一样),我们不必循环这个生成的数据集;我们只需要把所有的论据传递给律师协会一次。

为了让每一个小节都容易区分,我们必须循环添加到ax(轴)的每一个小节,并自定义其外观(面色和透明度)。

使用极坐标条可视化文件系统树

我们想在这个食谱中展示如何解决一个“现实世界”的任务——如何使用 matplotlib 来可视化我们的目录占用率。

在这个食谱中,我们将学习如何可视化具有相对大小的文件系统树。

做好准备

我们都有很大的硬盘,里面有时会有我们通常会忘记的东西。如果能看到这样一个目录里面有什么,里面最大的文件是什么,那就太好了。

虽然有许多更复杂和精细的软件产品可以完成这项工作,但我们想展示如何使用 Python 和 matplotlib 来实现这一点。

怎么做...

让我们执行以下步骤:

  1. 实现一些助手函数来处理文件夹发现和内部数据结构。

  2. 实现主功能draw(),进行绘图。

  3. 实现验证用户输入参数的主程序主体:

    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()
    
  4. 接下来,我们将实现主程序主体,在这里我们验证当从命令行调用程序时用户给出的输入参数:

    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/

它将产生一个类似于这个的绘图:

How to do it...

它是如何工作的...

我们将从代码的底部开始,在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指定xsyszszdir参数。其他全部直接转入matplotlib.axes.Axes.plot。我们将解释这些具体的论点:

  • xsys:这是 x 轴和 y 轴的坐标
  • zs:这是 z 轴的值。它可以是所有点的一个,也可以是每个点的一个
  • zdir:这将选择 z 轴尺寸(通常这是zs,但可以是xsys)

模块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()

在代码之前的产生如下图:

How to do it...

它是如何工作的...

我们必须做和 2D 世界一样的准备工作。不同的是,我们需要指定后端的类型。然后我们生成一些随机数据,例如,4 年的销售(2011-2014 年)。

我们需要为三维轴指定相同的 Z 值。

我们从色图集中随机选择一种颜色,然后我们关联每一个 Z 顺序的xsys对集合,我们将渲染条形图系列。

还有更多...

其他来自 2D matplotlib 的绘图可在此获得;例如,界面与plot()相似的scatter() ,但是增加了点标记的大小。我们也熟悉contour``contourfbar

仅在 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()

在代码之前的将给出以下输出:

There's more...

创建三维直方图

与 3D 条类似,我们可能希望创建 3D 直方图。它们用来很容易地发现三个独立变量之间的相关性。它们可用于从图像中提取信息,其中第三维可以是被分析图像的(x,y)空间中的通道强度。

在这个食谱中,我们将学习如何创建三维直方图。

做好准备

回想一下,直方图表示某个值在特定列(通常称为“bin”)中出现的次数。三维直方图代表网格中出现的次数。这个网格是矩形的,包含两个变量,这两个变量是两列中的数据。

怎么做...

对于该计算,我们将:

  1. 使用 NumPy,因为它具有计算两个变量直方图的功能。
  2. 从正态分布中生成 x 和 y,但使用不同的参数,以便能够在生成的直方图中区分相关性。
  3. 绘制同一数据集的散点图,以展示散点图与三维直方图的显示差异。

下面是实现所述步骤的代码示例:

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()

在代码之前的将给出以下输出:

How to do it...

它是如何工作的...

我们使用np.histogram2d准备一个计算机直方图,该直方图返回我们的直方图(hist)和 x 和 y 面元边缘。

因为对于bard3d函数我们需要 x,y 空间中的坐标,所以我们需要计算公共矩阵坐标,为此我们使用np.meshgrid,它将 x 和 y 位置向量组合到 2D 空间网格(矩阵)中。我们可以用它来绘制 xy 平面位置的条。

变量dxdy表示每个条的底部宽度,我们希望使其恒定,因此我们给 xy 平面中每个位置一个 0.1 点的值。

z 轴(dz)中的值实际上是我们的计算机直方图(在变量hist中),它表示特定仓中常见 x 和 y 样本的计数。

下面的散点图(在前面的图中)显示了 2D 轴,该轴也显示了两个相似分布之间的相关性,但具有不同的起始参数集。

有时候,3D 给了我们更多的信息,并以更好的方式与数据中包含的内容产生了共鸣。由于 3D 可视化往往比 2D 更令人困惑,建议我们在选择它们而不是 2D 之前三思。

在 matplotlib 中制作动画

在这份食谱中,我们将探索如何使我们的图表栩栩如生。有时候让图片在动画中移动来解释当我们改变变量的值时发生了什么是更具描述性的。我们的主库有有限但通常足够的动画功能,我们将解释如何使用它们。

做好准备

从 1.1 版本开始,标准 matplotlib 增加了一个动画框架,它的主要类是matplotlib.animation.Animation。此类是基类;与已经提供的类TimedAnimationArtistAnimationFuncAnimation的情况一样,它将针对特定行为被子类化。下表给出了这些类的描述:

|

类名(父类)

|

描述

| | --- | --- | | 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 格式的现代视频播放器打开。图(框架)应该如下图所示:

How to do it...

它是如何工作的...

最重要的是功能init()animate()save()。我们首先通过传递两个回调函数initanimate来构造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)

前面的代码将产生以下具有旋转图形的窗口:

How to do it...

它是如何工作的...

我们生成数据集,并为 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(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))或者所见即所得(en.wikipedia.org/wiki/WYSIWY…)来达到同样的目的,为什么还要用 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):这将调整图像大小,并使用过滤器进行重采样。可能的过滤器有NEARESTBILINEARBICUBICANTIALIAS。默认为NEAREST
  • im.rotate(angle, filter):逆时针方向旋转图像。
  • im.split():这个分割图像的波段,并返回单个波段的元组。用于将一个 RGB 图像分割成三个单波段图像。
  • im.transform(size, method, data, filter):这将使用数据和过滤器对给定图像进行变换。变身可以是AFFINEEXTENTQUADMESH。您可以在官方文档中阅读更多关于转换的内容。数据定义了原始图像中将应用变换的框。

ImageDraw模块允许我们在图像上绘制,在这里我们可以使用圆弧、椭圆、直线、切片、点和多边形等功能来修改加载图像的像素。

ImageChops模块包含多个图像通道操作(因此得名Chops),可用于图像合成、绘画、特殊效果和其他处理操作。通道操作仅允许用于 8 位图像。以下是一些有趣的频道操作:

  • ImageChops.duplicate(image):这将当前图像复制到新的图像对象中
  • ImageChops.invert(image):这将反转图像并返回副本
  • ImageChops.difference(image1, image2):这有助于验证图像是否相同,无需目视检查

ImageFilter模块包含内核类的实现,允许创建自定义卷积内核。该模块还包含一组健康的通用滤镜,允许将知名滤镜(BLURMedianFilter)应用到我们的图像中。

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()

前面的代码应该给出如下的图:

How to do it...

它是如何工作的...

我们首先创建一个大小合适的图形,即 16 x 8。我们需要这个尺寸来适合我们想要显示的图像。现在,我们使用csv模块从文件中加载数据。实例化csv读取器对象,我们可以逐行迭代文件中的数据。注意第一行是多么特别,它是描述我们的列的标题。由于我们在 x 轴上绘制了年,在 y 轴上绘制了温度,我们看到:

xlabel, ylabel, _ = header

并使用以下行:

plt.xlabel(xlabel)
plt.ylabel(ylabel)

我们在这里使用了整洁的 Python 约定将头解包为三个变量,其中通过使用_作为变量名,我们表明我们对该变量的值不感兴趣。

我们将 load_data功能中的headerdatarows列表返回给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的大部分论点都是一样的——我们调整xyboxpad以将文本定位到行的对面。文本在TextArea类实例中,这类似于我们对图像所做的,但是文本time.TextAreaOffsetImage继承自同一个父类OffsetBox

我们将这个TextArea实例中的文本设置为no_pirates,并将其放入我们的AnnotationBbox中。

显示带有图中其他图的图像

这个食谱将展示我们如何简单而有效地使用 Python matplotlib 库来处理图像通道和显示外部图像的每个通道直方图。

做好准备

我们已经提供了一些示例图像,但是代码已经准备好加载任何图像文件,前提是它得到 matplotlib 的imread函数的支持。

在本食谱中,我们将学习如何组合不同的 matplotlib 图,以实现简单图像查看器的功能,该查看器显示红色、绿色和蓝色通道的图像直方图。

怎么做...

为了展示如何构建一个图像直方图查看器,我们将实现一个名为ImageViewer的简单类,该类将包含帮助器方法,用于:

  1. 加载图像。
  2. 从图像矩阵中分离出 RGB 通道。
  3. 配置图形和轴(子场景)。
  4. 绘制通道直方图。
  5. 绘制图像。

下面的代码展示了如何构建一个图像直方图查看器:

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 记号,并添加一个图例,以防我们想要在非彩色环境下打印此图。因此,即使在这些环境中,我们也可以辨别通道表示。

运行此代码后,我们将获得以下截图:

How it works...

还有更多...

直方图绘图类型的使用只是图像查看器示例的一种选择。我们可以使用 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 坐标对指定:

  1. 我们实例化Basemap定义要使用的投影(merc为墨卡托)。
  2. 我们为地图的左下角和右上角定义(在同一个Basemap构造函数中)经度和纬度。
  3. 我们设置Basemap实例地图,来绘制海岸线和国家。
  4. 我们设置Basemap实例地图来填充大陆并绘制地图边界。
  5. 我们指示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()

这将给我们的地球一个可识别的部分:

How to do it...

既然我们知道如何绘制地图,我们就需要知道如何在地图上绘制数据。如果我们回想一下Basemap是当前地图投影中经度和纬度对的大代码转换器,我们会意识到我们所需要的是一个包含 long/lat 的数据集,在用 matplotlib 绘制之前,我们将其传递给Basemap进行投影。我们使用cities.shpcities.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工具包的功能表面,更多的例子可以在matplotlib.org/basemap/use…的官方文档中找到。

官方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文件,我们可以在自己喜欢的网页浏览器中打开。页面应该如下图所示:

How to do it...

它是如何工作的...

这里的主要切入点是我们的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_divgeochart实例:

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 的图像库来生成图像、渲染线和点,以及渲染文本。

怎么做...

我们将通过执行以下步骤来展示创建个人简单验证码生成器的过程:

  1. 定义大小、文本、字体大小、背景颜色和验证码长度。
  2. 从英语字母表中随机挑选字符。
  3. 使用定义的字体和颜色在图像上绘制。
  4. 以线条和弧线的形式添加一些噪点。
  5. 将图像对象和验证码一起返回给呼叫者。
  6. 向用户显示生成的图像。

下面的代码展示了如何创建一个个人和简单的验证码生成器:

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()

这将生成类似于以下内容的图像:

How to do it...

它是如何工作的...

这个例子给出了一个如何使用 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(www.google.com/recaptcha)等事件 web 服务,有已经验证的 Python 模块reCAPTCHA-client(pypi.python.org/pypi/recapt…)可以注册使用。它不需要任何图像库,因为图像是直接从 reCAPTCHA web 服务中提取的,但它有其他依赖项,如pycrypto。通过使用这个网络服务和图书馆,您还可以帮助使用来自谷歌图书项目或旧版本《纽约时报》的光学字符识别(OCR)扫描的书籍。在 reCAPTCHA 网站上阅读更多内容。**

七、使用正确的绘图来理解数据

在这一章中,我们将涵盖以下食谱:

  • 理解对数图
  • 理解光谱图
  • 创建主干图
  • 绘制矢量流的流线
  • 使用彩色地图
  • 使用散点图和直方图
  • 绘制两个变量之间的互相关
  • 自相关的重要性

简介

在这一章中,我们将更加专注于理解我们想用我们呈现的数据说什么,以及如何有效地说出来。我们将展示一些新的技术和绘图,但所有这些都将通过理解我们想要传达给用户的信息来强调。让我们问一个问题,“为什么我们要在这种状态下呈现信息?”。这是在数据探索阶段应该问的最重要的问题。如果我们错过了理解数据并以某种方式呈现数据的机会,那么观众肯定无法正确理解数据。

理解对数图

更多时候不是,看日报和类似的文章,一就能找到被媒体机构用来歪曲事实的图表。一个常见的例子是使用线性标度来创建所谓的恐慌图,其中一个不断增长的值跟随很长一段时间(年),并且起始值比最近的值小几个数量级。当这些值被正确地可视化时,将会(并且通常应该)产生线性或几乎线性的图表,从它们所展示的文章中消除一些恐慌。

做好准备

对于对数刻度,连续值的比率是恒定的。当我们试图读取日志图时,这一点很重要。对于线性(算术)标度,常数是连续值之间的距离。换句话说,对数图在数量级上具有恒定的距离。我们将在下面的图中看到这一点。接下来解释用于产生这个数字的代码。

作为一般经验法则,对数刻度应在以下情况下使用:

  • 当呈现的数据具有跨越几个数量级的值时
  • 当呈现的数据向大值倾斜时(某些点比其余数据大得多)
  • 当你想显示变化率(增长率),而不是变化值时

不要盲目遵循这些规则;它们更像是暗示,而不是规则。对于手头的数据和项目或客户向您提出的要求,始终使用您自己的判断。

根据数据范围,应使用不同的日志库。日志的标准基数是 10,但是如果数据范围较小,基数为 2 会更有用,因为它会在较小的范围内显示更多的分辨率。

如果我们有适合在对数标度上显示的数据范围,我们会注意到,以前过于接近而无法判断任何差异的值现在相距甚远。这使我们能够比以线性比例呈现数据更容易地阅读图表。

在收集长期时间序列数据的增长率图表中,我们希望看到的不是在某个时间点测量的绝对值,而是时间上的增长。我们仍将获得绝对值信息,但该信息的优先级较低。

此外,如果数据分布具有正偏斜,例如薪水,取值(薪水)的对数将有助于我们将数据拟合到模型中,因为对数变换将给出更正态的数据分布。

怎么做...

我们将用一个示例代码来举例说明这一点,该代码使用不同的比例(线性和对数)在两个不同的图上显示相同的两个数据集(一个是线性的,一个是对数的)。

我们将借助这些步骤后提到的代码来执行以下步骤:

  1. 生成两个简单的数据集:y-指数/对数性质和 z-线性。
  2. 创建一个包含四个子绘图网格的图形。
  3. 创建两个包含 y 数据集的子图:一个是对数标度,一个是线性标度。
  4. 创建另外两个包含 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()

该代码将产生以下输出:

How to do it...

它是如何工作的...

我们生成一些样本数据和两个因变量: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模块。

我们还可以安装libasoundALSA ( 高级 Linux 声音架构)头文件到避免运行时警告。这是可选的,因为我们不会使用 ALSA 图书馆提供的功能。对于 Ubuntu Linux,发出以下命令:

$ sudo apt-get install libasound2-dev

要安装scikits.audiolab,我们将使用它来读取 WAV 文件,我们将使用pip:

$ pip install scikits.audiolab

始终记得进入当前项目的虚拟环境,因为您不想弄脏系统库。

怎么做...

对于这个食谱,我们将使用预先录制的声音文件test.wav,它可以在本书的文件库中找到。但是我们也可以生成一个样本,稍后我们会尝试。

在本例中,我们按顺序执行以下步骤:

  1. 读取包含录音样本的 WAV 文件。

  2. 使用NFFT定义傅里叶变换所用窗口的长度。

  3. Define the overlapping data points using noverlap while 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()完成的,它接受开始和结束帧。我们通过将采样率乘以我们想要可视化的时间点(startend)来计算帧数。

还有更多...

如果找不到音频(波),可以轻松生成一个。以下是如何生成它:

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 轴表示信号的频率。

There's more...

创建主干图

二维茎图将数据显示为从基线沿 x 轴延伸的线。一个圆(默认)或其他标记,其 y 轴代表数据值,终止每个茎。

在这个食谱中,我们将讨论如何创建一个主干图。

不要将茎图与茎叶图混淆,茎叶图是一种通过将值的最后一个重要数字分离为叶,将较高阶的值分离为茎来表示数据的方法。

Creating a stem plot

做好准备

对于这种图,我们希望使用一系列离散数据,而普通的线图无论如何都没有意义。

将离散序列绘制为主干,其中数据值表示为每个主干末端的标记。主干从基线(通常在 y = 0)延伸到数据点值。

怎么做...

我们将使用 matplotlib 使用stem()函数绘制主干图。当 x 值作为从 0 到 len(y) - 1 的简单序列生成时,该函数可以仅使用一系列 y 值。如果我们为stem()函数提供 x 和 y 序列,它们将用于两个轴。

我们要用主干图配置的是几个格式化程序:

  • linefmt:这是茎线的线格式器
  • markerfmt:行尾的茎用这个参数格式化
  • basefmt:这会格式化基线的外观
  • label:这定义了干图图例的标签
  • hold:保存当前坐标轴上的当前图形
  • bottom:设置基线位置在 y 轴上的位置,默认值为 0

参数保持被用作图的常用特征。如果打开(True),以下所有图都将添加到当前轴。否则,每个图将创建新的图形和轴。

要创建主干图,请执行以下步骤:

  1. 生成随机噪声数据。
  2. 配置阀杆选项。
  3. 画出主干。

下面是实现它的代码:

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()

先前的代码产生了如下图:

How to do it...

它是如何工作的...

首先,我们需要一些数据。对于这个配方,生成的采样伪信号就足够了。在现实世界中,任何离散的顺序数据都可以使用主干图进行适当的可视化。我们使用 Numpy 的numpy.linspacenumpy.cosnumpy.sin功能生成该信号。

然后我们为主干图和基线位置设置一个标签,默认为 0.0

如果我们想要绘制多个主干图,我们会将hold设置为True,并且生成的图调用将在同一组轴上渲染。

matplotlib.stem的调用返回三个对象。首先是markerline,一个Line2D的例子。这保存了对表示茎本身的线的引用,只呈现标记,而不呈现连接标记的线。通过编辑Line2D实例的属性,可以使该行可见;这个过程将很快解释。最后一个也是一个Line2D实例,baseline,引用了一条代表stemlines来源的水平线。返回的第二个对象是stemlines,这当然是表示茎线的Line2D实例的集合(此时是 Python 列表)。

我们使用这些返回的对象来操纵主干图的视觉吸引力,使用setp函数将属性应用于这些对象中的所有线条(T1)实例,或者对象的集合。

尝试想要的设置,直到你理解setp如何改变你的绘图风格。

绘制矢量流的流线

流图用于可视化矢量场中的流。《科学与自然》中的例子包括磁力和重力领域以及液体物质的运动。

矢量场可以这样可视化,我们给每个点指定一条线和一个或多个箭头。强度可以用线的长度来表示,方向可以用指向特定方向的箭头来表示。

通常,力的强度随着特定流线的长度而可视化,但是密度也可以用于相同的目的。

做好准备

为了可视化矢量场,我们将使用 matplotlib 的matplotlib.pyplot.streamplot函数。这个函数从一个流的流线创建图,均匀地填充区域。速度场被插值,流线被积分。该功能的原始来源是可视化风模式或液体流动;因此,我们不需要严格的向量线,而是向量场的统一表示。

这个函数最重要的参数是(XY),它们是一维 NumPy 阵列的均匀间隔网格,以及(UV),它们匹配(XY)速度的二维 NumPy 阵列。矩阵UV的尺寸必须保证行数与Y的长度相等,列数必须与X的长度匹配。

如果给linewidth参数一个匹配 u 和 v 速度形状的二维数组,或者它可以只是一个所有行都接受的整数值,则可以控制每一行的流图的线宽。

颜色也可以只是所有流线的一个值和一个类似linewidth参数的矩阵。

箭头(类别FancyArrowPatch)用于指示矢量方向,我们可以使用两个参数来控制它们:arrowsize更改箭头的大小,arrowstyle更改箭头的格式(例如"simple", "->"...).

怎么做...

我们将从一个简单的例子开始,只是为了了解这里发生了什么。请执行以下步骤:

  1. 创建数据向量。
  2. 打印中间值。
  3. 绘制溪流图。
  4. 用流线显示我们的矢量。

以下是代码示例:

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\.        ]])

而它也生成了如下流线流程图:

How to do it...

它是如何工作的...

我们通过使用 NumPy 的mgrid实例索引二维网格来创建一个 X 和 Y 的矢量场。我们将网格的范围指定为开始和停止(分别为-2 和 2)。第三个索引代表步长。步长表示起点和终点之间包含的点数。如果我们想包含停止值,我们使用一个复数作为步长,其中幅度用于开始和停止之间所需的点的数量,停止是包含的。

网格,像这样充实,然后用来计算矢量速度。这里,为了举例,我们只使用相同的 meshgrid属性作为矢量速度。这将生成一个图,清楚地显示简单的线性相关性和所表示的矢量场的流动。

玩转UV的值,了解一下UV的值是如何影响溪流绘图的。比如做U = np.sin(X)或者V = np.sin(Y)。接下来,尝试更改开始和停止值。查看U = np.sin(X)如下图:

How it works...

记住,绘图是一组生成的线条和箭头补丁;因此,没有办法(至少目前)更新现有的图,因为线和箭头对向量和场一无所知。未来的实现可能会包括这一点,但目前这是 matplotlib 当前版本的一个已知限制。

还有更多...

当然,这个例子只是提供了一个了解和理解 matplotlib 的流图特性和功能的机会。

真正的力量来自于你手头有真正的数据可以玩。在理解了这个方法之后,你将能够认识到你有什么工具,这样当你得到数据并且你知道它的领域时,你将能够为这项工作选择最好的工具。

使用彩色地图

对数据进行颜色编码会对观看者如何感知你的视觉效果产生很大的影响,因为它们带有关于颜色和颜色代表什么的假设。

显而易见,如果颜色被用来给数据增加额外的信息,它总是好的。知道何时以及如何在可视化中使用颜色更好。

做好准备

如果您的数据不是自然颜色编码的(如地球/地形高度或物体温度),最好不要进行任何自然颜色的人工映射。我们希望正确理解数据,因此选择颜色来帮助读者轻松解码数据。如果我们表示的金融数据与凯尔温斯或摄氏温度无关,我们不希望读者不断试图压制学习的温度颜色映射。

如果可能的话,如果数据中没有很强的相关性,避免通常的红色/绿色关联,将它们与那些颜色关联起来。

为了帮助您选择正确的颜色映射,我们将解释matplotlib包中提供的一些颜色映射,如果您知道它们用于什么以及如何找到它们,这些颜色映射可以节省大量时间并帮助您。

彩色地图通常可分为以下几类:

  • 顺序:这个代表同一个颜色从低到高饱和度的两个色调的单色色图,比如从白色到亮蓝色。这在大多数情况下是理想的,因为它们清楚地显示了从低值到高值的变化。
  • 发散:这代表中心点,是中间值(通常是一些浅色),但是范围在高值和低值的方向上有两个不同的色调。这对于具有显著中值的数据可能是理想的;例如,当中位数为 0 时,它清楚地显示了负值和正值之间的差异。
  • 定性:对于的情况,数据没有固有的排序,你所要做的就是保证不同的类别之间容易辨别,这就是要选择的色图。
  • 循环:这个使用起来很方便,数据可以环绕端点值,例如,表示一天中的时间、风向或相位角。

matplotlib 附带了很多预定义的地图,我们可以将它们分为几类。我们将建议何时使用这些颜色图。最常见的基础色图是autumnbonecoolcopperflaggrayhothsvjetpinkprismsprintsummerwinterspectral

我们还有一组来自约克科学可视化软件包的彩色地图。这是从 GIST 包演变而来的,所以这个集合中的所有颜色图都有gist_作为它们名称的前缀。

Yorick 科学可视化包也是用 C 语言编写的解释语言,最近不太活跃。你可以在它的官方网站yorick.sourceforge.net/index.php找到更多信息。

这些颜色映射集包含以下映射:gist_earthgist_heatgist_ncargist_rainbowgist_stern

然后,我们有了基于 color brewer(colorbrewer.org)的颜色图,在这里我们可以将它们分类为以下几类:

  • 发散:这个是亮度在中点最高,向不同端点降低的地方
  • 连续:这是亮度单调下降的地方
  • 定性:这是使用不同颜色集合来区分数据类别的地方

此外,还有一些其他颜色图可供使用:

|

Colormap(颜色映射)

|

描述

| | --- | --- | | brg | 这将代表一个发散的蓝-红-绿颜色图。 | | bwr | 这将代表一个发散的蓝-白-红颜色图。 | | coolwarm | 这对于三维着色、色盲和颜色排序非常有用。 | | rainbow | 这表示具有发散亮度的光谱紫-蓝-绿-黄-橙-红颜色图。 | | seismic | 这代表了一个发散的蓝-白-红颜色图。 | | terrain | 这代表了 Mapmaker 的颜色——蓝色、绿色、黄色、棕色和白色——最初来自 IGOR Pro 软件。 |

这里展示的大部分地图可以通过将_r后缀放在颜色地图的名称后来反转,例如hot_rhot的反转循环颜色地图。

怎么做...

我们可以在 matplotlib 中的许多项目上设置颜色映射。例如,可以在imagepcolorscatter上设置色图。这通常通过函数调用的参数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函数,该函数返回所有已注册颜色图的列表。

最后,我们想告诉你如何制作一个好看的彩色地图。在以下示例中,我们需要:

  1. 导航到 ColorBrewer 网站,以十六进制格式获取不同的颜色映射颜色值。

  2. 生成 x 和 y 的随机样本,其中 y 为数值的累计和(模拟股价变化)。

  3. 将自定义应用于 matplotlib 的散点图功能。

  4. 调整散点图标记线的颜色和宽度,使图对观察者来说更加易读和令人愉快。

    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()
    

前面的代码将呈现一个漂亮的图形:

How to do it...

它是如何工作的...

我们使用 ColorBrewer 网站找出红-黄-绿发散色图中的颜色。然后,我们在代码中列出了这些颜色,并将其应用到散点图中。

ColorBrewer 是由辛西娅·布鲁尔马克·哈罗威宾夕法尼亚州立大学为探索彩色地图而开发的网络工具。这是一个非常方便的工具,可以提取不同范围的彩色地图,并使用微小的变化将它们应用到地图上,这样您就可以立即感觉到它们在图表上的样子。该特定地图位于 colorbrewer2.org/index.php?t… & n=9](colorbrewer2.org/index.php?t…) 。

有时,我们必须在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]

我们需要执行以下步骤:

  1. 关键字flowers使用谷歌趋势搜索量一年的干净数据集;我们将这个数据集导入变量d
  2. 使用与我们的谷歌趋势数据集相同长度(365 个数据点)的随机正态分布;这将是数据集d1
  3. 创建一个包含四个子绘图的绘图。
  4. 在第一个子绘图中,绘制dd1的散点图。
  5. 在第二个子绘图中,用d1绘制d1的散点图。
  6. 在第三个子图中,用反转的d1渲染d1的散点图。
  7. 在第四个子图中,使用由d1d组合构建的相似数据集渲染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()

下面的是我们在执行前面的代码时应该得到的输出:

How to do it...

它是如何工作的...

我们在前面的输出中看到的示例清楚地显示了不同数据集之间是否存在任何相关性。虽然第二个子图(右上)显示了数据集d1d1本身的理想或完美的正相关(很明显),但我们可以看到第四个子图(右下)暗示了正相关,尽管不理想。我们从d1d(随机)构建了这个数据集来模拟两个相似的信号(事件),而使用dd1绘制的第二个子图中有一定的随机性(或噪声),但仍然可以与原始(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)

先前的代码应产生以下输出:

There's more...

绘制两个变量之间的互相关

如果我们有来自两个不同观测的两个不同数据集,我们想知道这两个事件集是否相关。我们想交叉关联它们,看看它们是否有任何匹配。我们正在寻找一种在更大的数据样本中包含更小的数据样本的模式。模式不一定是明显或琐碎的模式。

做好准备

我们可以使用 pyplot 实验室的 matplot libmatplotlib.pyplot.xcorr函数。该函数可以绘制两个数据集之间的相关性,这样我们就可以看到绘制的值之间是否有任何显著的模式。假设xy长度相同。

如果我们将参数normed作为True传递,我们可以在第 0 个滞后时(即没有时间延迟或时间滞后时)通过互相关进行归一化。

在幕后,关联是使用 NumPy 的numpy.correlate功能完成的。

使用参数usevlines(将其设置为True),我们可以指示 matplotlib 使用vlines()而不是plot()来绘制相关图的线条。主要区别在于,如果我们使用的是plot(),我们可以使用在**kwargs参数中传递给matplotlib.pyplot.xcorr函数的标准Line2D属性来设置线条的样式。

怎么做...

在以下示例中,我们需要执行以下步骤:

  1. 导入matplotlib.pyplot模块。
  2. 导入numpy包。
  3. 对关键词使用一年谷歌搜索量趋势的干净数据集。
  4. 绘制数据集(真实的和人工的)和互相关图。
  5. 收紧布局,以便更好地了解标签和刻度。
  6. 添加适当的标签和网格,以便于理解绘图。

以下是将执行上述步骤的代码:

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()

之前的代码将呈现以下输出:

How to do it...

它是如何工作的...

我们使用了一个真实数据集,其中有一个可识别的模式(两个峰值以相似的方式在整个数据集上重复;参考前面的图)。另一个数据集只是一些随机的正态分布数据,其长度与公共服务谷歌趋势的实际累积数据相同。

我们将两个数据集绘制在输出的上半部分,以可视化数据。

使用 matplotlib 的xcorr,反过来使用 NumPy 的correlate()函数,我们计算互相关,并将其绘制在屏幕的下半部分。

NumPy 中的互相关计算返回一个相关系数数组,该数组表示两个数据集(或信号,如果用于信号处理领域,通常称为信号)的相似程度。

互相关图(correlogram)告诉我们,这两个信号是不相关的,这由相关值的高度(出现在特定时间滞后的垂直线)来表示。我们可以看到不止一条垂直线(时滞 n 时的相关系数)在 0.5 以上。

例如,如果两个数据集在 100 的时间延迟时具有相关性(即,由两个不同传感器观察到的同一对象之间的 100 秒移动),我们将在前面的输出中看到 x = 100 的垂直线(代表相关系数)。

自相关的重要性

自相关表示在连续的时间间隔内,给定的时间序列和自身的滞后(即时间延迟)版本之间的相似程度。它发生在时间序列研究中,当与给定时间周期相关的误差延续到未来的时间周期。例如,如果我们预测股票股息的增长,一年的高估很可能导致未来几年的高估。

时间序列分析数据出现在许多不同的科学应用和金融过程中。一些例子包括:生成的金融业绩报告、一段时间内的价格、计算波动性等。

如果我们在分析未知数据,自相关可以帮助我们检测数据是否随机。为此,我们可以使用相关图。它可以帮助提供问题的答案,例如:数据是随机的吗?这个时间序列数据是白噪声信号吗?是正弦曲线吗?是自回归吗?这个时间序列数据的模型是什么?

做好准备

我们将使用 matplotlib 来比较两组数据。一个是某个关键词一年(365 天)搜索量的谷歌日趋势。另一组是正态分布的 365 个随机测量值(生成的随机数据)。

我们将自动校正两个数据集,并比较相关图如何可视化数据中的模式。

怎么做...

在本节中,我们将执行以下步骤:

  1. 导入matplotlib.pyplot模块。
  2. 导入numpy包。
  3. 使用一年谷歌搜索量的干净数据集。
  4. 绘制数据集并绘制其自相关图。
  5. 使用 NumPy 生成相同长度的随机数据集。
  6. 在同一图上绘制随机数据集,并绘制其自相关图。
  7. 添加适当的标签和网格,以便于理解绘图。

以下是代码:

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()

先前的代码将呈现以下输出:

How to do it...

它是如何工作的...

看左边的图,很容易发现搜索量数据中的模式,其中左下方的图具有模式不明显但仍然可能存在的正态分布随机数据。

计算和绘制随机数据的自相关,我们可以看到在 0 处有很高的相关性,这是预期的,数据与自身相关,没有任何时滞。但走前走后没有时间滞后,信号几乎为 0。因此,我们可以有把握地得出结论,原始时间的信号与所检查的任何时间滞后之间没有相关性。

看看真实的数据,谷歌搜索量的趋势,我们可以看到相同的行为在零时滞,仍然是我们可以期待的任何自相关信号。但是在零时差后的 30、60 和 110 天左右,我们有强烈的信号。这表明这个特定的搜索词和人们在谷歌搜索引擎上搜索它的方式有一种模式。

我们将把解释为什么这是一个非常不同的故事的练习留给读者。记住相关性和因果关系是两个截然不同的东西。

还有更多...

当我们想要识别未知数据的模型时,自相关经常被使用。当我们试图将数据拟合到模型中时,数据如何与其自身相关有时是为我们所看到的数据集确定合适模型的第一步。这需要的不仅仅是 Python 它需要数学建模和各种统计测试(Ljung-Box 测试、Box-Pierce 测试等)的知识,这些知识将帮助我们回答我们可能遇到的任何问题。