1. 先决条件
在阅读本教程之前,你应该知道一点 Python。如果你想更新你的记忆, 请查看 Python 教程。
如果你希望在本教程中使用示例,还必须在计算机上安装一些软件。请参阅 http://scipy.org/install.html 的说明。
2. 基本知识
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 一个用于描述数组中元素类型的对象。你可以创建或指定 dtype 使用标准的 Python 类型。此外,NumPy 提供了自己的类型。例如,numpy.int32,numpy.int16 和 nunpy.float64。
ndarray.itemsize 数组中每个元素以字节为单元的大小。例如,float64
类型的元素数组的 itemsize
是 8 (=64/8),而 complex32
类型的元素数组的 itemsize
是 4 (=32/8)。这等价于 ndarray.dtype.itemsize
。
ndarray.date 包含数组实际元素的缓冲区。通常,我们不需要使用这个属性,因为我们使用索引功能访问数组中的元素。
2.1 一个例子
>>> 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)
<type 'numpy.ndarray'>
>>> b = np.array([6, 7, 8])
>>> b
array([6, 7, 8])
>>> type(b)
<type 'numpy.ndarray'>
2.2 创建数组
有多种方式可以创建数组。
例如,你可以使用 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) # 错误
>>> 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 提供了一些函数来创建具有初始占位符内容的数组。这就最大限度地减少了增长阵列的必要性,这是一项昂贵的操作。
zero
函数用来创建一个全 0 数组,ones
函数用来创建一个全 1 数组,以及 empty
函数用来创建一个取决于内存状态的初始内容随机的数组。默认情况下,创建的数组的 dtype 为 float64
。
>>> 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 ) # dtype 可以指定
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 提供了类似于 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
与浮点参数一起使用时,由于浮点精度有限,一般不可能预测获得的元素数。因此,通常最好使用 linsapce
函数,并将我们需要的元素数作为参数,替换步骤:
>>> 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
,zero
,zero_like
,ones
,ones_like
,empty
,empty_like
,arange
linspace
,numpy_random.rand
,numpy_random.randn
,fromfuntcion
,fromfile
2.3 打印数组
打印数组时,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
更改打印选项。
>>> np.set_printoptions(threshold=np.nan)
2.4 基本操作
数组元素依次对应进行算术运算。创建一个新数组并填充结果。
>>> 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 数组的乘法操作符 *
是对数组元素依次对应相乘,这与许多矩阵语言不同。矩阵的乘法可以使用 dot
函数或方法来执行:
>>> A = np.array( [[1,1],
... [0,1]] )
>>> B = np.array( [[2,0],
... [3,4]] )
>>> A*B # 数组元素乘法
[0, 4]])
>>> A.dot(B) # 矩阵乘法
array([[5, 4],
[3, 4]])
>>> np.dot(A, B) # 另一种矩阵乘法
array([[5, 4],
[3, 4]])
某些操作,例如 +=
和 *=
,是为了修改现有数组,而不是创建一个新的数组。
>>> a = np.ones((2,3), dtype=int)
>>> b = np.random.random((2,3))
>>> a *= 3
>>> a
array([[3, 3, 3],
[3, 3, 3]])
>>> b += a
>>> b
array([[ 3.417022 , 3.72032449, 3.00011437],
[ 3.30233257, 3.14675589, 3.09233859]])
>>> a += b # b 未自动转换为整数类型
Traceback (most recent call last):
...
TypeError: 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 = np.random.random((2,3))
>>> a
array([[ 0.18626021, 0.34556073, 0.39676747],
[ 0.53881673, 0.41919451, 0.6852195 ]])
>>> a.sum()
2.5718191614547998
>>> a.min()
0.1862602113776709
>>> a.max()
0.6852195003967595
默认情况下,这些应用于数组的运算,就如同对一个数字列表的操作,而不管数组的形状。但是,通过指定 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]])
2.5 通用函数
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
,inv
,lexsort
,max
,maximum
,mean
,median
,min
,minimum
,
nonzero
,outer
,prod
,re
,round
,sort
,std
,sum
,trace
,transpose
,var
,
vdot
,vectorize
,where
2.6 索引、切片和迭代
一维数组可以被索引、切片和迭代,这类似于列表和其他 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[:6:2] = -1000 # equivalent to a[0:6:2] = -1000; from start to position 6, exclusive, set every 2nd element to -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.))
...
nan
1.0
nan
3.0
nan
5.0
6.0
7.0
8.0
9.0
多维数组每个坐标轴可以有一个索引。这些索引以逗号分隔的元组给出:
>>> 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, ...]
。
dots (...
) 表示生成完整索引元组所需的冒号。例如,如果 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]
但是,如果要对数组中的每个元素执行运算,则可以对数组的所有元素使用一个 iterator 的 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
另见:
Indexing
,Indexing (reference)
,newaxis
,ndenumerate
,indices
3. 形状操作
3.1 更改数组的形状
数组的形状由每个坐标轴上的元素数决定:
>>> a = np.floor(10*np.random.random((3,4)))
>>> a
array([[ 2., 8., 0., 6.],
[ 4., 5., 1., 1.],
[ 8., 9., 3., 6.]])
>>> a.shape
(3, 4)
可以使用多种命令更改数组的形状。请注意,一下三种命令都是返回修改后的数组,但不会修改原始数组:
>>> a.ravel() # 返回拼合后的数组
array([ 2., 8., 0., 6., 4., 5., 1., 1., 8., 9., 3., 6.])
>>> a.reshape(6,2) # 返回修改形状后的数组
array([[ 2., 8.],
[ 0., 6.],
[ 4., 5.],
[ 1., 1.],
[ 8., 9.],
[ 3., 6.]])
>>> a.T # 返回转置后的数组
array([[ 2., 4., 8.],
[ 8., 5., 9.],
[ 0., 1., 3.],
[ 6., 1., 6.]])
>>> a.T.shape
(4, 3)
>>> a.shape
(3, 4)
通过 ravel()
拼合后的数组中的元素顺序通常是“C-style”,即最右边的索引“更改最快”,因此在 a[0, 0]
之后的元素是 a[0, 1]
。如果数组被重塑为其他形状,则数组将再次被视为“C-Style”。NumPy 通常会创建按此顺序存储的数组,因此 ravel()
通常不需要复制其参数,但是如果数组是通过使用另一个数组的切片或用异常的选项创建的,则可能需要复制。可以使用可选参数和 FORTRAN-style 数组指示 ravel()
和 reshape()
函数使得最左边的索引更改最快。
reshape
函数返回修改形状的参数,而 ndarray.resize
方法修改数组本身:
>>> a
array([[ 2., 8., 0., 6.],
[ 4., 5., 1., 1.],
[ 8., 9., 3., 6.]])
>>> a.resize((2,6))
>>> a
array([[ 2., 8., 0., 6., 4., 5.],
[ 1., 1., 8., 9., 3., 6.]])
如果在修改形状操作中将纬度指定未 -1,则会自动计算其他纬度:
>>> a.reshape(3,-1)
array([[ 2., 8., 0., 6.],
[ 4., 5., 1., 1.],
[ 8., 9., 3., 6.]])
另见:
ndarray.shape
,reshape
,resize
,ravel
3.2 将不同数组堆叠在一起
多个数组可以沿不同的坐标轴堆叠在一起:
>>> a = np.floor(10*np.random.random((2,2)))
>>> a
array([[ 8., 8.],
[ 0., 0.]])
>>> b = np.floor(10*np.random.random((2,2)))
>>> b
array([[ 1., 8.],
[ 0., 4.]])
>>> np.vstack((a,b))
array([[ 8., 8.],
[ 0., 0.],
[ 1., 8.],
[ 0., 4.]])
>>> np.hstack((a,b))
array([[ 8., 8., 1., 8.],
[ 0., 0., 0., 4.]])
column_stack
函数将一维数组作为列堆叠到二维数组中。它等价于仅用二维数组的 hstack
:
>>> from numpy import newaxis
>>> np.column_stack((a,b)) # 二维数组
array([[ 8., 8., 1., 8.],
[ 0., 0., 0., 4.]])
>>> 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] # 这允许有一个二维数组列向量
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
。一般情况下,对于具有两个以上维度的数组,hstack
堆栈沿第二个坐标轴,vstack
堆栈沿第一个坐标轴,并且 concatenate
允许提供一个可选参数,既给出要连接的坐标轴数目。
注意
在复杂的情况下,r_
和 c_
对于沿一个坐标轴堆叠数字来创建数组非常有用。它们允许使用范围文本 (":")。
>>> np.r_[1:4,0,4]
array([1, 2, 3, 0, 4])
当数组作为参数使用时,在默认行为中 r_
和 c_
类似于 vstack
和 hstack
,
但允许提供一个可选参数,既给出要连接的坐标轴数目。
另见:
hstack
,vstack
,column_stack
,concatenate
,c_
,r_
3.3 将一个数组拆分为几个较小的数组
使用 hsplit
可以沿其水平轴拆分数组,方法是指定拆分成相同形状数组的数目,或者指定从哪些列之后拆分:
>>> a = np.floor(10*np.random.random((2,12)))
>>> a
array([[ 9., 5., 6., 3., 6., 8., 0., 7., 9., 7., 2., 7.],
[ 1., 4., 9., 2., 2., 1., 0., 6., 2., 2., 4., 0.]])
>>> np.hsplit(a,3) # 拆分成 3 个
[array([[ 9., 5., 6., 3.],
[ 1., 4., 9., 2.]]), array([[ 6., 8., 0., 7.],
[ 2., 1., 0., 6.]]), array([[ 9., 7., 2., 7.],
[ 2., 2., 4., 0.]])]
>>> np.hsplit(a,(3,4)) # 在第三列和第四列后拆分 a
[array([[ 9., 5., 6.],
[ 1., 4., 9.]]), array([[ 3.],
[ 2.]]), array([[ 6., 8., 0., 7., 9., 7., 2., 7.],
[ 2., 1., 0., 6., 2., 2., 4., 0.]])]
vsplit
沿竖直轴进行拆分,array_split
允许一个指定轴进行拆分。
4. 副本和视图
在运算和操作数组时,它们的数据有时会复制到一个新数组中,有时却不会。这常常是初学者困惑的根源。这里有三个示例:
4.1 完全无复制
简单赋值不会复制数组对象或它们的数据。
>>> a = np.arange(12)
>>> b = a # 未创建新对象
>>> b is a # a 和 b 是同一 ndarray 对象的两个名称
True
>>> b.shape = 3,4 # 改变 a 的形状
>>> a.shape
(3, 4)
Python 将可变对象作为引用传递,因此函数调用不会复制。
>>> def f(x):
... print(id(x))
...
>>> id(a) # id 是对象的唯一标识符
148293216
>>> f(a)
148293216
4.2 视图或浅复制
不同的数组对象可以共享相同的数据。view
方法创建一个可以查看相同数据的新数组对象。
>>> c = a.view()
>>> c is a
False
>>> c.base is a # c 是 a 的一个数据视图
True
>>> c.flags.owndata
False
>>>
>>> c.shape = 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]])
数组切片返回它的一个视图:
>>> s = a[ : , 1:3] # 为了清晰而添加的空格; 也可以写成 "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]])
4.3 深复制
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]])
4.4 函数和方法概述
下面列出了一些有用的 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
5. 较少基础
5.1 广播规则
广播允许通用函数以有意义的方式处理不是完全相同形状的输入。
广播的第一条规则是,如果所有输入数组的维数都不相同,则重复将“1”预置到形状较小的数组,直到所有数组的维数相同为止。
广播的第二条规则确保了大小为 1 的数组在一个特定维度中的作用,就好像它们沿该维度是具有最大形状的数组。假定数组元素的值与“广播”数组的维数相同。
在应用广播规则后,所有数组的大小必须匹配。更多详情可以在广播中找到。
6. 花式索引和索引技
NumPy 提供了比常规 Python 序列更多的索引功能。除了按整数和切片进行索引外, 正如我们前面看到的那样, 数组可以通过整数数组和布尔值数组进行索引。
6.1 使用索引数组进行索引
>>> 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] # 例如, a[ : , j]
array([[[ 2, 1],
[ 3, 3]],
[[ 6, 5],
[ 7, 7]],
[[10, 9],
[11, 11]]])
当然,可以把 i
和 j
放入一个序列 (当作一个列表) 然后做索引列表。
>>> l = [i,j]
>>> a[l] # 等价于 a[i,j]
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 ?
IndexError: index (3) out of range (0<=index<=2) in dimension 0
>>>
>>> a[tuple(s)] # 与 a[i,j] 相同
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 在索引列表中发生了两次,但第 0 元素只递增一次。这是因为 Python 要求 "a+=1" 等同于 "a = a + 1"。
6.2 使用布尔数组进行索引
当我们使用索引数组 (整数) 对数组进行索引,我们提供一个索引列表来挑选。而使用布尔数组的方法是不同的;我们明确地选择我们想要的数组中的哪些项,以及哪些是我们不需要的。
对于布尔索引,最自然的方法是使用与原始数组具有相同形状的布尔数组:
>>> 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 set
的图像:
>>> import numpy as np
>>> import matplotlib.pyplot as plt
>>> def mandelbrot( h,w, maxit=20 ):
... """Returns an image of the Mandelbrot fractal of size (h,w)."""
... y,x = np.ogrid[ -1.4:1.4:h*1j, -2:0.8:w*1j ]
... c = x+y*1j
... z = c
... divtime = maxit + np.zeros(z.shape, dtype=int)
...
... for i in range(maxit):
... z = z**2 + c
... diverge = z*np.conj(z) > 2**2 # who is diverging
... div_now = diverge & (divtime==maxit) # who is diverging now
... divtime[div_now] = i # note when
... z[diverge] = 2 # avoid diverging too much
...
... return divtime
>>> plt.imshow(mandelbrot(400,400))
>>> plt.show()
用布尔值进行索引的第二种方法与整数索引相似;对于数组的每个维度,我们给出一个一维布尔数组来选择我们想要的切片:
>>> 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,:] # 选择行
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 的第二轴 (列) 进行索引。
6.3 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
还可以实现如下所示的减少:
>>> 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 相比,它利用广播规则,以避免创建参数数组的大小输出乘以向量的数量。
6.4 使用字符串进行索引
请参见结构化数组。
7. 线性代数
正在进行的工作。这里包括基本的线性代数。
7.1 简单数组操作
请参阅 numpy 文件夹中的 linalg.py。
>>> import numpy as np
>>> a = np.array([[1.0, 2.0], [3.0, 4.0]])
>>> print(a)
[[ 1. 2.]
[ 3. 4.]]
>>> a.transpose()
array([[ 1., 3.],
[ 2., 4.]])
>>> np.linalg.inv(a)
array([[-2. , 1. ],
[ 1.5, -0.5]])
>>> u = np.eye(2) # unit 2x2 matrix; "eye" represents "I"
>>> u
array([[ 1., 0.],
[ 0., 1.]])
>>> j = np.array([[0.0, -1.0], [1.0, 0.0]])
>>> np.dot (j, j) # matrix product
array([[-1., 0.],
[ 0., -1.]])
>>> np.trace(u) # trace
2.0
>>> y = np.array([[5.], [7.]])
>>> np.linalg.solve(a, y)
array([[-3.],
[ 4.]])
>>> np.linalg.eig(j)
(array([ 0.+1.j, 0.-1.j]), array([[ 0.70710678+0.j , 0.70710678-0.j ],
[ 0.00000000-0.70710678j, 0.00000000+0.70710678j]]))
Parameters:
square matrix
Returns
The eigenvalues, each repeated according to its multiplicity.
The normalized (unit "length") eigenvectors, such that the
column ``v[:,i]`` is the eigenvector corresponding to the
eigenvalue ``w[i]`` .
8. 技巧和提示
在这里, 我们给出简短有用的提示列表。
8.1 “自动”重塑形状
要更改数组的尺寸,可以省略其中一个大小,然后自动推断:
>>> a = np.arange(30)
>>> a.shape = 2,-1,3 # -1 means "无论需要什么"
>>> a.shape
(2, 5, 3)
>>> a
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]]])
8.2 向量堆叠
如何从相同大小的行向量列表中构造二维数组?在 MATLAB 中,这是很容易的:如果 x
和 y
是两个相同长度的向量,只需要做 m=[x; y]
。在 NumPy 中,这是通过 column_stack
,dstack
,hstack
和 vstack
函数来实现的,具体取决于要进行堆叠的维度。例如:
x = np.arange(0,10,2) # x=([0,2,4,6,8])
y = np.arange(5) # y=([0,1,2,3,4])
m = np.vstack([x,y]) # m=([[0,2,4,6,8],
# [0,1,2,3,4]])
xy = np.hstack([x,y]) # xy =([0,2,4,6,8,0,1,2,3,4])
在两个以上的维度中,这些函数背后的逻辑可能很奇怪。
8.3 直方图
应用于数组的 NumPy histogram
函数返回一对向量:数组的直方图和容器的向量。当心: matplotlib
也有一个函数建立直方图 (称为 hist
,如在 Matlab),不同于 NumPy。主要的区别是 pylab.hist
会自动绘制直方图,而 numpy.histogram
则只生成数据。
>>> import numpy as np
>>> 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 = np.random.normal(mu,sigma,10000)
>>> # Plot a normalized histogram with 50 bins
>>> plt.hist(v, bins=50, normed=1) # matplotlib version (plot)
>>> plt.show()
>>> # Compute the histogram with numpy and then plot it
>>> (n, bins) = np.histogram(v, bins=50, normed=True) # NumPy version (no plot)
>>> plt.plot(.5*(bins[1:]+bins[:-1]), n)
>>> plt.show()
网友评论