NumPy快速入门#

先决条件#

您需要了解一些 Python 知识. 要回顾一下,请参阅 Python tutorial .

要运行这些示例,除了 NumPy 之外,还需要安装 matplotlib .

学习者概况

这是 NumPy 中数组的快速概述. 它演示了 n 维( \(n>=2\) )数组是如何表示和操作的. 特别是,如果您不知道如何将常用函数应用于 n 维数组(不使用 for 循环),或者您想了解 n 维数组的轴和形状属性,那么本文可能会有所帮助.

学习目标

阅读后,您应该能够:

  • 了解 NumPy 中一维,二维和 n 维数组之间的区别;

  • 了解如何在不使用 for 循环的情况下将一些线性代数运算应用于 n 维数组;

  • 了解 n 维数组的轴和形状属性.

基础知识#

NumPy 的主要对象是同质多维数组. 它是元素的表格(通常是数字),所有元素的类型相同,由非负整数的元组索引. 在 NumPy 中,维度称为轴.

例如,3D 空间中点的坐标数组 [1, 2, 1] 有一个轴. 该轴有 3 个元素,因此我们说它的长度为 3. 在下图所示的示例中,该数组有 2 个轴. 第一个轴的长度为 2,第二个轴的长度为 3.

[[1., 0., 0.],
 [0., 1., 2.]]

NumPy 的数组类称为 ndarray . 它也以别名 array 为人所知. 请注意, numpy.array 与标准 Python 库类 array.array 不同,后者仅处理一维数组并提供较少的功能. ndarray 对象更重要的属性是:

ndarray.ndim

数组的轴(维度)的数量.

ndarray.shape

数组的维度. 这是一个整数元组,指示数组在每个维度上的大小. 对于具有 n 行和 m 列的矩阵, shape 将是 (n,m) . 因此, shape 元组的长度是轴的数量, ndim .

ndarray.size

数组中元素的总数. 这等于 shape 元素的乘积.

ndarray.dtype

一个描述数组中元素类型的对象. 可以使用标准 Python 类型创建或指定 dtype. 此外,NumPy 提供了它自己的类型. numpy.int32,numpy.int16 和 numpy.float64 是一些示例.

ndarray.itemsize

数组中每个元素的字节大小.例如,类型为 float64 的元素的数组的 itemsize 为 8 (=64/8),而类型为 complex32 的元素的数组的 itemsize 为 4 (=32/8).它等价于 ndarray.dtype.itemsize .

ndarray.data

包含数组实际元素的缓冲区.通常,我们不需要使用这个属性,因为我们将使用索引工具访问数组中的元素.

一个例子#

>>> import numpy as np
>>> a = np.arange(15).reshape(3, 5)
>>> a
array([[ 0,  1,  2,  3,  4],
       [ 5,  6,  7,  8,  9],
       [10, 11, 12, 13, 14]])
>>> a.shape
(3, 5)
>>> a.ndim
2
>>> a.dtype.name
'int64'
>>> a.itemsize
8
>>> a.size
15
>>> type(a)
<class 'numpy.ndarray'>
>>> b = np.array([6, 7, 8])
>>> b
array([6, 7, 8])
>>> type(b)
<class 'numpy.ndarray'>

创建数组#

有几种创建数组的方法.

例如,你可以使用 array 函数从常规Python列表或元组创建一个数组.结果数组的类型是从序列中元素的类型推导出来的.

>>> import numpy as np
>>> a = np.array([2, 3, 4])
>>> a
array([2, 3, 4])
>>> a.dtype
dtype('int64')
>>> b = np.array([1.2, 3.5, 5.1])
>>> b.dtype
dtype('float64')

一个常见的错误是使用多个参数调用 array ,而不是提供一个单一的序列作为参数.

>>> a = np.array(1, 2, 3, 4)    # WRONG
Traceback (most recent call last):
  ...
TypeError: array() takes from 1 to 2 positional arguments but 4 were given
>>> a = np.array([1, 2, 3, 4])  # RIGHT

array 将序列的序列转换为二维数组,将序列的序列的序列转换为三维数组,依此类推.

>>> b = np.array([(1.5, 2, 3), (4, 5, 6)])
>>> b
array([[1.5, 2. , 3. ],
       [4. , 5. , 6. ]])

数组的类型也可以在创建时显式指定:

>>> c = np.array([[1, 2], [3, 4]], dtype=complex)
>>> c
array([[1.+0.j, 2.+0.j],
       [3.+0.j, 4.+0.j]])

通常,数组的元素最初是未知的,但其大小是已知的.因此,NumPy提供了几个函数来创建具有初始占位符内容的数组.这些最大限度地减少了增长数组的必要性,这是一个昂贵的操作.

函数 zeros 创建一个充满零的数组,函数 ones 创建一个充满一的数组,函数 empty 创建一个数组,其初始内容是随机的,取决于内存的状态.默认情况下,创建的数组的dtype是 float64 ,但可以通过关键字参数 dtype 指定.

>>> np.zeros((3, 4))
array([[0., 0., 0., 0.],
       [0., 0., 0., 0.],
       [0., 0., 0., 0.]])
>>> np.ones((2, 3, 4), dtype=np.int16)
array([[[1, 1, 1, 1],
        [1, 1, 1, 1],
        [1, 1, 1, 1]],

       [[1, 1, 1, 1],
        [1, 1, 1, 1],
        [1, 1, 1, 1]]], dtype=int16)
>>> np.empty((2, 3)) 
array([[3.73603959e-262, 6.02658058e-154, 6.55490914e-260],  # may vary
       [5.30498948e-313, 3.14673309e-307, 1.00000000e+000]])

要创建数字序列,NumPy提供了 arange 函数,它类似于Python的内置 range ,但返回一个数组.

>>> np.arange(10, 30, 5)
array([10, 15, 20, 25])
>>> np.arange(0, 2, 0.3)  # it accepts float arguments
array([0. , 0.3, 0.6, 0.9, 1.2, 1.5, 1.8])

arange 与浮点参数一起使用时,通常不可能预测获得的元素数量,这是由于有限的浮点精度.因此,通常最好使用 linspace 函数,该函数接收我们想要的元素数量作为参数,而不是步长:

>>> from numpy import pi
>>> np.linspace(0, 2, 9)                   # 9 numbers from 0 to 2
array([0.  , 0.25, 0.5 , 0.75, 1.  , 1.25, 1.5 , 1.75, 2.  ])
>>> x = np.linspace(0, 2 * pi, 100)        # useful to evaluate function at lots of points
>>> f = np.sin(x)

打印数组#

当你打印一个数组时,NumPy以类似于嵌套列表的方式显示它,但具有以下布局:

  • 最后一个轴从左到右打印,

  • 倒数第二个轴从上到下打印,

  • 其余的也从上到下打印,每个切片与下一个切片之间用一个空行分隔.

一维数组然后作为行打印,二维数组作为矩阵打印,三维数组作为矩阵列表打印.

>>> a = np.arange(6)                    # 1d array
>>> print(a)
[0 1 2 3 4 5]
>>>
>>> b = np.arange(12).reshape(4, 3)     # 2d array
>>> print(b)
[[ 0  1  2]
 [ 3  4  5]
 [ 6  7  8]
 [ 9 10 11]]
>>>
>>> c = np.arange(24).reshape(2, 3, 4)  # 3d array
>>> print(c)
[[[ 0  1  2  3]
  [ 4  5  6  7]
  [ 8  9 10 11]]

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

请参阅 below 以获取有关 reshape 的更多详细信息.

如果数组太大而无法打印,NumPy会自动跳过数组的中心部分,只打印角:

>>> print(np.arange(10000))
[   0    1    2 ... 9997 9998 9999]
>>>
>>> print(np.arange(10000).reshape(100, 100))
[[   0    1    2 ...   97   98   99]
 [ 100  101  102 ...  197  198  199]
 [ 200  201  202 ...  297  298  299]
 ...
 [9700 9701 9702 ... 9797 9798 9799]
 [9800 9801 9802 ... 9897 9898 9899]
 [9900 9901 9902 ... 9997 9998 9999]]

要禁用此行为并强制NumPy打印整个数组,可以使用 set_printoptions 更改打印选项.

>>> np.set_printoptions(threshold=sys.maxsize)  # sys module should be imported

基本操作#

数组上的算术运算符是按元素应用的.创建一个新数组,并用结果填充它.

>>> a = np.array([20, 30, 40, 50])
>>> b = np.arange(4)
>>> b
array([0, 1, 2, 3])
>>> c = a - b
>>> c
array([20, 29, 38, 47])
>>> b**2
array([0, 1, 4, 9])
>>> 10 * np.sin(a)
array([ 9.12945251, -9.88031624,  7.4511316 , -2.62374854])
>>> a < 35
array([ True,  True, False, False])

与许多矩阵语言不同,乘积运算符 * 在NumPy数组中按元素操作.矩阵乘积可以使用 @ 运算符(在python >=3.5中)或 dot 函数或方法执行:

>>> A = np.array([[1, 1],
...               [0, 1]])
>>> B = np.array([[2, 0],
...               [3, 4]])
>>> A * B     # elementwise product
array([[2, 0],
       [0, 4]])
>>> A @ B     # matrix product
array([[5, 4],
       [3, 4]])
>>> A.dot(B)  # another matrix product
array([[5, 4],
       [3, 4]])

有些操作,比如 +=*= ,会原地修改已有的数组,而不是创建一个新的数组.

>>> rg = np.random.default_rng(1)  # create instance of default random number generator
>>> a = np.ones((2, 3), dtype=int)
>>> b = rg.random((2, 3))
>>> a *= 3
>>> a
array([[3, 3, 3],
       [3, 3, 3]])
>>> b += a
>>> b
array([[3.51182162, 3.9504637 , 3.14415961],
       [3.94864945, 3.31183145, 3.42332645]])
>>> a += b  # b is not automatically converted to integer type
Traceback (most recent call last):
    ...
numpy._core._exceptions._UFuncOutputCastingError: Cannot cast ufunc 'add' output from dtype('float64') to dtype('int64') with casting rule 'same_kind'

当对不同类型的数组进行运算时,结果数组的类型对应于更通用或更精确的类型(这种行为称为向上转型).

>>> a = np.ones(3, dtype=np.int32)
>>> b = np.linspace(0, pi, 3)
>>> b.dtype.name
'float64'
>>> c = a + b
>>> c
array([1.        , 2.57079633, 4.14159265])
>>> c.dtype.name
'float64'
>>> d = np.exp(c * 1j)
>>> d
array([ 0.54030231+0.84147098j, -0.84147098+0.54030231j,
       -0.54030231-0.84147098j])
>>> d.dtype.name
'complex128'

许多一元运算,比如计算数组中所有元素的和,都是以 ndarray 类的方法来实现的.

>>> a = rg.random((2, 3))
>>> a
array([[0.82770259, 0.40919914, 0.54959369],
       [0.02755911, 0.75351311, 0.53814331]])
>>> a.sum()
3.1057109529998157
>>> a.min()
0.027559113243068367
>>> a.max()
0.8277025938204418

默认情况下,这些操作应用到数组时,就好像它是一个数字列表,而不管它的形状.但是,通过指定 axis 参数,您可以沿着数组的指定轴应用操作:

>>> b = np.arange(12).reshape(3, 4)
>>> b
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
>>>
>>> b.sum(axis=0)     # sum of each column
array([12, 15, 18, 21])
>>>
>>> b.min(axis=1)     # min of each row
array([0, 4, 8])
>>>
>>> b.cumsum(axis=1)  # cumulative sum along each row
array([[ 0,  1,  3,  6],
       [ 4,  9, 15, 22],
       [ 8, 17, 27, 38]])

通用函数#

NumPy 提供了熟悉的数学函数,如 sin,cos 和 exp.在NumPy中,这些函数被称为“通用函数”( ufunc ).在 NumPy 中,这些函数在数组上逐元素地操作,并产生一个数组作为输出.

>>> B = np.arange(3)
>>> B
array([0, 1, 2])
>>> np.exp(B)
array([1.        , 2.71828183, 7.3890561 ])
>>> np.sqrt(B)
array([0.        , 1.        , 1.41421356])
>>> C = np.array([2., -1., 4.])
>>> np.add(B, C)
array([2., 0., 6.])

索引,切片和迭代#

一维数组可以被索引,切片和迭代,很像 lists 和其他 Python 序列.

>>> a = np.arange(10)**3
>>> a
array([  0,   1,   8,  27,  64, 125, 216, 343, 512, 729])
>>> a[2]
8
>>> a[2:5]
array([ 8, 27, 64])
>>> # equivalent to a[0:6:2] = 1000;
>>> # from start to position 6, exclusive, set every 2nd element to 1000
>>> a[:6:2] = 1000
>>> a
array([1000,    1, 1000,   27, 1000,  125,  216,  343,  512,  729])
>>> a[::-1]  # reversed a
array([ 729,  512,  343,  216,  125, 1000,   27, 1000,    1, 1000])
>>> for i in a:
...     print(i**(1 / 3.))
...
9.999999999999998  # may vary
1.0
9.999999999999998
3.0
9.999999999999998
4.999999999999999
5.999999999999999
6.999999999999999
7.999999999999999
8.999999999999998

多维数组在每个轴上都有一个索引.这些索引在一个由逗号分隔的元组中给出:

>>> def f(x, y):
...     return 10 * x + y
...
>>> b = np.fromfunction(f, (5, 4), dtype=int)
>>> b
array([[ 0,  1,  2,  3],
       [10, 11, 12, 13],
       [20, 21, 22, 23],
       [30, 31, 32, 33],
       [40, 41, 42, 43]])
>>> b[2, 3]
23
>>> b[0:5, 1]  # each row in the second column of b
array([ 1, 11, 21, 31, 41])
>>> b[:, 1]    # equivalent to the previous example
array([ 1, 11, 21, 31, 41])
>>> b[1:3, :]  # each column in the second and third row of b
array([[10, 11, 12, 13],
       [20, 21, 22, 23]])

当提供的索引少于轴的数量时,缺少的索引被认为是完整切片:

>>> b[-1]   # the last row. Equivalent to b[-1, :]
array([40, 41, 42, 43])

b[i] 中括号内的表达式被视为一个 i ,后面跟着尽可能多的 : 实例,以表示剩余的轴.NumPy 也允许您使用点来写它,如 b[i, ...] .

点( ... )表示生成完整索引元组所需的尽可能多的冒号.例如,如果 x 是一个具有 5 个轴的数组,则

  • x[1, 2, ...] 等价于 x[1, 2, :, :, :] ,

  • x[..., 3] 等价于 x[:, :, :, :, 3] ,

  • x[4, ..., 5, :] 等价于 x[4, :, :, 5, :] .

>>> c = np.array([[[  0,  1,  2],  # a 3D array (two stacked 2D arrays)
...                [ 10, 12, 13]],
...               [[100, 101, 102],
...                [110, 112, 113]]])
>>> c.shape
(2, 2, 3)
>>> c[1, ...]  # same as c[1, :, :] or c[1]
array([[100, 101, 102],
       [110, 112, 113]])
>>> c[..., 2]  # same as c[:, :, 2]
array([[  2,  13],
       [102, 113]])

对多维数组的迭代是相对于第一个轴进行的:

>>> for row in b:
...     print(row)
...
[0 1 2 3]
[10 11 12 13]
[20 21 22 23]
[30 31 32 33]
[40 41 42 43]

然而,如果想要对数组中的每个元素执行操作,可以使用 flat 属性,它是一个遍历数组所有元素的 iterator

>>> for element in b.flat:
...     print(element)
...
0
1
2
3
10
11
12
13
20
21
22
23
30
31
32
33
40
41
42
43

形状操作#

改变数组的形状#

数组有一个由每个轴上的元素数量决定的形状:

>>> a = np.floor(10 * rg.random((3, 4)))
>>> a
array([[3., 7., 3., 4.],
       [1., 4., 2., 2.],
       [7., 2., 4., 9.]])
>>> a.shape
(3, 4)

可以使用各种命令更改数组的形状. 请注意,以下三个命令都返回一个修改后的数组,但不会更改原始数组:

>>> a.ravel()  # returns the array, flattened
array([3., 7., 3., 4., 1., 4., 2., 2., 7., 2., 4., 9.])
>>> a.reshape(6, 2)  # returns the array with a modified shape
array([[3., 7.],
       [3., 4.],
       [1., 4.],
       [2., 2.],
       [7., 2.],
       [4., 9.]])
>>> a.T  # returns the array, transposed
array([[3., 1., 7.],
       [7., 4., 2.],
       [3., 2., 4.],
       [4., 2., 9.]])
>>> a.T.shape
(4, 3)
>>> a.shape
(3, 4)

ravel 产生的数组中的元素的顺序通常是“C风格”,也就是说,最右边的索引“变化最快”,因此 a[0, 0] 之后的元素是 a[0, 1] .如果数组被重塑为其他形状,则数组再次被视为“C风格”.NumPy通常创建以这种顺序存储的数组,因此 ravel 通常不需要复制其参数,但如果数组是通过获取另一个数组的切片或使用不寻常的选项创建的,则可能需要复制.可以使用一个可选参数指示函数 ravelreshape 使用FORTRAN风格的数组,其中最左边的索引变化最快.

reshape 函数返回一个修改了形状的参数,而 ndarray.resize 方法会直接修改数组本身:

>>> a
array([[3., 7., 3., 4.],
       [1., 4., 2., 2.],
       [7., 2., 4., 9.]])
>>> a.resize((2, 6))
>>> a
array([[3., 7., 3., 4., 1., 4.],
       [2., 2., 7., 2., 4., 9.]])

如果在 reshape 操作中,某个维度被指定为 -1 ,则其他维度会自动计算:

>>> a.reshape(3, -1)
array([[3., 7., 3., 4.],
       [1., 4., 2., 2.],
       [7., 2., 4., 9.]])

将不同的数组堆叠在一起#

多个数组可以沿着不同的轴堆叠在一起:

>>> a = np.floor(10 * rg.random((2, 2)))
>>> a
array([[9., 7.],
       [5., 2.]])
>>> b = np.floor(10 * rg.random((2, 2)))
>>> b
array([[1., 9.],
       [5., 1.]])
>>> np.vstack((a, b))
array([[9., 7.],
       [5., 2.],
       [1., 9.],
       [5., 1.]])
>>> np.hstack((a, b))
array([[9., 7., 1., 9.],
       [5., 2., 5., 1.]])

函数 column_stack 将一维数组作为列堆叠到二维数组中.它等价于 hstack ,但仅适用于二维数组:

>>> from numpy import newaxis
>>> np.column_stack((a, b))  # with 2D arrays
array([[9., 7., 1., 9.],
       [5., 2., 5., 1.]])
>>> a = np.array([4., 2.])
>>> b = np.array([3., 8.])
>>> np.column_stack((a, b))  # returns a 2D array
array([[4., 3.],
       [2., 8.]])
>>> np.hstack((a, b))        # the result is different
array([4., 2., 3., 8.])
>>> a[:, newaxis]  # view `a` as a 2D column vector
array([[4.],
       [2.]])
>>> np.column_stack((a[:, newaxis], b[:, newaxis]))
array([[4., 3.],
       [2., 8.]])
>>> np.hstack((a[:, newaxis], b[:, newaxis]))  # the result is the same
array([[4., 3.],
       [2., 8.]])

一般来说,对于具有两个以上维度的数组, hstack 沿着它们的第二个轴堆叠, vstack 沿着它们的第一个轴堆叠,而 concatenate 允许使用一个可选参数来指定应该进行连接操作的轴的编号.

注意

在复杂的情况下, r_c_ 对于沿一个轴堆叠数字来创建数组很有用.它们允许使用范围字面量 : .

>>> np.r_[1:4, 0, 4]
array([1, 2, 3, 0, 4])

当与数组一起作为参数使用时, r_c_ 在默认行为上类似于 vstackhstack ,但允许使用一个可选参数来指定沿哪个轴进行连接.

将一个数组分割成几个较小的数组#

使用 hsplit ,您可以沿着数组的水平轴分割数组,可以通过指定要返回的形状相等的数组的数量,也可以通过指定应该在其后进行分割的列:

>>> a = np.floor(10 * rg.random((2, 12)))
>>> a
array([[6., 7., 6., 9., 0., 5., 4., 0., 6., 8., 5., 2.],
       [8., 5., 5., 7., 1., 8., 6., 7., 1., 8., 1., 0.]])
>>> # Split `a` into 3
>>> np.hsplit(a, 3)
[array([[6., 7., 6., 9.],
       [8., 5., 5., 7.]]), array([[0., 5., 4., 0.],
       [1., 8., 6., 7.]]), array([[6., 8., 5., 2.],
       [1., 8., 1., 0.]])]
>>> # Split `a` after the third and the fourth column
>>> np.hsplit(a, (3, 4))
[array([[6., 7., 6.],
       [8., 5., 5.]]), array([[9.],
       [7.]]), array([[0., 5., 4., 0., 6., 8., 5., 2.],
       [1., 8., 6., 7., 1., 8., 1., 0.]])]

vsplit 沿垂直轴分割, array_split 允许指定沿哪个轴分割.

复制与视图#

在操作和处理数组时,它们的数据有时会被复制到一个新的数组中,有时则不会.这通常是初学者的困惑之源.有三种情况:

完全不复制#

简单的赋值不会复制对象或它们的数据.

>>> a = np.array([[ 0,  1,  2,  3],
...               [ 4,  5,  6,  7],
...               [ 8,  9, 10, 11]])
>>> b = a            # no new object is created
>>> b is a           # a and b are two names for the same ndarray object
True

Python 通过引用传递可变对象,因此函数调用不会进行复制.

>>> def f(x):
...     print(id(x))
...
>>> id(a)  # id is a unique identifier of an object 
148293216  # may vary
>>> f(a)   
148293216  # may vary

视图或浅复制#

不同的数组对象可以共享相同的数据. view 方法创建一个新的数组对象,它查看相同的数据.

>>> c = a.view()
>>> c is a
False
>>> c.base is a            # c is a view of the data owned by a
True
>>> c.flags.owndata
False
>>>
>>> c = c.reshape((2, 6))  # a's shape doesn't change, reassigned c is still a view of a
>>> a.shape
(3, 4)
>>> c[0, 4] = 1234         # a's data changes
>>> a
array([[   0,    1,    2,    3],
       [1234,    5,    6,    7],
       [   8,    9,   10,   11]])

切片一个数组会返回它的一个视图:

>>> s = a[:, 1:3]
>>> s[:] = 10  # s[:] is a view of s. Note the difference between s = 10 and s[:] = 10
>>> a
array([[   0,   10,   10,    3],
       [1234,   10,   10,    7],
       [   8,   10,   10,   11]])

深复制#

copy 方法创建一个数组及其数据的完整副本.

>>> d = a.copy()  # a new array object with new data is created
>>> d is a
False
>>> d.base is a  # d doesn't share anything with a
False
>>> d[0, 0] = 9999
>>> a
array([[   0,   10,   10,    3],
       [1234,   10,   10,    7],
       [   8,   10,   10,   11]])

如果不再需要原始数组,有时应该在切片后调用 copy .例如,假设 a 是一个巨大的中间结果,最终结果 b 仅包含 a 的一小部分,则在使用切片构造 b 时应进行深复制:

>>> a = np.arange(int(1e8))
>>> b = a[:100].copy()
>>> del a  # the memory of ``a`` can be released.

如果使用 b = a[:100] ,则 ab 引用,即使执行了 del a ,它也会保留在内存中.

另请参见 复制与视图 .

函数和方法概览#

以下是按类别排序的一些有用的 NumPy 函数和方法名称的列表.有关完整列表,请参见 按主题分类的例程和对象 .

数组创建

arange , array , copy , empty , empty_like , eye , fromfile , fromfunction , identity , linspace , logspace , mgrid , ogrid , ones , ones_like , r_ , zeros , zeros_like

转换

ndarray.astype , atleast_1d , atleast_2d , atleast_3d , mat

操作

array_split , column_stack , concatenate , diagonal , dsplit , dstack , hsplit , hstack , ndarray.item , newaxis , ravel , repeat , reshape , resize , squeeze , swapaxes , take , transpose , vsplit , vstack

问题

all , any , nonzero , where

排序

argmax , argmin , argsort , max , min , ptp , searchsorted , sort

运算

choose , compress , cumprod , cumsum , inner , ndarray.fill , imag , prod , put , putmask , real , sum

基本统计

cov , mean , std , var

基本线性代数

cross , dot , outer , linalg.svd , vdot

不那么基础#

广播规则#

广播允许通用函数以有意义的方式处理形状不完全相同的输入.

广播的第一条规则是,如果所有输入数组的维度数不相同,则会重复地在较小数组的形状前添加“1”,直到所有数组的维度数相同.

广播的第二条规则确保沿特定维度大小为1的数组,其行为就像它们在该维度上具有最大形状数组的大小一样.对于“广播”数组,假定数组元素的值沿该维度相同.

应用广播规则后,所有数组的大小必须匹配.更多详细信息可以在 广播 中找到.

高级索引和索引技巧#

NumPy 提供了比常规 Python 序列更多的索引功能.除了我们之前看到的整数和切片索引之外,数组还可以通过整数数组和布尔数组进行索引.

使用索引数组进行索引#

>>> a = np.arange(12)**2  # the first 12 square numbers
>>> i = np.array([1, 1, 3, 8, 5])  # an array of indices
>>> a[i]  # the elements of `a` at the positions `i`
array([ 1,  1,  9, 64, 25])
>>>
>>> j = np.array([[3, 4], [9, 7]])  # a bidimensional array of indices
>>> a[j]  # the same shape as `j`
array([[ 9, 16],
       [81, 49]])

当索引数组 a 是多维的时,单个索引数组指的是 a 的第一个维度.以下示例通过使用调色板将标签图像转换为彩色图像来显示此行为.

>>> palette = np.array([[0, 0, 0],         # black
...                     [255, 0, 0],       # red
...                     [0, 255, 0],       # green
...                     [0, 0, 255],       # blue
...                     [255, 255, 255]])  # white
>>> image = np.array([[0, 1, 2, 0],  # each value corresponds to a color in the palette
...                   [0, 3, 4, 0]])
>>> palette[image]  # the (2, 4, 3) color image
array([[[  0,   0,   0],
        [255,   0,   0],
        [  0, 255,   0],
        [  0,   0,   0]],

       [[  0,   0,   0],
        [  0,   0, 255],
        [255, 255, 255],
        [  0,   0,   0]]])

我们也可以给出多个维度的索引.每个维度的索引数组必须具有相同的形状.

>>> a = np.arange(12).reshape(3, 4)
>>> a
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
>>> i = np.array([[0, 1],  # indices for the first dim of `a`
...               [1, 2]])
>>> j = np.array([[2, 1],  # indices for the second dim
...               [3, 3]])
>>>
>>> a[i, j]  # i and j must have equal shape
array([[ 2,  5],
       [ 7, 11]])
>>>
>>> a[i, 2]
array([[ 2,  6],
       [ 6, 10]])
>>>
>>> a[:, j]
array([[[ 2,  1],
        [ 3,  3]],

       [[ 6,  5],
        [ 7,  7]],

       [[10,  9],
        [11, 11]]])

在 Python 中, arr[i, j]arr[(i, j)] 完全相同—所以我们可以把 ij 放在一个 tuple 中,然后用它进行索引.

>>> l = (i, j)
>>> # equivalent to a[i, j]
>>> a[l]
array([[ 2,  5],
       [ 7, 11]])

但是,我们不能通过将 ij 放入数组中来做到这一点,因为该数组将被解释为索引 a 的第一个维度.

>>> s = np.array([i, j])
>>> # not what we want
>>> a[s]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: index 3 is out of bounds for axis 0 with size 3
>>> # same as `a[i, j]`
>>> a[tuple(s)]
array([[ 2,  5],
       [ 7, 11]])

使用数组进行索引的另一个常见用法是搜索时间相关序列的最大值:

>>> time = np.linspace(20, 145, 5)  # time scale
>>> data = np.sin(np.arange(20)).reshape(5, 4)  # 4 time-dependent series
>>> time
array([ 20.  ,  51.25,  82.5 , 113.75, 145.  ])
>>> data
array([[ 0.        ,  0.84147098,  0.90929743,  0.14112001],
       [-0.7568025 , -0.95892427, -0.2794155 ,  0.6569866 ],
       [ 0.98935825,  0.41211849, -0.54402111, -0.99999021],
       [-0.53657292,  0.42016704,  0.99060736,  0.65028784],
       [-0.28790332, -0.96139749, -0.75098725,  0.14987721]])
>>> # index of the maxima for each series
>>> ind = data.argmax(axis=0)
>>> ind
array([2, 0, 3, 1])
>>> # times corresponding to the maxima
>>> time_max = time[ind]
>>>
>>> data_max = data[ind, range(data.shape[1])]  # => data[ind[0], 0], data[ind[1], 1]...
>>> time_max
array([ 82.5 ,  20.  , 113.75,  51.25])
>>> data_max
array([0.98935825, 0.84147098, 0.99060736, 0.6569866 ])
>>> np.all(data_max == data.max(axis=0))
True

您还可以使用带数组的索引作为赋值目标:

>>> a = np.arange(5)
>>> a
array([0, 1, 2, 3, 4])
>>> a[[1, 3, 4]] = 0
>>> a
array([0, 0, 2, 0, 0])

但是,当索引列表包含重复项时,赋值将执行多次,最后只留下最后一个值:

>>> a = np.arange(5)
>>> a[[0, 0, 2]] = [1, 2, 3]
>>> a
array([2, 1, 3, 3, 4])

这很合理,但如果要使用 Python 的 += 构造,请注意,它可能不会执行您期望的操作:

>>> a = np.arange(5)
>>> a[[0, 0, 2]] += 1
>>> a
array([1, 1, 3, 3, 4])

即使索引列表中 0 出现了两次,第 0 个元素也只会递增一次.这是因为 Python 要求 a += 1 等同于 a = a + 1 .

使用布尔数组进行索引#

当我们使用(整数)索引数组索引数组时,我们提供要选择的索引列表.对于布尔索引,方法是不同的;我们明确选择数组中我们想要的项和我们不想要的项.

人们可以想到的最自然的布尔索引方式是使用与原始数组具有相同形状的布尔数组:

>>> a = np.arange(12).reshape(3, 4)
>>> b = a > 4
>>> b  # `b` is a boolean with `a`'s shape
array([[False, False, False, False],
       [False,  True,  True,  True],
       [ True,  True,  True,  True]])
>>> a[b]  # 1d array with the selected elements
array([ 5,  6,  7,  8,  9, 10, 11])

此属性在赋值中非常有用:

>>> a[b] = 0  # All elements of `a` higher than 4 become 0
>>> a
array([[0, 1, 2, 3],
       [4, 0, 0, 0],
       [0, 0, 0, 0]])

您可以查看以下示例,了解如何使用布尔索引来生成 Mandelbrot set 的图像:

>>> import numpy as np
>>> import matplotlib.pyplot as plt
>>> def mandelbrot(h, w, maxit=20, r=2):
...     """Returns an image of the Mandelbrot fractal of size (h,w)."""
...     x = np.linspace(-2.5, 1.5, 4*h+1)
...     y = np.linspace(-1.5, 1.5, 3*w+1)
...     A, B = np.meshgrid(x, y)
...     C = A + B*1j
...     z = np.zeros_like(C)
...     divtime = maxit + np.zeros(z.shape, dtype=int)
...
...     for i in range(maxit):
...         z = z**2 + C
...         diverge = abs(z) > r                    # who is diverging
...         div_now = diverge & (divtime == maxit)  # who is diverging now
...         divtime[div_now] = i                    # note when
...         z[diverge] = r                          # avoid diverging too much
...
...     return divtime
>>> plt.clf()
>>> plt.imshow(mandelbrot(400, 400))
../_images/quickstart-1.png

第二种使用布尔索引的方法更类似于整数索引;对于数组的每个维度,我们提供一个一维布尔数组来选择我们想要的切片:

>>> a = np.arange(12).reshape(3, 4)
>>> b1 = np.array([False, True, True])         # first dim selection
>>> b2 = np.array([True, False, True, False])  # second dim selection
>>>
>>> a[b1, :]                                   # selecting rows
array([[ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
>>>
>>> a[b1]                                      # same thing
array([[ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
>>>
>>> a[:, b2]                                   # selecting columns
array([[ 0,  2],
       [ 4,  6],
       [ 8, 10]])
>>>
>>> a[b1, b2]                                  # a weird thing to do
array([ 4, 10])

请注意,一维布尔数组的长度必须与您想要切片的维度(或轴)的长度一致.在前面的例子中, b1 的长度为 3 ( a 中的行数),而 b2 (长度为 4) 适合索引 a 的第二个轴(列).

ix_() 函数#

ix_ 函数可用于组合不同的向量,以便获得每个 n 元组的结果.例如,如果您想计算所有来自向量 a,b 和 c 的三元组的 a+bc:

>>> a = np.array([2, 3, 4, 5])
>>> b = np.array([8, 5, 4])
>>> c = np.array([5, 4, 6, 8, 3])
>>> ax, bx, cx = np.ix_(a, b, c)
>>> ax
array([[[2]],

       [[3]],

       [[4]],

       [[5]]])
>>> bx
array([[[8],
        [5],
        [4]]])
>>> cx
array([[[5, 4, 6, 8, 3]]])
>>> ax.shape, bx.shape, cx.shape
((4, 1, 1), (1, 3, 1), (1, 1, 5))
>>> result = ax + bx * cx
>>> result
array([[[42, 34, 50, 66, 26],
        [27, 22, 32, 42, 17],
        [22, 18, 26, 34, 14]],

       [[43, 35, 51, 67, 27],
        [28, 23, 33, 43, 18],
        [23, 19, 27, 35, 15]],

       [[44, 36, 52, 68, 28],
        [29, 24, 34, 44, 19],
        [24, 20, 28, 36, 16]],

       [[45, 37, 53, 69, 29],
        [30, 25, 35, 45, 20],
        [25, 21, 29, 37, 17]]])
>>> result[3, 2, 4]
17
>>> a[3] + b[2] * c[4]
17

你也可以像下面这样实现 reduce:

>>> def ufunc_reduce(ufct, *vectors):
...    vs = np.ix_(*vectors)
...    r = ufct.identity
...    for v in vs:
...        r = ufct(r, v)
...    return r

然后像这样使用它:

>>> ufunc_reduce(np.add, a, b, c)
array([[[15, 14, 16, 18, 13],
        [12, 11, 13, 15, 10],
        [11, 10, 12, 14,  9]],

       [[16, 15, 17, 19, 14],
        [13, 12, 14, 16, 11],
        [12, 11, 13, 15, 10]],

       [[17, 16, 18, 20, 15],
        [14, 13, 15, 17, 12],
        [13, 12, 14, 16, 11]],

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

与普通的 ufunc.reduce 相比,此版本 reduce 的优点是它利用了 broadcasting rules ,从而避免创建大小为输出乘以向量数的参数数组.

用字符串索引#

请参阅 结构化数组 .

技巧与提示#

这里我们提供一个简短而有用的技巧列表.

“自动”重塑#

要更改数组的尺寸,您可以省略其中一个大小,然后会自动推断出来:

>>> a = np.arange(30)
>>> b = a.reshape((2, -1, 3))  # -1 means "whatever is needed"
>>> b.shape
(2, 5, 3)
>>> b
array([[[ 0,  1,  2],
        [ 3,  4,  5],
        [ 6,  7,  8],
        [ 9, 10, 11],
        [12, 13, 14]],

       [[15, 16, 17],
        [18, 19, 20],
        [21, 22, 23],
        [24, 25, 26],
        [27, 28, 29]]])

向量堆叠#

我们如何从等长的行向量列表中构造一个二维数组?在 MATLAB 中,这非常容易:如果 xy 是两个长度相同的向量,您只需要执行 m=[x;y] .在 NumPy 中,这可以通过函数 column_stack , dstack , hstackvstack 来实现,具体取决于要进行堆叠的维度.例如:

>>> x = np.arange(0, 10, 2)
>>> y = np.arange(5)
>>> m = np.vstack([x, y])
>>> m
array([[0, 2, 4, 6, 8],
       [0, 1, 2, 3, 4]])
>>> xy = np.hstack([x, y])
>>> xy
array([0, 2, 4, 6, 8, 0, 1, 2, 3, 4])

超过两个维度时,这些函数背后的逻辑可能很奇怪.

直方图#

应用于数组的 NumPy histogram 函数返回一对向量:数组的直方图和一个 bin 边缘向量.注意: matplotlib 还有一个构建直方图的函数(称为 hist ,与 Matlab 中一样),它与 NumPy 中的函数不同.主要区别在于 pylab.hist 自动绘制直方图,而 numpy.histogram 仅生成数据.

>>> import numpy as np
>>> rg = np.random.default_rng(1)
>>> import matplotlib.pyplot as plt
>>> # Build a vector of 10000 normal deviates with variance 0.5^2 and mean 2
>>> mu, sigma = 2, 0.5
>>> v = rg.normal(mu, sigma, 10000)
>>> # Plot a normalized histogram with 50 bins
>>> plt.hist(v, bins=50, density=True)       # matplotlib version (plot)
(array...)
>>> # Compute the histogram with numpy and then plot it
>>> (n, bins) = np.histogram(v, bins=50, density=True)  # NumPy version (no plot)
>>> plt.plot(.5 * (bins[1:] + bins[:-1]), n) 
../_images/quickstart-2.png

使用 Matplotlib >=3.4,您还可以使用 plt.stairs(n, bins) .

进一步阅读#