NumPy的主要对象是同类多维数组,这是一个相同类型的元素(通常是数字)组成的表。在NumPy中,维度称为axis,axis的数量叫做rank。
例如,三维空间中的一个坐标为[1, 2, 1]的点,即为rank=1的数组,因为这个数组只有一个axis。有些人可能会有疑惑,不是说三维空间么,为什么说它只有一个axis呢?axis可以理解成轴,抛开这个点,单看这个数组[1, 2, 1],它确实只需要一个axis即可表示。这个axis长度为3。
又如,[[ 1., 0., 0.],[ 0., 1., 2.]],这个数组的rank=2(2维的)。第一维(axis)长度为2,第二维长度为3。
也可以这么理解,数组是由2个行向量组成,所以第一维长度为2,每个行向量由3个向量(点)组成,所以第二维长度为3。
Numpy的数组类称为ndarray。numpy.array和Python标准库中的array.array是两回事,标准库中的array.array只能处理一维数组且只提供了少数的功能。ndarray对象中更加重要的特性如下:
ndarray.ndim
数组的axis(维)的数量。在Python中,维的数量被称作rank。ndarray.shape
数组的各个维(注意和维和维数要区分开)。它是一个数组各个维的长度构成的整数元组。对n行m列矩阵而言,shape将是(n,m)。因此,shape元组的长度也就是rank,也是维数ndim。ndarray.size
数组所有元素总数量。等于shpe元组各元素的乘积。ndarray.dtype
一个描述数组中元素类型的对象。用户可以使用Python标准类型创建或指定dtype。此外,NumPy还提供了其自有的类型,比如numpy.int32, numpy.int16, numpy.float64。ndarray.itemsize
数组各元素的占多少字节。比如,一个元素类型是float64的数组,其itemsize为8(=64/8,64位除以8)。同理,元素类型是complex32的数组的itemsize为4(=32/8)。ndarray.itemsize等于ndarray.dtype.itemsize。ndarray.data
装载数组真实元素的缓冲区。通常,我们用不到这个属性,因为我们一般使用索引访问数组元素。
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. 创建数组
有多种方法来创建数组。
你可以利用常规的Python列表或元组同伙array函数来创建一个数组。创建出的数组类型和序列元素类型是一致的。
>>> 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')
>>> c = np.array(('a','b','c'))
>>> c
array(['a', 'b', 'c'],
dtype='|S1')
>>> c.shape
(3,)
>>> c.size
3
>>> c.ndim
1
>>> c.dtype
dtype('S1')
注意,array函数只接收一个参数,且这个参数是一个python序列。常见的错误是给array函数传递多个数字作为参数,如a = np.array(1,2,3,4)
。
数组将序列组成的序列转化为二维数组,将序列组成的序列组成的序列转化为三维数组,如此等等。
>>> 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]])
一般情况是,数组元素最初是未知的,但其size是已知的。因此,NumPy提供了一些函数,利用初始的占位符来创建数组。这些函数减少了费时费力的手动填充数组的成本。
函数zeros可以创建一个全部填充0的数组;函数ones创建的数组全部填充为1;函数empty创建的函数的初始内容是随机的,依赖于内存状态。默认情况下,这些函数创建的数组的dtype是float64。
>>> a = np.zeros((3,4))
>>> a
array([[ 0., 0., 0., 0.],
[ 0., 0., 0., 0.],
[ 0., 0., 0., 0.]])
>>> a.dtype
dtype('float64')
>>> 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([[ 0., 0., 0.],
[ 0., 0., 0.]])
Numpy提供了一个类似于range的函数,可以返回一列数字,用以代替Python序列来创建数组。
>>> np.arange( 10, 30, 5 )
array([10, 15, 20, 25])
>>> np.arange( 0, 2, 0.3 ) # it accepts float arguments
array([ 0. , 0.3, 0.6, 0.9, 1.2, 1.5, 1.8])
当给arange函数传递的参数是浮点数时,由于浮点数精度的有限性,通常难以预测最终将获得什么样的数字。基于此,一般更好的方法是使用函数linspace,其接收一个我们想要的元素数量作为参数,而不是step:
>>> from numpy import pi
>>> np.linspace( 0, 2, 9 ) # 9 numbers from 0 to 2
array([ 0. , 0.25, 0.5 , 0.75, 1. , 1.25, 1.5 , 1.75, 2. ])
>>> x = np.linspace( 0, 2*np.pi, 100 ) # useful to evaluate function at lots of points
>>> f = np.sin(x)
3. 打印数组
打印数组时,NumPy通过类似嵌套列表的方式将数组展现出来,但会遵循以下层次布局:
最后的那个axis从左往右依次打印
比如数组有4维,那么从左到右打印的是第4维(第4个axis)倒数第二个axis从上到下打印
其余的axis也从上到下打印,每个切片由一个空行分开
基于此,一维数组将打印成一行;二维数组将打印成一个矩阵;三维数组将打印成一个矩阵列表。
>>> a = np.arange(6) # 1d array
>>> print(a)
[0 1 2 3 4 5]
>>>
>>> b = np.arange(12).reshape(4,3) # 2d array
>>> print(b)
[[ 0 1 2]
[ 3 4 5]
[ 6 7 8]
[ 9 10 11]]
>>>
>>> c = np.arange(24).reshape(2,3,4) # 3d array
>>> 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]]
如果不想省略中间部分,可以通过set_printoptions来强制NumPy打印所有数据。
>>> np.set_printoptions(threshold='nan')
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], dtype=bool)
不同于许多矩阵语言,在NumPy中,乘号”*”是两个数组中位置相同的元素相乘。矩阵乘法通过dot函数或方法来实现:
>>> A = np.array( [[1,1],
... [0,1]] )
>>> B = np.array( [[2,0],
... [3,4]] )
>>> A*B # elementwise product
array([[2, 0],
[0, 4]])
>>> A.dot(B) # matrix product
array([[5, 4],
[3, 4]])
>>> np.dot(A, B) # another matrix product
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 is not automatically converted to integer type
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,np.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参数,可以将运算应用于指定的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]])
>>> b.cumsum(axis = 0)
array([[ 0, 1, 2, 3],
[ 4, 6, 8, 10],
[12, 15, 18, 21]])
5. 通用函数
NumPy提供了常见的数学函数,如sin,cos,exp等。在NumPy中,这些函数称为“通用函数”(ufunc)。这些函数作用于数组中每个元素,并产生一个新的结果数组。
>>> 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.])
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
多维数组的每个axis都有一个索引。这些索引由一组逗号分隔的数字给出。
>>> 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] # each row in the second column of b
array([ 1, 11, 21, 31, 41])
>>> b[ : ,1] # equivalent to the previous example
array([ 1, 11, 21, 31, 41])
>>> b[1:3, : ] # each column in the second and third row of b
array([[10, 11, 12, 13],
[20, 21, 22, 23]])
如果给出的索引少于axis的数量,那么缺失的索引被认为是整体切片:
>>> b[-1] # the last row. Equivalent to b[-1,:]
array([40, 41, 42, 43])
b[i]的括号中的表达式被解析成一个i后面跟着足够多的冒号“:”,其足以代表其余的axis。NumPy中还可以使用点“.”写成b[i,…]的形式。
(…)表示足够的冒号来产生一个完整的索引元组。比如,如果x是一个5维数组(就是说它有5个axis),那么
- 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]])
对多维数组迭代是就第一个axis而言的:
>>> 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