ndarray 对象介绍#
NumPy 的主要对象是同构的多维数组。它是一个同类型元素(通常是数字)构成的表,由非负整数元组索引。NumPy 中的维度被称为轴(axes)。
例如,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
# arange() 类似于 Python 内建的 range(), 但返回 ndarray 对象;
# reshape() 将 ndarray 对象变为特定维度的对象,并返回该 ndarray 对象
a = np.arange(15).reshape(3, 5)
print(a)
# [[ 0 1 2 3 4]
# [ 5 6 7 8 9]
# [10 11 12 13 14]]
print(a.shape) # (3, 5)
print(a.ndim) # 2
print(a.dtype.name) # int64
print(a.itemsize) # 4
print(a.size) # 15
print(type(a)) # <class 'numpy.ndarray'>
b = np.array([6, 7, 8])
print(b) # [6 7 8]
print(type(b)) # <class 'numpy.ndarray'>
创建数组#
有多种创建数组的方法,请查看创建数组函数文档了解详细信息。
① 使用 array 函数从常规的 Python 列表或元组创建数组
numpy.array(object, dtype=None, *, copy=True,
order='K', subok=False, ndmin=0, like=None)
import numpy as np
a = np.array([2, 3, 4])
print(a) # [2 3 4]
print(a.dtype) # int32
b = np.array([1.2, 3.5, 5.1])
print(b.dtype) # float64
所得数组元素的类型是从序列元素的类型自动推导得出的。
使用 array 函数的一个常见错误是在调用时为其提供了多个参数,而不是提供单个序列参数:
import numpy as np
a = np.array(1, 2, 3, 4) # 错误
# TypeError: array() takes from 1 to 2 positional arguments but 4 were given
a = np.array([1, 2, 3, 4]) # 正确
a = np.array((1, 2, 3, 4)) # 正确
array 函数可以将序列的序列转换为二维数组,将序列的序列的序列转换为三维数值,依次类推;还可以在 array 的参数中显式指定元素类型:
import numpy as np
b = np.array([(1.5, 2, 3), (4, 5, 6)])
print(b)
# [[1.5 2. 3. ]
# [4. 5. 6. ]]
c = np.array([[1, 2], [3, 4]], dtype=complex)
print(c)
# [[1.+0.j 2.+0.j]
# [3.+0.j 4.+0.j]]
② 用特定函数创建具有特定尺寸和初始元素值的数组
-
zeros:通过给定形状和类型创建数组,并用 0 填充元素值。numpy.zeros(shape, dtype=float, order='C', *, like=None) -
ones:通过给定形状和类型创建数组,并用 1 填充元素值。numpy.ones(shape, dtype=None, order='C', *, like=None) -
empty:通过给定形状和类型创建数组,同时不初始化元素值。numpy.empty(shape, dtype=float, order='C', *, like=None) -
full:创建一个具有给定形状和类型的数组,并用给定的fill_value值填充各元素。numpy.full(shape, fill_value, dtype=None, order='C', *, like=None)
import numpy as np
print(np.zeros((3, 4))) # 不指定 dtype 值时,默认类型是 float64
# [[0. 0. 0. 0.]
# [0. 0. 0. 0.]
# [0. 0. 0. 0.]]
print(np.ones((2, 3, 4), dtype=np.int16)) # 显式指定 dtype 值
# [[[1 1 1 1]
# [1 1 1 1]
# [1 1 1 1]]
# [[1 1 1 1]
# [1 1 1 1]
# [1 1 1 1]]]
print(np.empty((2, 3)))
# [[1.39069238e-309 1.39069238e-309 1.39069238e-309]
# [1.39069238e-309 1.39069238e-309 1.39069238e-309]]
arange 函数与 Python 内建的 range 函数类似,但返回 ndarray 对象。
numpy.arange([start, ]stop, [step, ]dtype=None, *, like=None)
import numpy as np
print(np.arange(10, 30, 5)) # [10 15 20 25]
print(np.arange(0, 2, 0.3)) # [0. 0.3 0.6 0.9 1.2 1.5 1.8]
当 arange 与浮点参数一起使用时,由于有限的浮点精度,通常不可能预测所获得的元素的数量。出于这个原因,通常最好使用 linspace 函数来显式指定我们想要的元素数量,而不是像 arange 函数那样指定步长:
numpy.linspace(start, stop, num=50, endpoint=True,
retstep=False, dtype=None, axis=0)
import numpy as np
print(np.linspace(0, 2, 9)) # [0. 0.25 0.5 0.75 1. 1.25 1.5 1.75 2. ]
print(np.linspace(0, 2 * np.pi, 30))
# [0. 0.21666156 0.43332312 0.64998469 0.86664625 1.08330781
# 1.29996937 1.51663094 1.7332925 1.94995406 2.16661562 2.38327719
# 2.59993875 2.81660031 3.03326187 3.24992343 3.466585 3.68324656
# 3.89990812 4.11656968 4.33323125 4.54989281 4.76655437 4.98321593
# 5.1998775 5.41653906 5.63320062 5.84986218 6.06652374 6.28318531]
还有更多的创建数组的函数。该页面把创建数组的函数分为多个分类:从形状或值创建、从已有数据创建、创建记录数组 numpy.rec、创建字符数组 numpy.char、数值范围、构建矩阵、矩阵类。
注意该页面许多函数具有 _like 后缀,如 zeros_like、ones_like、empty_like、full_like,这些函数的功能和不带 _like 的函数类似,但可不必为其提供 shape 和 dtype 参数,而可通过为其提供一个原型数组 a,从而创建与该原型具有相同形状和类型的数组。
实际创建数组时,应根据具体场景,从该页面选择使用合适的函数。
基本操作#
根据矢量化和广播规则,对数组的算术运算符会逐个应用到每个元素。下面是创建一个新数组并填充结果的示例:
import numpy as np
a = np.array([20, 30, 40, 50])
b = np.arange(4)
c = a - b
print(c) # [20 29 38 47]
print(b**2) # [0 1 4 9]
print(10 * np.sin(a)) # [ 9.12945251 -9.88031624 7.4511316 -2.62374854]
print(a < 35) # [ True True False False]
与许多矩阵运算语言不同,乘法运算符 * 在 NumPy 数组中是逐个元素进行。矩阵乘积可以使用 @ 运算符(对 Python >= 3.5)或 dot 函数或方法执行:
import numpy as np
A = np.array( [[1,1], [0,1]] )
B = np.array( [[2,0], [3,4]] )
print(A * B)
# [[2 0]
# [0 4]]
print(A @ B)
print(A.dot(B))
print(np.dot(A, B))
# 结果均为:
# [[5 4]
# [3 4]]
某些操作(例如 += 和 *=)会更直接更改被操作的矩阵数组而不会创建新矩阵数组。
import numpy as np
a = np.ones((2,3), dtype=int)
b = np.random.random((2,3))
a *= 3
print(a)
# [[3 3 3]
# [3 3 3]]
b += a
print(b)
# [[3.42596777 3.14670933 3.10927589]
# [3.64844792 3.381072 3.46206654]]
a += b # b 的元素是浮点类型,无法被自动转换为 a 的整数类型
# numpy.core._exceptions._UFuncOutputCastingError: Cannot cast ufunc 'add' output from dtype('float64') to dtype('int32') with casting rule 'same_kind'
当对不同类型的数组进行操作时,会遵守向上转换的规则,即结果数组的类型将是参与运算的类型中更一般和更通用的类型。
import numpy as np
a = np.ones(3, dtype=np.int32)
b = np.linspace(0, np.pi, 3)
print(b.dtype.name) # float64
c = a + b
print(c, c.dtype.name) # [1. 2.57079633 4.14159265] float64
d = np.exp(c*1j)
print(d, d.dtype.name) # [ 0.54030231+0.84147098j -0.84147098+0.54030231j -0.54030231-0.84147098j] complex128
许多一元运算,如计算数组中所有元素的和,都是作为 ndarray 类的方法实现的。
import numpy as np
rng = np.random.default_rng() # 创建一个伪随机数生成器
a = rng.random((2, 3))
print(a)
# [[0.54981685 0.85835019 0.50779762]
# [0.54727067 0.92390732 0.83417839]]
print(a.sum(), a.max(), a.min())
# 4.2213210430646395 0.9239073221612277 0.5077976241685102
默认情况下,适用于数字列表的运算也适用于任何形状的数组。不过,通过指定 axis 参数,您可以沿数组的指定轴进行运算:
import numpy as np
b = np.arange(12).reshape(3, 4)
print(b)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
print(b.sum(axis=0)) # 每列之和
# [12 15 18 21]
print(b.min(axis=1)) # 每行的最小值
# [0 4 8]
print(b.cumsum(axis=1)) # 沿每行的累加和
# [[ 0 1 3 6]
# [ 4 9 15 22]
# [ 8 17 27 38]]
通用函数#
NumPy 提供了一些我们原来已熟悉的数学函数,如 sin,cos 和 exp。在 NumPy 中,这些被称为“通用函数”(ufunc),这些函数在数组上按元素逐个进行运算,产生一个数组作为输出。
import numpy as np
a = np.arange(3)
b = np.array([2., -1., 4.])
print(a) # [0 1 2]
print(np.exp(a)) # [1. 2.71828183 7.3890561 ]
print(np.sqrt(a)) # [0. 1. 1.41421356]
print(np.add(a, b)) # [2. 0. 6.]
其他常见的通用函数包括:
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 序列一样对一维数组进行索引、切分和迭代。
import numpy as np
a = np.arange(10)**3
print(a) # [ 0 1 8 27 64 125 216 343 512 729]
print(a[2:5]) # [ 8 27 64]
# 等价于 a[0:6:2] = 1000,从开始到 6,将每第二个元素值设置为 1000
a[:6:2] = 1000
print(a) # [1000 1 1000 27 1000 125 216 343 512 729]
print(a[::-1]) # a 的逆序数组
# [ 729 512 343 216 125 1000 27 1000 1 1000]
for e in a:
print(e**(1 / 3.))
# 9.999999999999998
# 1.0
# 9.999999999999998
# 3.0
# 9.999999999999998
# 4.999999999999999
# 5.999999999999999
# 6.999999999999999
# 7.999999999999999
# 8.999999999999998
多维数组的每个轴都可以有一个索引,这些索引以逗号分隔的元组给出(注意这与 Python 的多维列表或元组的方法不同)。
import numpy as np
def f(x, y):
return 10 * x + y
# fromfunction 通过针对每个元素执行一次函数而构建数组
b = np.fromfunction(f, (5, 4), dtype=int)
print(b)
# [[ 0 1 2 3]
# [10 11 12 13]
# [20 21 22 23]
# [30 31 32 33]
# [40 41 42 43]]
print(b[2, 3]) # 23
# 以下两行均打印 a 的所有列的第二行
print(b[0:5, 1])
print(b[:, 1])
# [ 1 11 21 31 41]
print(b[1:3, :]) # a 的第 2、3 行的所有列
# [[10 11 12 13]
# [20 21 22 23]]
当提供的索引数少于轴的数量时,缺失的索引被认为是完整的切片:
print(b[-1]) # 最后一行,等价于 b[-1, :]
# [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, :]。
一个更复杂的示例:
import numpy as np
c = np.array([[[ 0, 1, 2], # 一个三维数组
[ 10, 12, 13]],
[[100, 101, 102],
[110, 112, 113]]])
print(c.shape) # (2, 2, 3)
print(c[1, ...]) # c[1, ...] 等价于 c[1, :, :] 或 c[1]
# [[100 101 102]
# [110 112 113]]
print(c[..., 2]) # c[..., 2] 等价于 c[:, :, 2]
# [[ 2 13]
# [102 113]]
对多维数组进行迭代操作是针对第一个轴进行的:
import numpy as np
b = np.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 属性,该属性是数组的所有元素的迭代器:
import numpy as np
b = np.array([[ 0, 1, 2, 3],
[10, 11, 12, 13],
[20, 21, 22, 23],
[30, 31, 32, 33],
[40, 41, 42, 43]])
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