同时,NumPy是SciPy、Matplotlib等扩展路的基础组件
Numpy 的主要数据结构是一个同构多维数组
,大于矩阵的概念
维度( dimension)在 Numpy 中称为 axis
NumPy的数组的类被称为 ndarray
NumPy 的 numpy.array 和 Python 标准库中的 array.array 并不相同,NumPy 的 array 仅处理一维数组,因此功能更弱
ndarray包含以下属性(attribute):
>>> import numpy as np
>>> a = np.arange(12).reshape(3,4)
>>> a
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
>>> a.shape
(3, 4)
>>> a.ndim
2
>>> a.dtype.name
'int32'
>>> a.itemsize
4
>>> a.size
12
>>> type(a)
<class 'numpy.ndarray'>
>>> b = np.array([3, 4, 5])
>>> b
array([3, 4, 5])
>>> type(b)
<class 'numpy.ndarray'>
NumPy提供多种创建数组的方法
例如,NumPy可以直接从标准库的列表或元祖中创建数组
>>> import numpy as np
>>> a = np.array([3, 4, 5])
>>>
>>> a.dtype
dtype('int32')
>>> b = np.array([1.3, 3.3, 5.4])
>>> b.dtype
dtype('float64')
需要注意的是,传递给NumPy 数组的构造函数必须是列表或元祖,不能是多个元素。
>>> a = np.array(1,2,3,4) # Wrong
>>> a = np.array([1,2,3,4]) # Right
>>> a = np.array((1,2,3,4)) # Right
NumPy数组的构造函数会自动将传入格式转换为NumPy的数组格式
基本库的列表和元组在构造 NumPy 数组时可以混用
>>> b = np.array([(1.2, 2.3), (5, 6)])
>>> b
array([[1.2, 2.3],
[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]])
在实际应用中,构造数组时具体元素的取值往往不确定,但是数组大小一般会提前确定(比如上次tf神经网络时,先生成特定像素的全0图片【ndarray矩阵】)
因此,NumPy提供了一系列的函数方便构造空数组或特定元素值的数组。
默认情况下使用上述三个函数创建的数组的数值类型是 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)
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([[6.17692977e-312, 6.17692977e-312, 6.17692846e-312],
[6.17692915e-312, 6.17692977e-312, 6.17692915e-312]])
NumPy 提供可迅速生成数列的函数
>>> 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)
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)
>>> 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]]]
如果数组太大, NumPy 会自动将中间的数值替换为省略号,只打印头尾的数值
>>> print(np.arange(100000))
[ 0 1 2 ... 99997 99998 99999]
如果需要禁用此行为,可以使用:
np.set_printoptions(threshold=np.nan) 当程序报错时使用
import sys
import numpy as np
np.set_printoptions(threshold=sys.maxsize)
数组间的数学运算通常为元素级别的运算
运算结果会以新数组的形式返回
>>> a = np.array([10, 20, 30, 40, 50])
>>> b = np.arange(5)
>>> b
array([0, 1, 2, 3, 4])
>>> c = a-b
>>> c
array([10, 19, 28, 37, 46])
>>> b**2
array([ 0, 1, 4, 9, 16], dtype=int32)
>>> 10 * np.sin(a)
array([-5.44021111, 9.12945251, -9.88031624, 7.4511316 , -2.62374854])
>>> a < 35
array([ True, True, True, False, False])
乘号(*) 在NumPy的数组运算中是求元素积,而非求数量积(点积)
求数量积需要使用 @ 或 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]])
与标准库类似,某些运算符(例如 += 和 *=)将直接修改左侧变量,而非返回新数组
>>> 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.069918 , 3.66590394, 3.7354047 ],
[3.95409542, 3.42614061, 3.79219622]])
>>> a + b
array([[6.069918 , 6.66590394, 6.7354047 ],
[6.95409542, 6.42614061, 6.79219622]])
>>> a += b # b不能自动转换为int类型
Traceback (most recent call last):
File "" , line 1, in <module>
TypeError: Cannot cast ufunc add output from dtype('float64') to dtype('int32') with casting rule 'same_kind'
如果运算包含多个不同类型的数组,运算结果将向更精确的类型对齐( upcasting)
>>> a = np.ones(3, dtype=np.int32)
>>> b = np.linspace(0,pi,3)
>>> b.dtype.name
'float64'
>>> c = a+b # c的类型与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 都直接提供相应的函数
>>> c = np.arange(12).reshape(3,4)
>>> c
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
>>> c.sum(axis=0) # 0维上求和(最外维)
array([12, 15, 18, 21])
>>> c.min(axis=1) # 取1维上的最小值
array([0, 4, 8])
>>> c.cumsum(axis=1) # 按行梯形求和
array([[ 0, 1, 3, 6],
[ 4, 9, 15, 22],
[ 8, 17, 27, 38]], dtype=int32)
NumPy 提供了一些常用的数学函数,例如 sin、 cos、 exp 等
此类数学函数被称为通用函数( universal functions)
与数学运算类似,通用函数通常是针对数组元素进行操作的
>>> 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.])
一维数组可以直接被索引、裁剪、或迭代,行为类似标准库的列表
>>> 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[:6:2]
array([ 0, 8, 64], dtype=int32)
>>> a[:6:2] = -1000
>>> a
array([-1000, 1, -1000, 27, -1000, 125, 216, 343, 512,
729], dtype=int32)
>>> a[::-1] # 逆置
array([ 729, 512, 343, 216, 125, -1000, 27, -1000, 1,
-1000], dtype=int32)
多维数组每个维度都有一套索引,调用时可用逗号隔开
>>> 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] # 取每行的第二个元素
array([ 1, 11, 21, 31, 41])
>>> b[1:3, :] # 取前两行
array([[10, 11, 12, 13],
[20, 21, 22, 23]])
如果多维数组的索引没有提供,默认情况下会视为全集
>>> b[-1] # 等效为 b[-1, :]
array([40, 41, 42, 43])
上述例子中由于其他维度没有提供, NumPy 将视其他纬度为 :,即全集
除了冒号( :)之外, 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],
... [10,12,13]],
... [[100,101,102],
... [110,112,113]]])
>>> c.shape
(2, 2, 3)
>>> c[1,...] # c[1,:,:]
array([[100, 101, 102],
[110, 112, 113]])
>>> c[...,2] # c[:,:,2]
array([[ 2, 13],
[102, 113]])
遍历一个多维数组在默认情况下会遍历第一维度
如果想遍历一个数组的全部元素,可以使用 flat 属性
>>> 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]
# 取全部元素
>>> for element in b.flat:
... print(element)
...
0
1
2
3
...
一个数组的形状( shape)即每一维度的元素数量
>>> a = np.floor(10*np.random.random((3,4)))
>>> a
array([[4., 3., 0., 7.],
[6., 8., 4., 8.],
[3., 4., 8., 8.]])
>>> a.shape
(3, 4)
数组的形状可以通过多种方式修改
>>> a.ravel() # 返回一个压平后的一维数组
array([4., 3., 0., 7., 6., 8., 4., 8., 3., 4., 8., 8.])
>>> a.reshape(6,2)
array([[4., 3.],
[0., 7.],
[6., 8.],
[4., 8.],
[3., 4.],
[8., 8.]])
>>> a.T # 转置
array([[4., 6., 3.],
[3., 8., 4.],
[0., 4., 8.],
[7., 8., 8.]])
>>> a.T.shape
(4, 3)
在 ravel() 函数中,输出的一维数组通常是深度优先遍历的( C-style)
因此,如果使用 reshape() 函数,遍历方式也是深度优先
如果希望使用广度优先( FORTRAN-style)或其他遍历方式,可以使用 order 参数
承上所述,使用 reshape() 函数调整维度时,会返回一个新的数组
使用 nadrray.resize() 则会修改本身的纬度
>>> a
array([[4., 3., 0., 7.],
[6., 8., 4., 8.],
[3., 4., 8., 8.]])
>>> a.resize((2, 6))
>>> a
array([[4., 3., 0., 7., 6., 8.],
[4., 8., 3., 4., 8., 8.]])
调整数组维度时,也可以使用 -1 来自动计算剩余维度
>>> a.reshape(3,-1)
array([[4., 3., 0., 7.],
[6., 8., 4., 8.],
[3., 4., 8., 8.]])
NumPy 提供了数组叠加的函数 hstack 和 vstack
>>> a = np.floor(10*np.random.random((2,2)))
>>> a
array([[4., 7.],
[2., 0.]])
>>> b = np.floor(10*np.random.random((2,2)))
>>> b
array([[2., 1.],
[8., 2.]])
# 垂直叠加(0维扩展、添加行)
>>> np.vstack((a,b))
array([[4., 7.],
[2., 0.],
[2., 1.],
[8., 2.]])
# 水平相加(1维延伸、添加列)
>>> np.hstack((a,b))
array([[4., 7., 2., 1.],
[2., 0., 8., 2.]])
类似的, NumPy 提供 hsplit 和 vsplit 两个函数用于拆分数组
>>> a = np.floor(10*np.random.random((2,12)))
>>> a
array([[3., 3., 9., 7., 4., 3., 2., 5., 8., 6., 0., 3.],
[6., 0., 5., 9., 7., 9., 3., 5., 1., 0., 2., 3.]])
>>> np.hsplit(a,3) # 指定以第4列为垂直线来分隔
[array([[3., 3., 9., 7.],
[6., 0., 5., 9.]]), array([[4., 3., 2., 5.],
[7., 9., 3., 5.]]), array([[8., 6., 0., 3.],
[1., 0., 2., 3.]])]
>>> np.hsplit(a,(3,4)) # # 指定以第4、5列为垂直线来分隔
[array([[3., 3., 9.],
[6., 0., 5.]]), array([[7.],
[9.]]), array([[4., 3., 2., 5., 8., 6., 0., 3.],
[7., 9., 3., 5., 1., 0., 2., 3.]])]
vstack 和 vsplit 是对第一维操作( vertically,对于矩阵来说,即“行”)
hstack 和 hsplit 是对第二维操作( horizontally,对于矩阵来说,即“列”)
如要针对任意维操作:
叠加: concatenate
https://www.numpy.org/devdocs/reference/generated/numpy.concatenate.htm
拆分: array_split
https://www.numpy.org/devdocs/reference/generated/numpy.array_split.html
NumPy 对于返回过程中的内存控制有以下三种情况:
简单的赋值操作不会复制任何内存
>>> a = np.arange(12)
>>> b = a
>>> b is a
True
>>> b.shape = 3,4
>>> a.shape
(3, 4)
Python 会将可变( mutable)对象作为引用传递,因此也不会复制任何内存
>>> def f(x):
... x.shape = 4,3
...
>>> f(a)
>>> a.shape
(4, 3)
>>> b.shape
(4, 3)
view 函数会使用浅复制的方法创建一个数组对象的“视图”
>>> c = a.view()
>>> c is a
False
>>> c.base is a
True
>>> c.flags.owndata
False
>>> c.shape = 2,6 # a的shape没有被改变
>>> a.shape
(4, 3)
>>> c[0,4]=1234 # a的data没有被改变
>>> a
array([[ 0, 1, 2],
[ 3, 1234, 5],
[ 6, 7, 8],
[ 9, 10, 11]])
对数组进行裁剪即会使用 view 函数返回原数组的部分属性(即元素)
>>> s = a[:,1:3]
>>> s[:] = 10
>>> a
array([[ 0, 10, 10],
[ 3, 10, 10],
[ 6, 10, 10],
[ 9, 10, 10]])
copy 函数会返回一个数组的深度复制,包括所有的属性
>>> d = a.copy()
>>> d is a
False
>>> d.base is a # d不与a共享任何数据
False
>>> d[0,0] = 999
>>> a
array([[ 0, 10, 10],
[ 3, 10, 10],
[ 6, 10, 10],
[ 9, 10, 10]])
从垃圾处理的角度来说,如果从一个巨大的数组中截取了部分元素,而原数组不再被需
要,那么最好使用 copy 函数深度复制截取的子数组,并将原数组销毁
>>> a = np.arange(int(1e8))
>>> b = a[:100].copy()
>>> del a
上面的例子中,如果使用 b = a[:100] 而不使用 copy 函数,那么 a 所占据的内存将不
能被销毁
Numpy相对于Python标准库来说提供了更多的索引工具
例如,NumPy允许通过一组索引来一次性索引一个数组
>>> a = np.arange(12)
>>> a
array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
>>> a = np.arange(12)**2
>>> a
array([ 0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121],
dtype=int32)
>>> i = np.array([1,1,3,8,5])
>>> a[i]
array([ 1, 1, 9, 64, 25], dtype=int32)
>>> j = np.array([[3,4],[9,7]])
>>> a[j] # 形状和j一样
array([[ 9, 16],
[81, 49]], dtype=int32)
当被检索的数组是多维数组时,传递数组索引将被视为检索第一维度。例如,我们可以
通过检索调色盘来生成一张图片
>>> palette = np.array([[0,0,0], # black
... [255,0,0], # red
... [0,255,0], # green
... [0,0,255], # blue
... [255,255,255]]) #white
>>> 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]]])
NumPy也支持传递多维数组作为检索索引,但是每一维的形状必须相同
>>> 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], # a的第二维索引
... [3,3]])
>>> a[i,j] # i和j必须是相同形状的
array([[ 2, 5],
[ 7, 11]])
也可以把 i 和 j 放进一个列表里,再传递给 a
>>> l = [i, j]
>>> a[l] # 和a[i,j]等价
array([[ 2, 5],
[ 7, 11]])
但是,不能把 i 和 j 放进一个数组里,否则按照 NumPy 的设定将视为查询第一维度
>>> 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[tuple(s)] # 和a[i,j]等价
array([[ 2, 5],
[ 7, 11]])
数组索引经常被用在检索时间相关的数组中
>>> time = np.linspace(20,145,5) # time scale
>>> data = np.sin(np.arange(20).reshape(5,4)) # 4 time-dependent series
>>> 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) # index of the maxima for each series
>>> 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(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]])
与数组索引类似,布尔索引也可以传递多个给多维数组,从而获取对应元素
>>> a = np.arange(12).reshape(3,4)
>>>> a
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
>>> 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] # the same as a[b1,:]
array([[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
>>> a[:,b2] # 挑选列
array([[ 0, 2],
[ 4, 6],
[ 8, 10]])
>>> a[b1,b2] # a weird thing to do?
array([ 4, 10])
NumPy 提供一个 ix_ 函数,能把两个一维数组
转换为一个用于选取方形区域的索引器
>>> arr2 = np.arange(32).reshape((8,4))
>>> arr2
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, 30, 31]])
>>> arr2[np.ix_([1,5,7,2],[0,3,1,2])]
array([[ 4, 7, 5, 6],
[20, 23, 21, 22],
[28, 31, 29, 30],
[ 8, 11, 9, 10]])
❖ 在这个例子中, ix_ 函数将数组 [1,5,7,2] 和数
组 [0,3,1,2] 产生笛卡尔积,即: (1,0), (1,3),
(1,1), (1,2); (5,0), (5,3), (5,1), (5,2);
(7,0), (7,3), (7,1), (7,2); (2,0), (2,3),
(2,1), (2,2)
❖ 按照坐标 (1,0), (1,3), (1,1), (1,2) 取得
arr2 所对应的元素 4, 7, 5, 6
❖ 按照坐标 (5,0), (5,3), (5,1), (5,2) 取得
arr2 所对应的元素 20, 23, 21, 22
❖ 如此类推
❖ 更多的进阶功能可以参考官方教程:
❖ https://www.numpy.org/devdocs/user/quickstart.html
❖ NumPy 的实用小技巧:
❖ https://www.numpy.org/devdocs/user/quickstart.html#tricks-and-tips
❖ 函数详细说明文档及手册:
❖ https://www.numpy.org/devdocs/reference/routines.html
❖ Wes McKinney《利用 Python 进行数据分析》 :
❖ http://product.dangdang.com/25312917.html