NumPy

Table of Contents

1. NumPy 简介

NumPy (Numeric Python) is the fundamental package for scientific computing with Python.

NumPy 是一个运行速度非常快的数学库,主要用于数组计算。它可以让你在 Python 中使用向量和数学矩阵,以及许多用 C 语言实现的底层函数,你还可以体验到很好的运行效率。

参考:
http://www.numpy.org/
https://docs.scipy.org/doc/numpy/user/quickstart.html
NumPy Reference
http://old.sebug.net/paper/books/scipydoc/numpy_intro.html

2. NumPy 基础:ndarray

NumPy 中定义的最重要的对象是称为 ndarray 的 N 维数组类型,它是相同类型元素的集合。

下面是创建 2 维数组(大小为 2x3)和 3 维数组(大小为 2x3x4)的简单例子:

>>> import numpy as np
>>> x = np.array([[1, 2, 3], [4, 5, 6]])
>>> type(x)
<type 'numpy.ndarray'>
>>> x.shape
(2, 3)
>>> y = np.array([[[ 0,  1,  2, 3],
                   [ 3,  4,  5, 6],
                   [ 6,  7,  8, 9]],
                  [[18, 19, 20, 21],
                   [21, 22, 23, 24],
                   [24, 25, 26, 27]]])
>>> y.shape
(2, 3, 4)

2.1. ndarray 常见属性

1 是 ndarray 的常见属性。

Table 1: ndarray 常见属性
ndarray 属性 描述
ndim 数组维度。前面例子中 x.ndim=2
shape 形状,即多少行和列。前面例子中 x.shape=(2, 3)
size 元素个数。前面例子中 x.size=6
dtype 元素类型。前面例子中 x.dtype=dtype('int64')
itemsize 每一个条目所占的字节。前面例子中 x.itemsize=8
nbytes 所有元素占的字节。前面例子中 x.nbytes=48

2.1.1. ndarray 内存布局

ndarray 数组在内存中是连续内存块。有两种策略来组织多维数据:一种是 column-major order(Fortran 和 Matlab 采用这种策略),另一种是 row-major order(C 语言中采用这种策略)。如图 1 (摘自 Guide to NumPy, 2.3.1 Contiguous Memory Layout)所示。

numpy_ndarray_memory_layout.jpg

Figure 1: Options for memory layout of a 2-dimensional array

ndarray 既支持 C 风格的内存布局,也支持 Fortran 风格的内存布局。 默认为 C 风格,进行转置操作后变为 Fortran 风格的内存布局。

>>> x = np.array([[1, 2, 3], [4, 5, 6]])
>>> x.flags
  C_CONTIGUOUS : True           # 表明x使用的是C风格的内存布局
  F_CONTIGUOUS : False
  OWNDATA : True
  WRITEABLE : True
  ALIGNED : True
  UPDATEIFCOPY : False
>>> y = x.transpose()
>>> y.flags
  C_CONTIGUOUS : False
  F_CONTIGUOUS : True           # 表明y使用的是Fortran风格的内存布局
  OWNDATA : False
  WRITEABLE : True
  ALIGNED : True
  UPDATEIFCOPY : False

对于一维数组,显然既是 C 风格,又是 Fortran 风格的内存布局。

>>> x = np.array([0, 1, 2])     # 一维数组,显然既是C风格,又是Fortran风格的内存布局
>>> x.flags
  C_CONTIGUOUS : True
  F_CONTIGUOUS : True
  OWNDATA : True
  WRITEABLE : True
  ALIGNED : True
  UPDATEIFCOPY : False

2.2. 创建 ndarray

2 是创建 ndarray 的一些函数。

Table 2: 创建 ndarray 的函数
创建 ndarray 的函数 说明
array 创建 ndarray
empty 创建未初始化的 ndarray
zeros 创建全为 0 的 ndarray
ones 创建全为 1 的 ndarray
full 创建全为指定元素的 ndarray
identity 创建单位矩阵
eye 创建对角线(可指定哪个对角线)为 1,其它元素为 0 的二维数组
arange 基于开始值、终值和步长来创建一维数组
linspace 基于开始值、终值和元素个数来创建一维数组
logspace 和 linspace 类似,对数刻度上均匀分布的一维数据
frombuffer 从 buffer 创建数组
fromstring 从 string 序列中创建数组
fromfunction 从函数创建数组
fromfile 基于 tofile 命令保存的文件创建 ndarray
load 基于 save 命令保存的文件创建 ndarray
loadtxt 基于 csv 文本文件内容创建 ndarray

参考:https://docs.scipy.org/doc/numpy/reference/routines.array-creation.html

2.2.1. 基于 CSV 文件创建 ndarray(loadtxt 实例)

假设“1.txt”内容如下。

$ cat 1.txt
0, 1, 2
3, 4, 5

使用 loadtxt 可以把文本文件中的数据加载为 ndarray。如:

>>> import numpy as np
>>> x = np.loadtxt("1.txt", delimiter=",")       # 指定分隔符为逗号,默认使用空格为分隔符
>>> x
array([[ 0.,  1.,  2.],
       [ 3.,  4.,  5.]])

2.3. 存取元素(Indexing & Slicing)

数组元素的存取方法和 Python 的标准方法相同,如:

>>> a = np.arange(10)
>>> a
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> a[4]                 # 用整数作为下标可以获取数组中的某个元素
4
>>> a[1:3]               # 用范围作为下标获取数组的一个切片,含头(a[1])不含尾(a[3])
array([1, 2])
>>> a[:3]                # 省略开始下标,表示从a[0]开始
array([0, 1, 2])
>>> a[:-2]               # 下标可以使用负数,表示从数组后往前数
array([0, 1, 2, 3, 4, 5, 6, 7])
>>> a[0:5:2]             # 开始下标0,结束下标5,步长2
array([0, 2, 4])

和 Python 的列表序列不同, NumPy 中通过“下标范围”获取的新的数组是原始数组的一个视图。也就是说它与原始数组共享同一块数据空间:

>>> a
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> b=a[1:3]                                      # 通过“下标范围”获取的新的数组是原始数组的一个视图
>>> b[0]=99                                       # 修改了b[0]
>>> a
array([ 0, 99,  2,  3,  4,  5,  6,  7,  8,  9])   # a也被修改了

2.3.1. 多维数组

多维数组有多个轴,所以它的下标需要用多个值来表示,NumPy 中采用组元(tuple)表示数组的下标,也就是用逗号分开多个轴的索引。请看下面实例:

>>> import numpy as np
>>> x = np.array([[ 0,  1,  2,  3,  4,  5],
...               [10, 11, 12, 13, 14, 15],
...               [20, 21, 22, 23, 24, 25],
...               [30, 31, 32, 33, 34, 35],
...               [40, 41, 42, 43, 44, 45],
...               [50, 51, 52, 53, 54, 55]])
>>>
>>> x[0]
array([0, 1, 2, 3, 4, 5])
>>> x[1]
array([10, 11, 12, 13, 14, 15])
>>> x[1, 4]                      # 逗号分开。逗号前是0轴索引,逗号后是1轴索引。
14
>>> x[0, 2:5]                    # 逗号分开。逗号前是0轴索引,逗号后是1轴索引。
array([2, 3, 4])
>>> x[1, 2:5]                    # 逗号分开。逗号前是0轴索引,逗号后是1轴索引。
array([12, 13, 14])
>>> x[:, 2]
array([ 2, 12, 22, 32, 42, 52])
>>> x[1:4, 2:6]                  # 逗号分开。逗号前是0轴索引,逗号后是1轴索引。
array([[12, 13, 14, 15],
       [22, 23, 24, 25],
       [32, 33, 34, 35]])
>>> x[1:, 2:6]                   # 逗号分开。逗号前是0轴索引,逗号后是1轴索引。
array([[12, 13, 14, 15],
       [22, 23, 24, 25],
       [32, 33, 34, 35],
       [42, 43, 44, 45],
       [52, 53, 54, 55]])

下面是设置子多维数组元素为同一个数字的例子:

>>> x[1:4, 2:6] = [[100, 100, 100, 100], [100, 100, 100, 100], [100, 100, 100, 100]]
>>> x[1:4, 2:6]
array([[100, 100, 100, 100],
       [100, 100, 100, 100],
       [100, 100, 100, 100]])

当然没必要像上面这么繁琐,使用“Broadcast”(后文有介绍)可以更加简单:

>>> x[1:4, 2:6] = 100                    # 发生Broadcast
>>> x[1:4, 2:6]
array([[100, 100, 100, 100],
       [100, 100, 100, 100],
       [100, 100, 100, 100]])
>>> x[1:4, 2:6] = [100, 100, 100, 100]   # 发生Broadcast
>>> x[1:4, 2:6]
array([[100, 100, 100, 100],
       [100, 100, 100, 100],
       [100, 100, 100, 100]])
>>> x[1:4, 2:6] = [1, 2, 3, 4]           # 发生Broadcast
>>> x[1:4, 2:6]
array([[1, 2, 3, 4],
       [1, 2, 3, 4],
       [1, 2, 3, 4]])

2.3.2. 高级索引:整数序列索引

可以使用“整数序列”对数组元素进行存取,这时将使用整数序列中的每个元素作为下标,整数序列可以是 Python 中的列表或者 NumPy 中的数组。 使用整数序列作为下标获得的数组不和原始数组共享数据空间。 例如:

>>> import numpy as np
>>> x = np.array([9, 8, 7, 6, 5, 4, 3, 2, 1, 0])
>>> y = x[[0, 2, 3]]                    # 使用整数序列 [0, 2, 3] 对数组元素进行存取,相当于以序列中每个元素作为下标进行索引。
>>> y
array([9, 7, 6])
>>> y[0] = 1111                         # 使用整数序列作为下标获得的数组y和原数组x不共享空间。修改y,不会影响到x。
>>> y
array([1111,    7,    6])
>>> x
array([9, 8, 7, 6, 5, 4, 3, 2, 1, 0])

下面是多维数组中,使用整数序列索引的例子:

>>> import numpy as np
>>> x = np.array([[ 0,  1,  2,  3,  4,  5],
...               [10, 11, 12, 13, 14, 15],
...               [20, 21, 22, 23, 24, 25],
...               [30, 31, 32, 33, 34, 35],
...               [40, 41, 42, 43, 44, 45],
...               [50, 51, 52, 53, 54, 55]])
>>>
>>> x[2, [1,2,3,5]]           # 第0轴是一个数,第1轴是整数序列 [1,2,3,5]
array([21, 22, 23, 25])
>>> x[2:, [1,2,3,5]]          # 第0轴是一个范围,第1轴是整数序列 [1,2,3,5]
array([[21, 22, 23, 25],
       [31, 32, 33, 35],
       [41, 42, 43, 45],
       [51, 52, 53, 55]])

2.3.3. 高级索引:布尔数组索引

当使用布尔数组 b 作为下标存取数组 x 中的元素时,将收集数组 x 中所有在数组 b 中对应下标为 True 的元素。 使用布尔数组作为下标获得的数组不和原始数组共享数据空间。 注意这种方式只对应于 NumPy 中的布尔数组,不能使用 Python 中的布尔列表。

>>> import numpy as np
>>> x = np.array([4, 3, 2, 1, 0])
>>> x[np.array([True, False, False, True])]    # 使用布尔数组 np.array([True, False, False, True]) 作为索引,收集数组x中对应下标为True的元素
array([4, 1])
>>> x[[True, False, True, False, False]]       # 如果使用布尔列表 [True, False, True, False, False] 作为索引,则把True当作1, False当作0,按照整数序列方式获取x中的元素
array([3, 4, 3, 4, 4])

NumPy 布尔数组一般不是手工产生,而是使用布尔运算的 Universal Function 函数产生。如:

>>> x = np.random.rand(10)            # 产生长度为10,元素值在(0,1)区间内的随机数的数组
>>> x
array([ 0.31405475,  0.25280231,  0.08440324,  0.27874072,  0.81103738,
        0.19872166,  0.62412707,  0.62278573,  0.94702961,  0.18089782])
>>> x > 0.5                           # > 是Universal Function函数,返回布尔数组
array([False, False, False, False,  True, False,  True,  True,  True, False], dtype=bool)
>>> x[x > 0.5]                        # 使用x>0.5返回的布尔数组收集x中的元素,因此得到的结果是x中所有大于0.5的元素的数组
array([ 0.81103738,  0.62412707,  0.62278573,  0.94702961])

2.4. Shape manipulation

3 总结了 ndarray 形状操作的相关函数。

Table 3: Shape manipulation
Shape manipulation function Description
ndarray.reshape(shape[, order]) Returns an array containing the same data with a new shape.
ndarray.resize(new_shape[, refcheck]) Change shape and size of array in-place.
ndarray.transpose(*axes) Returns a view of the array with axes transposed.
ndarray.swapaxes(axis1, axis2) Return a view of the array with axis1 and axis2 interchanged.
ndarray.flatten([order]) Return a copy of the array collapsed into one dimension.
ndarray.ravel([order]) Return a flattened array.
ndarray.squeeze([axis]) Remove single-dimensional entries from the shape of a.

参考:
https://docs.scipy.org/doc/numpy/user/quickstart.html#shape-manipulation
https://docs.scipy.org/doc/numpy/reference/arrays.ndarray.html#shape-manipulation

2.4.1. reshape 实例

下面是 reshape 的使用实例:

>>> import numpy as np
>>> x = np.array([1, 2, 3, 4, 5, 6, 7, 8])
>>> x.reshape(2, 4)
array([[1, 2, 3, 4],
       [5, 6, 7, 8]])
>>> x.reshape(4, 2)
array([[1, 2],
       [3, 4],
       [5, 6],
       [7, 8]])
>>> x.reshape(2, 2, 2)
array([[[1, 2],
        [3, 4]],
       [[5, 6],
        [7, 8]]])

3. Array Calculation

4 中是数组计算的相关函数。它们中的大部分函数接受参数 axis:
(1) 如果省略参数 axis,则表示对整个数组进行操作;
(2) 如果提供了参数 axis,则表示在对应坐标轴上进行操作。

>>> x = np.array([[1, 2, 3], [4, 5, 6]])
>>> x.sum()              # 省略了axis,对所有元素求和
21
>>> x.sum(0)
array([5, 7, 9])         # 5=1+4, 7=2+5, 9=3+6
>>> x.sum(1)
array([ 6, 15])          # 6=1+2+3, 15=4+5+6
>>> x.min()              # 省略了axis,在所有元素中找最小元素
1
>>> x.min(0)
array([1, 2, 3])
>>> x.min(1)
array([1, 4])
Table 4: 数组计算相关函数
Calculation 函数 说明
ndarray.argmax([axis, out]) Return indices of the maximum values along the given axis.
ndarray.min([axis, out, keepdims]) Return the minimum along a given axis.
ndarray.argmin([axis, out]) Return indices of the minimum values along the given axis of a.
ndarray.ptp([axis, out]) Peak to peak (maximum - minimum) value along a given axis.
ndarray.clip([min, max, out]) Return an array whose values are limited to [min, max].
ndarray.conj() Complex-conjugate all elements.
ndarray.round([decimals, out]) Return a with each element rounded to the given number of decimals.
ndarray.trace([offset, axis1, axis2, dtype, out]) Return the sum along diagonals of the array.
ndarray.sum([axis, dtype, out, keepdims]) Return the sum of the array elements over the given axis.
ndarray.cumsum([axis, dtype, out]) Return the cumulative sum of the elements along the given axis.
ndarray.mean([axis, dtype, out, keepdims]) Returns the average of the array elements along given axis.
ndarray.var([axis, dtype, out, ddof, keepdims]) Returns the variance of the array elements, along given axis.
ndarray.std([axis, dtype, out, ddof, keepdims]) Returns the standard deviation of the array elements along given axis.
ndarray.prod([axis, dtype, out, keepdims]) Return the product of the array elements over the given axis
ndarray.cumprod([axis, dtype, out]) Return the cumulative product of the elements along the given axis.
ndarray.all([axis, out, keepdims]) Returns True if all elements evaluate to True.
ndarray.any([axis, out, keepdims]) Returns True if any of the elements of a evaluate to True.

参考:https://docs.scipy.org/doc/numpy/reference/arrays.ndarray.html#calculation

4. Universal Function (对每个元素进行操作的函数)

Universal Function(简写为 ufunc)是一种能对数组的每个元素进行操作的函数。 NumPy 内置的许多 ufunc 函数都是在 C 语言级别实现的,它们的计算速度非常快。

下面是 ufunc numpy.sin 的例子:

>>> import numpy as np
>>> x = np.linspace(0, 2*np.pi, 9)
>>> y = np.sin(x)                     # numpy.sin 是 ufunc ,它对数组每个元素进行操作
>>> y
array([  0.00000000e+00,   7.07106781e-01,   1.00000000e+00,
         7.07106781e-01,   1.22464680e-16,  -7.07106781e-01,
        -1.00000000e+00,  -7.07106781e-01,  -2.44929360e-16])

下面是 ufunc numpy.add 的例子:

>>> import numpy as np
>>> x = np.array([[1, 2, 3],
...               [4, 5, 6]])
>>> y = np.array([[11, 12, 13],
...               [14, 15, 16]])
>>> np.add(x, y)            # numpy.add 是 ufunc
array([[12, 14, 16],
       [18, 20, 22]])
>>> x+y                     # 由于Python的操作符重载功能,np.add(x, y)可以简单地写为x+y
array([[12, 14, 16],
       [18, 20, 22]])

参考:https://docs.scipy.org/doc/numpy/reference/ufuncs.html

4.1. Broadcasting

当我们使用 ufunc 函数对两个数组进行计算时,ufunc 函数会对这两个数组的对应元素进行计算,因此它要求这两个数组有相同的大小(shape 相同)。如果两个数组的 shape 不同的话,会进行广播(broadcasting)处理。

这里不详细介绍广播规则。下面是一个广播的实例:

>>> import numpy as np
>>> a = np.array([[  0.0,   0.0,   0.0],
                  [ 10.0,  10.0,  10.0],
                  [ 20.0,  20.0,  20.0],
                  [ 30.0,  30.0,  30.0]])
>>> b = np.array([1.0, 2.0, 3.0])
>>> a+b                         # a和b的形状不一样,会进行广播(broadcasting)处理
array([[  1.,   2.,   3.],
       [ 11.,  12.,  13.],
       [ 21.,  22.,  23.],
       [ 31.,  32.,  33.]])

参考:
http://old.sebug.net/paper/books/scipydoc/numpy_intro.html#id6
https://www.tutorialspoint.com/numpy/numpy_broadcasting.htm
https://docs.scipy.org/doc/numpy/reference/ufuncs.html#broadcasting

5. Matrix

在 NumPy 中的 matrix 是 array 的子类,它所以继承了 array 的所有特性并且有自己独特的地方。比如使用 matrix 时, * 是矩阵乘法;而使用 array 时, * 是 ufunc(每个对应元素相乘)。

>>> import numpy as np
>>> a = np.matrix('1 2; 3 4')      # 创建matrix。注:np.matrix 可以简写为 np.mat
>>> a
matrix([[1, 2],
        [3, 4]])
>>> a.T                            # a的转置矩阵
matrix([[1, 3],
        [2, 4]])
>>> a.I                            # a的逆矩阵
matrix([[-2. ,  1. ],
        [ 1.5, -0.5]])

参考:
https://docs.scipy.org/doc/numpy/reference/generated/numpy.matrix.html

Author: cig01

Created: <2016-04-29 Fri>

Last updated: <2018-12-02 Sun>

Creator: Emacs 27.1 (Org mode 9.4)