NumPy 秘籍第二版 二、高级索引和数组概念

233 阅读14分钟
原文链接: gitee.com

二、高级索引和数组概念

原文:Chapter 2. Advanced Indexing and Array Concepts

译者:飞龙

协议:CC BY-NC-SA 4.0

感谢一译的支持

在本章中,我们将介绍以下秘籍:

  • 安装SciPy
  • 安装PIL
  • 调整图像大小
  • 比较视图和副本
  • 翻转Lena
  • 花式索引
  • 位置列表索引
  • 布尔值索引
  • 数独的步长技巧
  • 数组广播

简介

NumPy以其高效的数组而闻名。 这种名声部分归因于方便的索引。 我们将使用图像演示高级索引技巧。 在深入索引之前,我们将安装必要的软件-SciPy和PIL。 如果您认为有必要,请查看第一章中的“安装 matplotlib”秘籍。

在本章和其他章节中,我们将使用以下导入:

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

我们还将尽可能使用print(),Python函数的最新语法。

注意

Python 2是一个仍然流行的Python主要版本,但它与Python 3不兼容。 Python 2正式支持到2020年。 其中一个主要区别是print()函数的语法。 本书使用尽可能与Python 2和Python 3兼容的代码。

本章中的一些示例涉及操纵图像。 为了实现它,我们将需要Python 图像库PIL),但不要担心;必要时,本章将给出帮助您安装PIL和其他必要Python软件的说明和指示。

安装SciPy

SciPy是科学的 Python库,与NumPy密切相关。 实际上,SciPy和NumPy曾经是多年前的同一个项目。 SciPy就像NumPy一样,是一个在BSD协议下的开源项目。 在这个秘籍中,我们将安装SciPy。 SciPy提供高级功能,包括统计,信号处理,线性代数,优化,FFT,ODE求解器,插值,特殊功能和积分。 与NumPy有一些重叠,但NumPy主要提供数组功能。

做好准备

在第一章中,我们讨论了如何安装setuptoolspip。 必要时重新阅读秘籍。

操作步骤

在本文中,我们将介绍安装SciPy的步骤:

  • 从源码安装:如果安装了Git,则可以使用以下命令克隆SciPy仓库:

    $ git clone https://github.com/scipy/scipy.git
    
    $ python setup.py build
    $ python setup.py install --user 
    
    

    这会将SciPy安装到您的主目录。 它需要Python 2.6或更高版本。

    在构建之前,您还需要安装SciPy所依赖的以下软件包:

    • BLASLAPACK
    • C和Fortran编译器

    您可能已将此软件安装为 NumPy安装的一部分。

  • 在 Linux 上安装 SciPy:大多数Linux发行版都有SciPy包。 对于某些流行的Linux发行版,我们将执行必要的步骤(您可能需要以root身份登录或具有sudo权限):

    • 要在Red Hat,Fedora和CentOS上安装SciPy,请从命令行运行以下指令:

      $ yum install python-scipy
      
      
    • 要在Mandriva上安装SciPy,请运行以下命令行指令:

      $ urpmi python-scipy
      
      
    • 要在Gentoo上安装SciPy,请运行以下命令行指令:

      $ sudo emerge scipy
      
      
    • 在Debian或Ubuntu上,我们需要输入以下指令:

      $ sudo apt-get install python-scipy
      
      
  • 在 Mac OS X 上安装 SciPy:Apple Developer Tools(XCode)是必需的,因为包含BLASLAPACK 它可以在App Store或Mac附带的安装DVD中找到;或者您可以从Apple Developer 网站获取最新版本。 确保已安装所有内容,包括所有可选包。

    您可能已为NumPy安装了Fortran编译器。 gfortran的二进制文件可以在r.research.att.com/tools/找到。

  • 使用easy_install或者pip安装 SciPy:您可以使用这两个命令安装SciPy(取决于权限需要sudo):

    $ [sudo] pip install scipy
    $ [sudo] easy_install scipy
    
    
  • 在 Windows 上安装:如果您已经安装了Python,首选方法是下载并使用二进制发行版。 或者,您可以安装Anaconda或Enthought Python 发行版,它随附其他科学Python软件包。

  • 检查你的安装:使用以下代码检查 SciPy安装:

    import scipy
    print(scipy.__version__)
    print(scipy.__file__)
    

    这应该打印正确的SciPy版本。

工作原理

大多数包管理器会为您处理依赖关系(如果有的话)。 但是,在某些情况下,您需要手动安装它们。 这超出了本书的范围。

另见

如果您遇到问题,可以向我们寻求帮助:

安装PIL

PIL是Python 图像库,是本章图像处理秘籍的先决条件。 如果您愿意,可以安装Pillow,它是PIL的一个分支。 有些人更喜欢Pillow API;但是,我们不打算在本书中介绍它的安装。

操作步骤

我们来看看如何安装PIL:

  • 在 Windows 上安装 PIL:使用PIL网站上的Windows可执行文件

  • 在 Debian 或 Ubuntu 上安装:在 Debian 或Ubuntu上,使用以下命令安装PIL:

    $ sudo apt-get install python-imaging
    
    
  • 使用easy_installpip进行安装:在编写本书时,似乎Red Hat,Fedora和CentOS的软件包管理器没有直接支持PIL。因此,如果您使用这些Linux发行版之一,请执行此步骤。

    使用以下任一命令安装:

    $ easy_install PIL
    $ sudo pip install PIL
    
    

另见

调整图像大小

注意

在这个秘籍中,我们会将SciPy发行版中可用的Lena样本图像加载到一个数组中。 顺便说一下,本章不涉及图像处理;我们将仅使用图像数据作为输入。

Lena Soderberg出现在1972年的“花花公子”杂志上。 由于历史原因,这些图像中的一个经常用于图像处理领域。 别担心;问题中的图像用于工作是完全安全的。

我们将使用repeat()函数调整图像大小。 此函数重复一个数组,这意味着在我们的用例中通过某个因子调整图像大小。

做好准备

此秘籍的先决条件是安装SciPy,matplotlib和PIL。 请查看本章中的相应秘籍和第一章。

操作步骤

使用以下步骤调整图像大小:

  1. 首先,导入SciPy。 SciPy具有lena()函数。 它用于将图像加载到NumPy数组中:

    lena = scipy.misc.lena()
    
    

    自版本0.10以来发生了一些重构,因此如果您使用的是旧版本,则正确的代码如下:

    lena = scipy.lena()
    
  2. 使用numpy.testing包中的assert_equal()函数检查Lena数组的形状 - 这是一个可选的健全性检查测试:

    np.testing.assert_equal((LENA_X, LENA_Y), lena.shape)
    
  3. 使用repeat()函数调整Lena数组的大小。 我们在xy方向给这个函数一个调整大小因子:

    resized = lena.repeat(yfactor, axis=0).repeat(xfactor, axis=1)
    
  4. 我们将在两个子图中绘制 Lena图像和调整大小的图像,这两个子图是同一网格的一部分。 使用以下代码在一个子图中绘制Lena数组:

    plt.subplot(211)
    plt.title("Lena")
    plt.axis("off")
    plt.imshow(lena)
    

    matplotlib subplot()函数创建一个子图。 此函数接受一个三位整数作为参数,其中第一个数字是行数,第二个数字是列数,最后一个数字是子图的索引,从1开始。 imshow()函数显示图像。 最后,show()函数显示最终结果。

    在另一个子图中绘制调整大小的数组并显示它。 索引现在为2:

    plt.subplot(212)
    plt.title("Resized")
    plt.axis("off")
    plt.imshow(resized)
    plt.show()
    

    以下截图显示了原始图像(第一个)和调整大小的图像(第二个)的结果:

    How to do it...

    以下是此秘籍的完整代码,在本书代码包中resize_lena.py文件中:

    import scipy.misc
    import matplotlib.pyplot as plt
    import numpy as np
    
    # This script resizes the Lena image from Scipy.
    
    # Loads the Lena image into an array
    lena = scipy.misc.lena()
    
    #Lena's dimensions
    LENA_X = 512
    LENA_Y = 512
    
    #Check the shape of the Lena array
    np.testing.assert_equal((LENA_X, LENA_Y), lena.shape)
    
    # Set the resize factors
    yfactor = 2
    xfactor = 3
    
    # Resize the Lena array
    resized = lena.repeat(yfactor, axis=0).repeat(xfactor, axis=1)
    
    #Check the shape of the resized array
    np.testing.assert_equal((yfactor * LENA_Y, xfactor * LENA_Y), resized.shape)
    
    # Plot the Lena array
    plt.subplot(211)
    plt.title("Lena")
    plt.axis("off")
    plt.imshow(lena)
    
    #Plot the resized array
    plt.subplot(212)
    plt.title("Resized")
    plt.axis("off")
    plt.imshow(resized)
    plt.show()
    

工作原理

repeat()函数重复数组,这里会导致更改原始图像的大小。 subplot() matplotlib函数创建一个子图。 imshow()函数显示图像。 最后,show()函数显示最终结果。

另见

  • 第一章中的“安装 matplotlib”。
  • 安装 SciPy
  • 安装 PIL_
  • repeat() 函数

创建视图和副本

重要的是知道我们何时处理共享的数组视图,以及何时有数组数据的副本。 例如,切片将创建一个视图。 这意味着如果将切片赋给变量然后更改源码数组,则此变量的值将更改。 我们将从着名的Lena图像创建一个数组,复制数组,创建一个视图,最后修改视图。

做好准备

先决条件与上一个秘籍相同。

操作步骤

让我们创建一个Lena数组的副本和视图:

  1. 创建Lena数组的副本:

    acopy = lena.copy()
    
    
  2. 创建数组视图:

    aview = lena.view()
    
    
  3. 使用flat迭代器将视图的所有值设置为0

    aview.flat = 0
    
    

    最终结果是只有一个图像(与副本相关的图像)显示了花花公子模特。 其他图像完全消失:

    How to do it...

    以下是本教程的代码,显示了数组视图和副本的行为,在本书代码包中copy_view.py文件中:

    import scipy.misc
    import matplotlib.pyplot as plt
    
    lena = scipy.misc.lena()
    acopy = lena.copy()
    aview = lena.view()
    
    # Plot the Lena array
    plt.subplot(221)
    plt.imshow(lena)
    
    #Plot the copy
    plt.subplot(222)
    plt.imshow(acopy)
    
    #Plot the view
    plt.subplot(223)
    plt.imshow(aview)
    
    # Plot the view after changes
    aview.flat = 0
    plt.subplot(224)
    plt.imshow(aview)
    
    plt.show()
    

工作原理

如您所见,通过更改程序末尾的视图,我们更改了原始Lena数组。 这产生了三个蓝色(如果您正在查看黑白图像,则为空白)图像 - 复制的数组不受影响。 重要的是要记住视图不是只读的。

另见

翻转Lena

当然,我们将以科学的名义翻转 SciPy Lena图像,或者至少作为演示。 除了翻转图像外,我们还会对其进行切片并对其应用蒙版。

操作步骤

步骤如下:

  1. 使用以下代码围绕纵轴翻转Lena数组:

    plt.imshow(lena[:,::-1])
    
    
  2. 从图像中取出切片并绘制图像。 在这一步中,我们将看一下Lena数组的形状。 形状是表示数组尺寸的元组。 以下代码有效地选择了花花公子图片的左上部分:

    plt.imshow(lena[:lena.shape[0]/2,:lena.shape[1]/2])
    
    
  3. 通过查找Lena数组中偶数的所有值(这对于演示目的而言是任意的),将掩码应用于图像。 复制数组并将偶数值更改为0。 这样可以在图像上放置大量蓝点(如果您正在查看黑白图像,则会出现黑点):

    mask = lena % 2 == 0
    masked_lena = lena.copy()
    masked_lena[mask] = 0
    
    

    所有这些工作都会产生一个2 x 2的图像网格,如下面的截图所示:

    How to do it...

    以下是本书代码包中flip_lena.py文件的完整代码:

    import scipy.misc
    import matplotlib.pyplot as plt
    
    # Load the Lena array
    lena = scipy.misc.lena()
    
    # Plot the Lena array
    plt.subplot(221)
    plt.title('Original')
    plt.axis('off')
    plt.imshow(lena)
    
    #Plot the flipped array
    plt.subplot(222)
    plt.title('Flipped')
    plt.axis('off')
    plt.imshow(lena[:,::-1])
    
    #Plot a slice array
    plt.subplot(223)
    plt.title('Sliced')
    plt.axis('off')
    plt.imshow(lena[:lena.shape[0]/2,:lena.shape[1]/2])
    
    # Apply a mask
    mask = lena % 2 == 0
    masked_lena = lena.copy()
    masked_lena[mask] = 0
    plt.subplot(224)
    plt.title('Masked')
    plt.axis('off')
    plt.imshow(masked_lena)
    
    plt.show()
    

另见

  • 第一章中的“安装 matplotlib”
  • 安装 SciPy
  • 安装 PIL

花式索引

在本教程中,我们将应用花式索引,将Lena图像的对角线值设置为0。 这将沿着对角线画出黑线,穿过它,不是因为图像有问题,而是作为练习。 花式索引是不涉及整数或切片的索引;这是正常的索引。

操作步骤

我们将从第一个对角线开始:

  1. 将主对角线的值设置为0

    要将对角线值设置为0,我们需要为xy值定义两个不同的范围:

    lena[range(xmax), range(ymax)] = 0
    
    
  2. 将次对角线的值设置为0

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

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

    最后,我们获取标记对角线的图像,如以下截图所示:

    How to do it...

    以下是本书代码包中fancy.py文件的完整代码:

    import scipy.misc
    import matplotlib.pyplot as plt
    
    # This script demonstrates fancy indexing by setting values
    # on the diagonals to 0.
    
    # Load the Lena array
    lena = scipy.misc.lena()
    xmax = lena.shape[0]
    ymax = lena.shape[1]
    
    # Fancy indexing
    # Set values on diagonal to 0
    # x 0-xmax
    # y 0-ymax
    lena[range(xmax), range(ymax)] = 0
    
    # Set values on other diagonal to 0
    # x xmax-0
    # y 0-ymax
    lena[range(xmax-1,-1,-1), range(ymax)] = 0
    
    # Plot Lena with diagonal lines set to 0
    plt.imshow(lena)
    plt.show()
    

工作原理

我们为x值和y值定义了单独的范围。 这些范围用于索引Lena数组。 基于内部NumPy迭代器对象执行花式索引。 执行以下步骤:

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

另见

使用位置列表建立索引

使用ix_()函数来重新调整Lena图像。 此函数从多个序列创建网格。

操作步骤

我们将从随机打乱数组索引开始:

  1. 使用numpy.random模块的shuffle()函数创建随机索引数组:

    def shuffle_indices(size):
       arr = np.arange(size)
       np.random.shuffle(arr)
    
       return arr
    
  2. 绘制打乱的索引:

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

    我们得到的是一个完全混乱的Lena图像,如下面的截图所示:

    How to do it...

    以下是秘籍的完整代码,在本书代码包中ix.py文件中:

    import scipy.misc
    import matplotlib.pyplot as plt
    import numpy as np
    
    # Load the Lena array
    lena = scipy.misc.lena()
    xmax = lena.shape[0]
    ymax = lena.shape[1]
    
    def shuffle_indices(size):
       '''
       Shuffles an array with values 0 - size
       '''
       arr = np.arange(size)
       np.random.shuffle(arr)
    
       return arr
    
    xindices = shuffle_indices(xmax)
    np.testing.assert_equal(len(xindices), xmax)
    yindices = shuffle_indices(ymax)
    np.testing.assert_equal(len(yindices), ymax)
    
    # Plot Lena
    plt.imshow(lena[np.ix_(xindices, yindices)])
    plt.show()
    

另见

使用布尔值进行索引

布尔索引是基于布尔数组的索引,属于花式索引的类别。

操作步骤

我们将这种索引技术应用于图像:

  1. 图像与对角线上的点。

    这在某些方面类似于本章中的花式秘籍。 这次,我们在图像的对角线上选择模为4的点:

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

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

    lena1 = lena.copy() 
    xindices = get_indices(lena.shape[0])
    yindices = get_indices(lena.shape[1])
    lena1[xindices, yindices] = 0
    plt.subplot(211)
    plt.imshow(lena1)
    
  2. 选择最大值的四分之一到四分之三的数组值,并将它们设置为0

    lena2[(lena > lena.max()/4) & (lena < 3 * lena.max()/4)] = 0
    

    带有两个新图像的绘图将如下面的截图所示:

    How to do it...

    以下是此秘籍的完整代码,在本书代码包中boolean_indexing.py文件中:

    import scipy.misc
    import matplotlib.pyplot as plt
    import numpy as np
    
    # Load the Lena array
    lena = scipy.misc.lena()
    
    def get_indices(size):
       arr = np.arange(size)
       return arr % 4 == 0
    
    # Plot Lena
    lena1 = lena.copy() 
    xindices = get_indices(lena.shape[0])
    yindices = get_indices(lena.shape[1])
    lena1[xindices, yindices] = 0
    plt.subplot(211)
    plt.imshow(lena1)
    
    lena2 = lena.copy() 
    # Between quarter and 3 quarters of the max value
    lena2[(lena > lena.max()/4) & (lena < 3 * lena.max()/4)] = 0
    plt.subplot(212)
    plt.imshow(lena2)
    
    plt.show()
    

工作原理

由于布尔索引是一种花式索引,因此它的工作方式基本相同。 这意味着索引受到特殊迭代器对象的帮助。

另见

  • 花式索引

数独的步长技巧

ndarray 类有一个strides字段,它是一个元组,表示遍历数组时,每个维度中要经过的字节数。 让我们应用一些步长技巧来解决将数独谜题分成3×3方格的问题。

注意

解释数独的规则超出了本书的范围。 简而言之,数独谜题由3 x 3正方形组成。 这些方块中的每一个包含九个数字。 更多信息请参阅en.wikipedia.org/wiki/Sudoku

操作步骤

应用步长技巧如下:

  1. 让我们定义sudoku数组。 这个数组充满了实际的已解决的数独谜题的内容:

    sudoku = np.array([
        [2, 8, 7, 1, 6, 5, 9, 4, 3],
        [9, 5, 4, 7, 3, 2, 1, 6, 8],
        [6, 1, 3, 8, 4, 9, 7, 5, 2],
        [8, 7, 9, 6, 5, 1, 2, 3, 4],
        [4, 2, 1, 3, 9, 8, 6, 7, 5],
        [3, 6, 5, 4, 2, 7, 8, 9, 1],
        [1, 9, 8, 5, 7, 3, 4, 2, 6],
        [5, 4, 2, 9, 1, 6, 3, 8, 7],
        [7, 3, 6, 2, 8, 4, 5, 1, 9]
        ])
    
  2. ndarrayitemsize字段为我们提供了数组中的字节数。 根据itemsize计算步长:

    strides = sudoku.itemsize * np.array([27, 3, 9, 1])
    
  3. 现在我们可以使用np.lib.stride_tricks模块的as_strided()函数将谜题分割成正方形:

    squares = np.lib.stride_tricks.as_strided(sudoku, shape=shape, strides=strides)
    print(squares)
    

    代码打印单独的数独方块,如下所示:

    [[[[2 8 7]
        [9 5 4]
        [6 1 3]]
    
      [[1 6 5]
        [7 3 2]
        [8 4 9]]
    
      [[9 4 3]
        [1 6 8]
        [7 5 2]]]
    
     [[[8 7 9]
        [4 2 1]
        [3 6 5]]
    
      [[6 5 1]
        [3 9 8]
        [4 2 7]]
    
      [[2 3 4]
        [6 7 5]
        [8 9 1]]]
    
     [[[1 9 8]
        [5 4 2]
        [7 3 6]]
    
      [[5 7 3]
        [9 1 6]
        [2 8 4]]
    
      [[4 2 6]
        [3 8 7]
        [5 1 9]]]]
    

    以下是此秘籍的完整源代码,在本书代码包中strides.py文件中:

    import numpy as np
    
    sudoku = np.array([
       [2, 8, 7, 1, 6, 5, 9, 4, 3],
       [9, 5, 4, 7, 3, 2, 1, 6, 8],
       [6, 1, 3, 8, 4, 9, 7, 5, 2],
       [8, 7, 9, 6, 5, 1, 2, 3, 4],
       [4, 2, 1, 3, 9, 8, 6, 7, 5],
       [3, 6, 5, 4, 2, 7, 8, 9, 1],
       [1, 9, 8, 5, 7, 3, 4, 2, 6],
       [5, 4, 2, 9, 1, 6, 3, 8, 7],
       [7, 3, 6, 2, 8, 4, 5, 1, 9]
       ])
    
    shape = (3, 3, 3, 3)
    
    strides = sudoku.itemsize * np.array([27, 3, 9, 1])
    
    squares = np.lib.stride_tricks.as_strided(sudoku, shape=shape, strides=strides)
    print(squares)
    

工作原理

我们应用了步长技巧将数独谜题分成3 x 3正方形。 这些步骤告诉我们在遍历数独数组时,每一步需要跳过的字节数。

另见

数组广播

你可能在不知道广播的情况下进行数组广播。 简而言之,即使操作数形状不相同,NumPy也会尝试执行操作。 在这个秘籍中,我们将数组乘以一个标量。 标量扩展为数组操作数的形状,然后执行乘法。 我们将下载一个音频文件并制作一个更安静的新版本。

操作步骤

让我们从读取WAV文件开始:

  1. 我们将使用标准Python代码下载Austin Powers的音频文件。 SciPy有一个WAV文件模块,允许您加载声音数据或生成WAV文件。 如果安装了SciPy,那么我们应该已经有了这个模块。 read()函数返回data数组和采样率。 在这个例子中,我们只关心数据:

    sample_rate, data = scipy.io.wavfile.read(WAV_FILE)
    
  2. 使用matplotlib绘制原始WAV数据。 将子图命名为Original

    plt.subplot(2, 1, 1)
    plt.title("Original")
    plt.plot(data)
    
  3. 现在我们将使用NumPy制作更安静的音频样本。 这只是一个问题,通过乘以常数来创建具有较小值的新数组。 这就是广播的神奇之处。 最后,由于WAV格式,我们需要确保我们拥有原始数组的相同数据类型:

    newdata = data * 0.2
    newdata = newdata.astype(np.uint8)
    
  4. 这个新数组可以写入新的WAV文件,如下所示:

    scipy.io.wavfile.write("quiet.wav",
        sample_rate, newdata)
    
  5. 使用matplotlib绘制新数据数组:

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

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

    How to do it...

    以下是此秘籍的完整代码,在本书代码包中broadcasting.py文件中:

    import scipy.io.wavfile
    import matplotlib.pyplot as plt
    import urllib2
    import numpy as np
    
    # Download audio file
    response = urllib2.urlopen('http://www.thesoundarchive.com/austinpowers/smashingbaby.wav')
    print(response.info())
    WAV_FILE = 'smashingbaby.wav'
    filehandle = open(WAV_FILE, 'w')
    filehandle.write(response.read())
    filehandle.close()
    sample_rate, data = scipy.io.wavfile.read(WAV_FILE)
    print("Data type", data.dtype, "Shape", data.shape)
    
    # Plot values original audio
    plt.subplot(2, 1, 1)
    plt.title("Original")
    plt.plot(data)
    
    # Create quieter audio
    newdata = data * 0.2
    newdata = newdata.astype(np.uint8)
    print("Data type", newdata.dtype, "Shape", newdata.shape)
    
    # Save quieter audio file
    scipy.io.wavfile.write("quiet.wav",
        sample_rate, newdata)
    
    # Plot values quieter file
    plt.subplot(2, 1, 2)
    plt.title("Quiet")
    plt.plot(newdata)
    
    plt.show()
    

另见

以下链接提供了更多背景信息: