numpy 读法:英['nʌmpi]
NumPy是Python中科学计算的基本包。它是一个Python库,提供了一个多维数组对象,各种派生对象(如屏蔽数组(masked arrays)和矩阵),以及用于对数组进行快速操作的各种例程,包括数学,逻辑,形状操作,排序,选择,I/O,离散傅里叶变换,基本线性代数,基本统计运算,随机模拟等等。
NumPy包的核心是ndarray对象。它封装了同构数据类型的n维数组,其中许多操作在编译的代码中执行以提高性能。NumPy数组和标准Python序列之间有几个重要的区别:
序列大小和速度在科学计算中尤为重要。作为一个简单的示例,考虑将一维序列中的每个元素与另一个长度相同的序列中的相应元素相乘的情况。如果数据存储在两个 Python 列表中,a 和 b,我们可以遍历每个元素:
c = []
for i in range(len(a)):
c.append(a[i]*b[i])
这是正确的答案,但如果 a 和 b 都包含数百万个数字,我们将为 Python 中循环的低效率付出代价。 我们可以在 C 中通过编写来更快地完成相同的任务(为了清楚起见,忽略变量声明和初始化、内存分配等):
for (i=0; i<rows; i++):{
c[i] = a[i] * b[i];
}
这节省了解释Python代码和操作Python对象所涉及的所有开销,但代价是牺牲了在Python中编码所获得的好处。此外,所需的编码工作随着我们数据的维度而增加。例如,在 2-D 数组的情况下,C 代码(像以前一样删节)扩展为:
for (i=0; i<rows; i++):{
for (i=0; j<colcumns; j++):{
c[i][j] = a[i][j] * b[i][j];
}
}
NumPy为我们提供了两全其美的优势:当涉及ndarray时,逐个元素的操作是"默认模式",但逐个元素的操作是由预编译的C代码快速执行的。在 NumPy 中:
c = a * b
以接近C的速度做了前面的例子,但是由于代码的简单性,我们期望从基于Python的东西中得到。事实上,NumPy甚至更简单!最后一个例子说明了NumPy的两个功能,它们是其大部分功能的基础:矢量化和广播。
矢量化描述了代码中没有任何显式循环,索引等 - 当然,这些事情只是在优化的,预编译的C代码中的"幕后"发生。矢量化代码具有许多优点,其中包括:
广播(Broadcasting)是用于描述操作的隐式逐个元素行为的术语;一般来说,在NumPy中,所有操作,不仅仅是算术运算,包括逻辑,位,函数等,都以这种隐式逐个元素的方式运行,即它们广播。此外,在上面的示例中,可以是相同形状的多维数组,也可以是标量和数组,甚至是两个具有不同形状的数组,前提是较小的数组可以扩展为较大的形状,从而使生成的广播是明确的。
需要了解一些Python。有关学习,可以参阅Python 3.10教程。
要处理这些下面这些示例,除了NumPy之外,还需要安装matplotlib。
这是NumPy中数组的快速概述。它演示了 n 维 (n>=2) 数组的表示方式和操作方式。特别是,如果您不知道如何将常用函数应用于 n 维数组(不使用 for 循环),或者如果您想了解 n 维数组的轴和形状属性,本文可能会有所帮助。
阅读本文后,应该能够:
NumPy的主要对象是同构多维数组。它是一个元素(通常是数字)表,所有元素都具有相同的类型,由非负整数元组索引。在NumPy中,维度称为轴。
例如,用于 3D 空间中点的坐标的数组[1, 2, 1]具有一个轴。该轴中有3个元素,因此我们说它的长度为3。在下面所示的示例中,数组有 2 个轴。第一个轴的长度为2,第二个轴的长度为3:
[[1. , 0. , 0.],
[0. , 1. , 2.]]
NumPy 的数组类称为 ndarray,它的别名也叫数组。 请注意,numpy.array 与标准 Python 库类 array.array 不同,后者只处理一维数组并且提供的功能较少。 ndarray 对象更重要的属性是:
ndarray.ndim
数组的轴数(尺寸)。
ndarray.shape
数组的维度。 这是一个整数元组,表示每个维度中数组的大小。 对于具有 n 行和 m 列的矩阵,形状将为 (n,m)。 因此,元组的长度就是轴数 ndim。
ndarray.size
数组的元素总数。这等于ndarray.shape的元素的乘积。
ndarray.dtype
描述数组中元素类型的对象。可以使用标准Python类型创建或指定dtype。此外,NumPy还提供了自己的类型,numpy.int32、numpy.int16 和 numpy.float64 就是一些例子。
ndarray.itemsize
数组中每个元素的大小(以byte为单位)。 例如,float64 类型的元素数组的项目大小为 8 (=64/8),而 complex32 类型的一个元素的项目大小为 4 (=32/8)。 它相当于 ndarray.dtype.itemsize。
ndarray.data
包含数组的实际元素的buffer。通常我们不需要使用此属性,因为我们将使用索引工具访问数组中的元素。
import numpy as np
>>> 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,而不是提供单个序列作为参数。
>>> b = np.array(1, 2, 3, 4) # WRONG
Traceback (most recent call last):
File "" , line 1, in <module>
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函数创建一个充满 1 的数组,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([[1.5, 2. , 3. ],
[4. , 5. , 6. ]])
为了创建数字序列,NumPy提供了类似于Python内置函数range的方法:arange,但返回一个数组。
>>> np.arange(10, 30, 5)
array([10, 15, 20, 25])
>>> np.arange(0, 2, 0.3) # 它接受float参数
array([0. , 0.3, 0.6, 0.9, 1.2, 1.5, 1.8])
当 arange 与浮点参数一起使用时,由于浮点精度有限,通常无法预测获得的元素数量。 出于这个原因,通常最好使用函数 linspace 接收我们想要的元素数量作为参数,而不是step:
>>> from numpy import pi
>>> np.linspace(0, 3, 9) # # 从0到3的9个数字
array([0. , 0.375, 0.75 , 1.125, 1.5 , 1.875, 2.25 , 2.625, 3. ])
>>> x = np.linspace(0, 2*pi, 100) # 在很多点评估函数很有用
>>> f = np.sin(x)
>>> f
array([ 0.00000000e+00, 6.34239197e-02, 1.26592454e-01, 1.89251244e-01,
2.51147987e-01, 3.12033446e-01, 3.71662456e-01, 4.29794912e-01,
4.86196736e-01, 5.40640817e-01, 5.92907929e-01, 6.42787610e-01,
6.90079011e-01, 7.34591709e-01, 7.76146464e-01, 8.14575952e-01,
8.49725430e-01, 8.81453363e-01, 9.09631995e-01, 9.34147860e-01,
9.54902241e-01, 9.71811568e-01, 9.84807753e-01, 9.93838464e-01,
9.98867339e-01, 9.99874128e-01, 9.96854776e-01, 9.89821442e-01,
9.78802446e-01, 9.63842159e-01, 9.45000819e-01, 9.22354294e-01,
8.95993774e-01, 8.66025404e-01, 8.32569855e-01, 7.95761841e-01,
7.55749574e-01, 7.12694171e-01, 6.66769001e-01, 6.18158986e-01,
5.67059864e-01, 5.13677392e-01, 4.58226522e-01, 4.00930535e-01,
3.42020143e-01, 2.81732557e-01, 2.20310533e-01, 1.58001396e-01,
9.50560433e-02, 3.17279335e-02, -3.17279335e-02, -9.50560433e-02,
-1.58001396e-01, -2.20310533e-01, -2.81732557e-01, -3.42020143e-01,
-4.00930535e-01, -4.58226522e-01, -5.13677392e-01, -5.67059864e-01,
-6.18158986e-01, -6.66769001e-01, -7.12694171e-01, -7.55749574e-01,
-7.95761841e-01, -8.32569855e-01, -8.66025404e-01, -8.95993774e-01,
-9.22354294e-01, -9.45000819e-01, -9.63842159e-01, -9.78802446e-01,
-9.89821442e-01, -9.96854776e-01, -9.99874128e-01, -9.98867339e-01,
-9.93838464e-01, -9.84807753e-01, -9.71811568e-01, -9.54902241e-01,
-9.34147860e-01, -9.09631995e-01, -8.81453363e-01, -8.49725430e-01,
-8.14575952e-01, -7.76146464e-01, -7.34591709e-01, -6.90079011e-01,
-6.42787610e-01, -5.92907929e-01, -5.40640817e-01, -4.86196736e-01,
-4.29794912e-01, -3.71662456e-01, -3.12033446e-01, -2.51147987e-01,
-1.89251244e-01, -1.26592454e-01, -6.34239197e-02, -2.44929360e-16])
打印数组时,NumPy 以类似于嵌套列表的方式显示它,但具有以下布局:
最后一个轴从左到右打印,
倒数第二个从上到下打印,
其余部分也从上到下打印,每个切片与下一个切片之间用空行隔开。
然后将一维数组打印为行,将二维数组打印为矩阵,将三维数组打印为矩阵列表。
>>> a = np.arange(6) # 一维
>>> print(a)
[0 1 2 3 4 5]
>>>
>>> b = np.arange(12).reshape(4, 3) # 二维
>>> print(b)
[[ 0 1 2]
[ 3 4 5]
[ 6 7 8]
[ 9 10 11]]
>>>
>>> c = np.arange(24).reshape(2, 3, 4) # 三维
>>> 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]]]
有关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更改打印选项。
>>> import sys
>>> import numpy as np
>>> print(sys.maxsize)
9223372036854775807
>>> np.set_printoptions(threshold=sys.maxsize)
数组上的算术运算符按元素逐个应用,创建一个新数组并用结果填充。
>>> 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 # 元素乘积
array([[2, 0],
[0, 4]])
>>> A @ B # 矩阵积
array([[5, 4],
[3, 4]])
>>> A.dot(B) # 矩阵积
array([[5, 4],
[3, 4]])
某些操作,例如 += 和 *=,会在适当的位置修改现有数组,而不是创建新数组。
>>> rg = np.random.default_rng(1) # 创建默认随机数生成器的实例
>>> 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 不会自动转换为整数类型
Traceback (most recent call last):
File "" , line 1, in <module>
numpy.core._exceptions.UFuncTypeError: Cannot cast ufunc 'add' output from dtype('float64') to dtype('int32') with casting rule 'same_kind'
当处理不同类型的数组时,生成的数组的类型对应于更通用或更精确的数组类型(这种行为称为 upcasting,向上转型)
>>> import math
>>> a = np.ones(3, dtype=np.int32)
>>> b = np.linspace(0, math.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
默认情况下,这些操作适用于数组,就好像它是一个数字列表一样,无论其形状如何。 但是,通过指定轴参数,您可以沿数组的指定轴应用操作:
>>> 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) # 每列的总和
array([12, 15, 18, 21])
>>>
>>> b.min(axis=1) # 每行的最小值
array([0, 4, 8])
>>> b.cumsum(axis=1) # 沿每一行的累积总和
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) # e的矩阵元素次幂
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.])
通用函数包括:
exp | sqrt | add | all | any |
apply_along_axis | argmax | argmin | argsort | average |
bincount | ceil | clip | conj | corrcoef |
cov | cross | cumprod | cumsum | diff |
dot | floor | inner | invert | lexsort |
max | maximum | mean | median | min |
minimum | nonzero | outer | prod | re |
round | sort | std | sum | trace |
transpose | var | vdot | vectorize | where |
一维数组可以被索引,切片和迭代,就像列表和其他Python序列一样。
>>> a = np.arange(10)**3
>>> a
array([ 0, 1, 8, 27, 64, 125, 216, 343, 512, 729], dtype=int32)
>>> a[2]
8
>>> a[2:5]
array([ 8, 27, 64], dtype=int32)
>>># 下面切片相当于a[0:6:2]
>>># 从0索引到6索引,每第2个元素为1000
>>> a[:6:2] = 1000
>>> a
array([1000, 1, 1000, 27, 1000, 125, 216, 343, 512, 729],
dtype=int32)
>>> a[::-1] # a翻转
array([ 729, 512, 343, 216, 125, 1000, 27, 1000, 1, 1000],
dtype=int32)
>>> for i in a:
... print(i**(1 / 3.))
...
9.999999999999998
1.0
9.999999999999998
3.0
9.999999999999998
5.0
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] # b的第二列的每一行
array([ 1, 11, 21, 31, 41])
>>> b[:, 1] # b的第二列的每一行
array([ 1, 11, 21, 31, 41])
>>> b[1:3, :] # b的第二行和第三行的每一列
array([[10, 11, 12, 13],
[20, 21, 22, 23]])
当提供的索引数少于轴数时,缺少的索引将被视为完整切片:
>>> 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[-1] # 最后一行, 等价于b[-1, :]
array([40, 41, 42, 43])
b[i] 中括号内的表达式被视为 i 后跟尽可能多的 实例 :表示剩余的轴。 NumPy 还允许使用点像b[i, …] 来编写它。
点 (…) 表示生成完整索引元组所需的冒号。 例如,如果 x 是一个有 5 个轴的数组,那么
>>> c = np.array([[[0, 1, 2],[10, 12, 13]], [[100, 101, 102],[110, 112, 113]]]) # 一个 3D 数组(两个堆叠的 2D 数组)
>>> c.shape
(2, 2, 3)
>>> c[1, ...] # 与 c[1, :, :] 或 c[1] 相同
array([[100, 101, 102],
[110, 112, 113]])
>>> c[..., 2] # 与c[:, :, 2] 相同
array([[ 2, 13],
[102, 113]])
迭代多维数组是相对于第一个轴完成的:
>>> 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]])
>>> 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属性,该属性是数组中所有元素的迭代器:
>>> 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
数组具有由沿每个轴的元素数指定的形状:
>>> rg = np.random.default_rng(1)
>>> a = np.floor(10 * rg.random((3, 4)))
>>> a
array([[5., 9., 1., 9.],
[3., 4., 8., 4.],
[5., 0., 7., 5.]])
>>> a.shape
(3, 4)
数组的形状可以使用各种命令进行更改。请注意,以下三个命令都返回已修改的数组,但不更改原始数组:
>>> a.ravel() # 返回一个展平的数组
array([5., 9., 1., 9., 3., 4., 8., 4., 5., 0., 7., 5.])
>>> a.reshape(6, 2) # 返回具有修改形状的数组
array([[5., 9.],
[1., 9.],
[3., 4.],
[8., 4.],
[5., 0.],
[7., 5.]])
>>> a.T # 返回转置数组
array([[5., 3., 5.],
[9., 4., 0.],
[1., 8., 7.],
[9., 4., 5.]])
>>> a.T.shape
(4, 3)
>>> a.shape
(3, 4)
ravel 生成的数组中元素的顺序通常是“C-style”,即最右边的索引“变化最快”,所以 a[0, 0] 之后的元素是 a[0, 1]。 如果阵列被重新塑造成其他形状,阵列再次被视为“C-style”。 NumPy 通常会创建按此顺序存储的数组,因此 ravel 通常不需要复制其参数,但如果数组是通过获取另一个数组的切片或使用不寻常的选项创建的,则可能需要复制它。 还可以使用可选参数指示函数 ravel 和 reshape 使用 FORTRAN 样式的数组,其中最左边的索引变化最快。
reshape 函数返回其参数并带有修改后的形状,而 ndarray.resize 方法修改数组本身:
>>> a.resize((2, 6))
>>> a
array([[5., 9., 1., 9., 3., 4.],
[8., 4., 5., 0., 7., 5.]])
如果在整形操作中将维度指定为 -1,则自动计算其他维度:
>>> a.reshape(3, -1)
array([[5., 9., 1., 9.],
[3., 4., 8., 4.],
[5., 0., 7., 5.]])
也可以看看:
ndarray.shape, reshape, resize, ravel
多个数组可以沿不同的轴堆叠在一起:
>>> a = np.floor(10 * rg.random((2, 2)))
>>> a
array([[3., 7.],
[3., 4.]])
>>> b = np.floor(10 * rg.random((2, 2)))
>>> b
array([[1., 4.],
[2., 2.]])
>>> np.vstack((a, b)) # 纵向堆叠
array([[3., 7.],
[3., 4.],
[1., 4.],
[2., 2.]])
>>> np.hstack((a, b)) # 横向堆叠
array([[3., 7., 1., 4.],
[3., 4., 2., 2.]])
函数 column_stack 将1D数组作为列堆叠到 2D 数组中。 仅对 2D 数组等效于 hstack:
>>> from numpy import newaxis
>>> np.column_stack((a, b)) # 使用二维数组
array([[3., 7., 1., 4.],
[3., 4., 2., 2.]])
>>> a = np.array([4., 2.])
>>> b = np.array([3., 8.])
>>> np.column_stack((a, b)) # 返回一个二维数组
array([[4., 3.],
[2., 8.]])
>>> np.hstack((a, b)) # 结果不一样
array([4., 2., 3., 8.])
>>> a[:, newaxis] # 添加新维度,将 `a` 视为 2D 列向量
array([[4.],
[2.]])
>>> np.column_stack((a[:, newaxis], b[:, newaxis]))
array([[4., 3.],
[2., 8.]])
>>> np.hstack((a[:, newaxis], b[:, newaxis])) # 结果一样
array([[4., 3.],
[2., 8.]])
另一方面,对于任何输入数组,函数 row_stack 等价于 vstack。 实际上,row_stack 是 vstack 的别名:
>>> np.column_stack is np.hstack
False
>>> np.row_stack is np.vstack
True
通常,对于具有两个以上维度的数组,hstack堆栈沿其第二个轴,vstack堆栈沿其第一个轴,并且连接允许使用可选参数,给出应沿其发生串联的轴的数量。
在复杂的情况下,r_ 和 c_ 对于通过沿一个轴堆叠数字来创建数组很有用。 它们允许使用range:
>>> np.r_[1:4, 0, 4] array([1, 2, 3, 0, 4])
当使用数组作为参数时,r_ 和 c_ 的默认行为类似于 vstack 和 hstack,但允许一个可选参数给出连接的轴的编号。
使用hsplit,可以沿着其水平轴拆分数组,方法是指定要返回的等形数组的数量,或者指定应在其之后进行除法的列:
>>> a = np.floor(10 * rg.random((2, 12)))
>>> a
array([[7., 2., 4., 9., 9., 7., 5., 2., 1., 9., 5., 1.],
[6., 7., 6., 9., 0., 5., 4., 0., 6., 8., 5., 2.]])
>>># 将 `a` 拆分为 3
>>> np.hsplit(a, 3)
[array([[7., 2., 4., 9.],
[6., 7., 6., 9.]]), array([[9., 7., 5., 2.],
[0., 5., 4., 0.]]), array([[1., 9., 5., 1.],
[6., 8., 5., 2.]])]
>>># 在第三列和第四列之后拆分`a`
>>> np.hsplit(a, (3, 4))
[array([[7., 2., 4.],
[6., 7., 6.]]), array([[9.],
[9.]]), array([[9., 7., 5., 2., 1., 9., 5., 1.],
[0., 5., 4., 0., 6., 8., 5., 2.]])]
vsplit 沿垂直轴拆分,array_split 允许指定沿哪个轴拆分。
在操作和操作数组时,它们的数据有时会被复制到新数组中,有时则不会。这通常是初学者感到困惑的根源。有三种情况:
简单赋值不会复制对象或其数据。
>>> a = np.array([[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]])
>>> b = a # 没有创建新的对象
>>> b is a # a,b只是同一个ndarray对象的两个名字
True
Python 将可变对象作为引用传递,因此函数调用不会进行复制。
>>> def f(x):
... print(id(x))
...
>>> id(a) # id 是对象的唯一标识符
1266139004528
>>> f(a)
1266139004528
不同的数组对象可以共享相同的数据。 view 方法创建一个查看相同数据的新数组对象。
>>> a = np.array([[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]])
>>> c = a.view()
>>> c is a
False
>>> c.base is a # c 是 a 的数据视图
True
>>> c.flags.owndata
False
>>>
>>> c = c.reshape((2, 6)) # a 的形状没有改变
>>> a.shape
(3, 4)
>>> c[0, 4] = 1234 # a 的数据发生变化
>>> a
array([[ 0, 1, 2, 3],
[1234, 5, 6, 7],
[ 8, 9, 10, 11]])
>>> c
array([[ 0, 1, 2, 3, 1234, 5],
[ 6, 7, 8, 9, 10, 11]])
>>> s = a[:, 1:3]
>>> s[:] = 10 # s[:]是s的视图。注意s= 10 和 s[:] = 10 之间的区别
>>> a
array([[ 0, 10, 10, 3],
[1234, 10, 10, 7],
[ 8, 10, 10, 11]])
>>> c
array([[ 0, 10, 10, 3, 1234, 10],
[ 10, 7, 8, 10, 10, 11]])
copy 方法制作数组及其数据的完整副本。
>>> d = a.copy() # 创建一个带有新数据的新数组对象
>>> d is a
False
>>> d.base is a # d 不与 a 共享任何内容
False
>>> d[0, 0] = 9999
>>> a
array([[ 0, 10, 10, 3],
[1234, 10, 10, 7],
[ 8, 10, 10, 11]])
如果不再需要原始数组,有时应该在切片后调用复制。 例如,假设 a 是一个巨大的中间结果,而最终结果 b 只包含 a 的一小部分,那么在使用切片构造 b 时应该进行深拷贝:
>>> a = np.arange(int(1e8))
>>> b = a[:100].copy()
>>> del a # 可以释放a占用的内存
如果改为使用 b = a[:100] ,则 a 被 b 引用,并且即使执行 del a 也会保留在内存中。
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
>>> a
array([ 0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121])
>>> i = np.array([1, 1, 3, 8, 5]) # 一个索引数组
>>> a[i] # `a` 在 `i` 位置的元素
array([ 1, 1, 9, 64, 25])
>>>
>>> j = np.array([[3, 4], [9, 7]]) # 一个二维索引数组
>>> a[j] # 与 `j` 相同的形状
array([[ 9, 16],
[81, 49]])
当索引数组 a 是多维的时,单个索引数组引用 a 的第一个维度。 以下示例通过使用调色板将标签图像转换为彩色图像来显示此行为。
>>> palette = np.array([[0, 0, 0], # 黑色
... [255, 0, 0], # 红色
... [0, 255, 0], # 绿色
... [0, 0, 255], # 蓝色
... [255, 255, 255]]) # 白色
>>> image = np.array([[0, 1, 2, 0],
... [0, 3, 4, 0]]) # 每个值对应调色板中的一种颜色,形状与索引数组相同,这里0代表取黑色[0, 0, 0], 1代表取红色[255, 0, 0] ...
>>> palette[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]]])
>>> palette[image].shape
(2, 4, 3)
我们还可以给出多个维度的索引。每个维度的索引数组必须具有相同的形状。
>>> 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],
... [1, 2]])
>>> j = np.array([[2, 1],
... [3, 3]])
>>>
>>> a[i, j] # i 和 j 必须具有相同的形状
array([[ 2, 5],
[ 7, 11]])
>>># 它等价于
>>> t = np.array([[a[0][2], a[1][1]],
... [a[1][3], a[2][3]]])
>>> t
array([[ 2, 5],
[ 7, 11]])
>>> a[i, 2] # 2被补齐与a 相同的形状
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)] 完全相同——所以我们可以将 i 和 j 放在一个元组中,然后用它进行索引。
>>> l = (i, j)
>>> a[l]
array([[ 2, 5],
[ 7, 11]])
但是,我们不能通过将 i 和 j 放入一个数组来做到这一点,因为这个数组将被解释为索引 a 的第一个维度。
>>> s = np.array([i, j])
>>> a[s]
Traceback (most recent call last):
File "" , line 1, in <module>
IndexError: index 3 is out of bounds for axis 0 with size 3
>>> 同 a[i, j] 一样
>>> a[tuple(s)]
array([[ 2, 5],
[ 7, 11]])
使用数组编制索引的另一个常见用途是搜索瞬态序列的最大值:
>>> time = np.linspace(20, 145, 5) # 时间尺度, 在指定的间隔内返回均匀间隔的数字
>>> data = np.sin(np.arange(20)).reshape(5, 4)
>>> 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]])
>>> # 每个系列的最大值索引
>>> ind = data.argmax(axis=0)
>>> ind
array([2, 0, 3, 1], dtype=int64)
>>> # 对应于最大值的时间
>>> 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
array([0, 1, 2, 3, 4])
>>> a[[0, 0, 2]] = [1, 2, 3] # a[0] = 2 会取代 a[0] = 1
>>> a
array([2, 1, 3, 3, 4])
这很合理且容易理解,但如果想使用 Python 的 += 结构,它可能无法达到我们的预期:
>>> a = np.arange(5)
>>> a
array([0, 1, 2, 3, 4])
>>> 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)
>>>> a
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
>>> b = a > 4 # 'b' 是具有 'a' 形状的布尔值
>>> b
array([[False, False, False, False],
[False, True, True, True],
[ True, True, True, True]])
>>> a[b] # 包含所选元素的一维数组
array([ 5, 6, 7, 8, 9, 10, 11])
此属性在分配赋值中非常有用,现在我们给原来大于4的元素重新赋值为0:
>>> a[b] = 0
>>> a
array([[0, 1, 2, 3],
[4, 0, 0, 0],
[0, 0, 0, 0]])
您可以查看以下示例,了解如何使用布尔索引生成Mandelbrot集的图像:
import numpy as np
import matplotlib.pyplot as plt
import pylab
def mandelbrot(h, w, maxit=20, r=2):
# 返回大小为 (h,w) 的 Mandelbrot 分形图像。
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 # 谁在发散
div_now = diverge & (divtime == maxit) # 现在谁在发散
divtime[div_now] = i # 注意什么时候
z[diverge] = r # 避免过多的偏离
return divtime
plt.imshow(mandelbrot(400, 400))
pylab.show()
第二种使用布尔索引的方式更类似于整数索引;对于数组的每个维度,我们给出一个一维布尔数组来选择我们想要的切片:
>>> a = np.arange(12).reshape(3, 4)
>>> b1 = np.array([False, True, True]) # 第一维选择
>>> b2 = np.array([True, False, True, False]) # 第二维选择
>>>
>>> a[b1,:] # 选择行
array([[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
>>>
>>> a[b1] # 同上
array([[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
>>>
>>> a[:, b2] # 选择列
array([[ 0, 2],
[ 4, 6],
[ 8, 10]])
>>>
>>> a[b1, b2] # 神奇的事!
array([ 4, 10])
注意,一维布尔数组的长度必须与要切片的维度(或轴)的长度一致。在前面的示例中,b1 的长度为 3(a 中的行数),b2(长度为 4 ) 适用于索引 a 的第二个轴(列)。
ix_函数可用于组合不同的向量以获得每个 n-uplet 的结果。例如,如果想计算从向量 a、b 和 c 中提取的所有三元组的所有 a+b*c:
>>> 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 的优势在于它利用广播规则来避免创建输出大小乘以向量数量的参数数组。
这个后面在结构化数组里面提
要更改数组的尺寸,可以省略其中一种尺寸,然后自动推导出:
>>> a = np.arange(30)
>>> b = a.reshape((2, -1, 3)) # -1 表示需要补全什么
>>> 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 中,这很简单:如果 x 和 y 是两个长度相同的向量,则只需执行 m=[x;y]。 在 NumPy 中,这通过函数 column_stack、dstack、hstack 和 vstack 工作,具体取决于要完成堆叠的维度。 例如:
>>> 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 直方图函数返回一对向量:数组的直方图和 bin 边缘的向量。 注意:matplotlib 还具有构建直方图的功能(称为 hist,如在 Matlab 中),它与 NumPy 中的不同。 主要区别在于 pylab.hist 会自动绘制直方图,而 numpy.histogram 只生成数据。
>>> import numpy as np
>>> rg = np.random.default_rng(1)
>>> import matplotlib.pyplot as plt
>>># 构建一个包含 10000 个正态偏差的向量,方差为 0.5^2,均值为 2
>>> mu, sigma = 2, 0.5
>>> v = rg.normal(mu, sigma, 10000)
>>># 绘制一个有 50 个 bin 的归一化直方图
>>> plt.hist(v, bins=50, density=True) # matplotlib版本
>>> # 用 numpy 计算直方图,然后绘制它
(array([0.00128706, 0.00257412, 0.00257412, 0.00257412, 0.00772237,
0.00900943, 0.01415767, 0.03861183, 0.04247301, 0.03989889,
0.05663068, 0.08623308, 0.13900258, 0.16088261, 0.24454157,
0.30117225, 0.34750645, 0.41700774, 0.51997261, 0.60234451,
0.68214229, 0.69501289, 0.78768128, 0.73619884, 0.79540365,
0.78896834, 0.72976354, 0.7014482 , 0.69501289, 0.60749275,
0.57660329, 0.4478972 , 0.39126652, 0.28186634, 0.24196745,
0.20592975, 0.13385434, 0.10425193, 0.07851072, 0.04762125,
0.0296024 , 0.03217652, 0.01029649, 0.00900943, 0.0064353 ,
0.00772237, 0.00514824, 0.00128706, 0.00257412, 0.00128706]), array([0.08106893, 0.15876533, 0.23646173, 0.31415813, 0.39185453,
0.46955093, 0.54724733, 0.62494373, 0.70264013, 0.78033653,
0.85803293, 0.93572933, 1.01342573, 1.09112213, 1.16881853,
1.24651493, 1.32421133, 1.40190773, 1.47960413, 1.55730053,
1.63499693, 1.71269333, 1.79038973, 1.86808613, 1.94578253,
2.02347893, 2.10117533, 2.17887173, 2.25656814, 2.33426454,
2.41196094, 2.48965734, 2.56735374, 2.64505014, 2.72274654,
2.80044294, 2.87813934, 2.95583574, 3.03353214, 3.11122854,
3.18892494, 3.26662134, 3.34431774, 3.42201414, 3.49971054,
3.57740694, 3.65510334, 3.73279974, 3.81049614, 3.88819254,
3.96588894]), <BarContainer object of 50 artists>)
>>> import pylab
>>> pylab.show()
>>> (n, bins) = np.histogram(v, bins=50, density=True) # np版本
>>> plt.plot(.5 * (bins[1:] + bins[:-1]), n)
[<matplotlib.lines.Line2D object at 0x000001E94F947640>]
>>> pylab.show()
matplotlib展示的直方图:
numpy展示的折线图:
整合在一起看:
在matplotlib >= 3.4时也可以用 plt.stairs(n, bins):
>>> plt.stairs(n, bins)
<matplotlib.patches.StepPatch object at 0x000001E950BDA730>
>>> pylab.show()