文章目录
先决条件
你需要了解一些Python知识。如果需要复习,请参考Python教程。
在使用示例之前,除了NumPy,你还需要安装matplotlib
。
学习者概况
这是NumPy中数组的快速概述。它演示了如何表示和操作n维数组。特别是,如果你不知道如何对n维数组应用常见函数(而不使用for循环),或者想了解n维数组的轴和形状属性,那么本文可能会对你有所帮助。
学习目标
阅读完本文后,你应该能够:
- 理解NumPy中一维、二维和n维数组的区别;
- 理解如何对n维数组应用一些线性代数运算,而不使用for循环;
- 理解n维数组的轴和形状属性。
基础知识
NumPy的主要对象是同质多维数组。它是一个由元素(通常是数字)组成的表格,所有元素的类型相同,由非负整数的元组索引。在NumPy中,维度被称为轴。
例如,三维空间中一个点的坐标数组[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) # 错误
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]) # 正确
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], # 可能会有所不同
[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) # 它接受浮点数参数
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) # 从0到2生成9个数字
array([0. , 0.25, 0.5 , 0.75, 1. , 1.25, 1.5 , 1.75, 2. ])
x = np.linspace(0, 2 * pi, 100) # 在许多点上评估函数很有用
f = np.sin(x)
另请参见
array
, zeros
, zeros_like
, ones
, ones_like
, empty
, empty_like
, arange
,linspace
, numpy.random.Generator.rand, numpy.random.Generator.randn,fromfunction
, fromfile
打印数组
当你打印一个数组时,NumPy以类似于嵌套列表的方式显示它,但布局如下:
- 最后一个轴从左到右打印,
- 倒数第二个轴从上到下打印,
- 其余轴也从上到下打印,每个切片与下一个切片之间用一个空行分隔。
一维数组然后被打印为行,二维数组被打印为矩阵,三维数组被打印为矩阵的列表。
a = np.arange(6) # 1维数组
print(a)
[0 1 2 3 4 5]
>>>
b = np.arange(12).reshape(4, 3) # 2维数组
print(b)
[[ 0 1 2]
[ 3 4 5]
[ 6 7 8]
[ 9 10 11]]
>>>
c = np.arange(24).reshape(2, 3, 4) # 3维数组
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]]]
如果一个数组太大而无法打印,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模块
基本操作
数组上的算术运算符是逐元素应用的。创建一个新数组,并用结果填充。
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):
...
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) # 每列的和
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)
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.])
另请参见
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])
a[2]
8
a[2:5]
array([ 8, 27, 64])
# 等同于 a[0:6:2] = 1000;
# 从开始到位置6(不包括6),每隔2个元素设置为1000
a[:6:2] = 1000
a
array([1000, 1, 1000, 27, 1000, 125, 216, 343, 512, 729])
a[::-1] # 反转 a
array([ 729, 512, 343, 216, 125, 1000, 27, 1000, 1, 1000])
for i in a:
print(i**(1 / 3.))
9.999999999999998 # 可能会有所不同
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] # b 中第二列的每一行
array([ 1, 11, 21, 31, 41])
b[:, 1] # 等同于上一个例子
array([ 1, 11, 21, 31, 41])
b[1:3, :] # b 中第二行和第三行的每一列
array([[10, 11, 12, 13],
[20, 21, 22, 23]])
当提供的索引少于轴的数量时,缺少的索引被视为完整切片:
b[-1] # 最后一行。等同于 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], # 一个3D数组(两个堆叠的2D数组)
[ 10, 12, 13]],
[[100, 101, 102],
[110, 112, 113]]])
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]])
对于多维数组的迭代是相对于第一个轴进行的:
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
另请参见
ndarrays 上的索引, 索引例程(参考),newaxis
, ndenumerate
,indices
形状操作
改变数组的形状
数组的形状由每个轴上的元素数量给出:
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)
np.array, np.zeros, np.zeros_like, np.ones, np.ones_like, np.empty, np.empty_like, np.full, np.full_like, np.arange, np.linspace, np.random.rand, np.random.randn, np.fromfunction, np.fromfile
- Printing Arrays
np.set_printoptions
- Basic Operations
np.add, np.subtract, np.multiply, np.divide, np.floor_divide, np.power, np.mod, np.sqrt, np.exp, np.log, np.log10, np.log2, np.sin, np.cos, np.tan, np.arcsin, np.arccos, np.arctan, np.sinh, np.cosh, np.tanh, np.arcsinh, np.arccosh, np.arctanh
- Shape Manipulation
np.reshape, np.ndarray.resize, np.ravel, np.transpose, np.swapaxes, np.flatten, np.expand_dims, np.squeeze, np.concatenate, np.vstack, np.hstack, np.column_stack, np.row_stack, np.split, np.hsplit, np.vsplit, np.array_split
- Copies and Views
np.copy, np.view
- Array Manipulation
np.resize, np.append, np.insert, np.delete, np.unique, np.flip, np.fliplr, np.flipud, np.roll, np.rot90, np.repeat, np.tile, np.trim_zeros, np.insert, np.delete, np.append, np.resize, np.concatenate, np.vstack, np.hstack, np.column_stack, np.row_stack, np.split, np.hsplit, np.vsplit, np.array_split
- Basic Statistics
np.mean, np.average, np.median, np.std, np.var, np.min, np.max, np.argmin, np.argmax, np.percentile, np.histogram
- Sorting and Searching
np.sort, np.argsort, np.searchsorted
- Linear Algebra
np.dot, np.vdot, np.inner, np.outer, np.matmul, np.linalg.det, np.linalg.eig, np.linalg.inv, np.linalg.pinv, np.linalg.qr, np.linalg.svd, np.linalg.solve, np.linalg.lstsq
- Random Sampling
np.random.rand, np.random.randn, np.random.randint, np.random.shuffle, np.random.choice, np.random.permutation
- File Input and Output
np.save, np.savez, np.load, np.loadtxt, np.savetxt
- Other Functions
np.where, np.select, np.extract, np.piecewise, np.vectorize
See also
转换
操作
array_split
,column_stack
,concatenate
,diagonal
,dsplit
,dstack
,hsplit
,hstack
,ndarray.item
,newaxis
,ravel
,repeat
,reshape
,resize
,squeeze
,swapaxes
,take
,transpose
,vsplit
,vstack
问题
排序
操作
choose
,compress
,cumprod
,cumsum
,inner
,ndarray.fill
,imag
,prod
,put
,putmask
,real
,sum
基本统计
基本线性代数
cross
,dot
,outer
,linalg.svd
,vdot
更高级的
广播规则
广播允许通用函数以有意义的方式处理形状不完全相同的输入。
广播的第一个规则是,如果所有输入数组的维数不相同,则会在较小数组的形状前面重复添加“1”,直到所有数组的维数相同。
广播的第二个规则确保在特定维度上大小为1的数组会像在该维度上具有最大形状的数组一样起作用。假设“广播”数组在该维度上的数组元素的值相同。
应用广播规则后,所有数组的大小必须匹配。更多细节可以在广播中找到。
高级索引和索引技巧
NumPy提供了比常规Python序列更多的索引功能。除了之前看到的整数和切片索引之外,数组还可以通过整数数组和布尔数组进行索引。
使用索引数组进行索引
a = np.arange(12)**2 # 前12个平方数
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]])
palette[image] # (2, 4, 3) 彩色图像
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], # `a`的第一个维度的索引
[1, 2]])
j = np.array([[2, 1], # 第二个维度的索引
[3, 3]])
>>>
a[i, j] # `i`和`j`必须具有相同的形状
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)]
完全相同,因此我们可以将i
和j
放在一个元组中,然后使用该元组进行索引。
l = (i, j)
# 等同于`a[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 "<stdin>", 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) # 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])
# 对应最大值的时间
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 出现了两次,但第一个元素只增加了一次。这是因为 Python 要求 a += 1
等同于 a = a + 1
。
使用布尔数组进行索引
当我们使用整数索引数组时,我们提供了要选择的索引列表。而使用布尔索引的方法不同;我们明确选择数组中我们想要的项和不想要的项。
布尔索引最自然的方式是使用与原始数组形状相同的布尔数组:
a = np.arange(12).reshape(3, 4)
b = a > 4
b # `b` 是一个与 `a` 形状相同的布尔数组
array([[False, False, False, False],
[False, True, True, True],
[ True, True, True, True]])
a[b] # 选择的元素组成的一维数组
array([ 5, 6, 7, 8, 9, 10, 11])
这个特性在赋值时非常有用:
a[b] = 0 # `a` 中所有大于 4 的元素变为 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
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.clf()
plt.imshow(mandelbrot(400, 400))
使用布尔数组进行索引的第二种方式更类似于整数索引;对于数组的每个维度,我们给出一个选择切片的 1D 布尔数组:
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])
注意,1D 布尔数组的长度必须与你要切片的维度(或轴)的长度相一致。在前面的示例中,b1
的长度为 3(a
的行数),而 b2
(长度为 4)适合索引 a
的第二个轴(列)。
ix_() 函数
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]]])
向量堆叠
如何从一组大小相等的行向量构建一个 2D 数组?在 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 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
# 用方差为 0.5^2 和均值为 2 生成一个包含 10000 个正态分布随机数的向量
mu, sigma = 2, 0.5
v = rg.normal(mu, sigma, 10000)
# 绘制一个归一化的具有 50 个 bin 的直方图
plt.hist(v, bins=50, density=True) # matplotlib 版本(绘图)
(array...)
# 使用 numpy 计算直方图,然后绘制它
(n, bins) = np.histogram(v, bins=50, density=True) # NumPy 版本(不绘图)
plt.plot(.5 * (bins[1:] + bins[:-1]), n)
在 Matplotlib >=3.4 中,你还可以使用 plt.stairs(n, bins)
。