NumPy 初级教程 ;
此教程基于numpy官方user guide https://docs.scipy.org/doc/numpy-1.14.1/user/
快速入门 ;
NumPy的主要对象是homegeneous 多维矩阵。它的元素要求是同一个类型(Python的array不要求elements必须是同一个类型),索引是a tuple of positive integers(数字0呢?)。在Numpy中,维度dimensions被称作axes(轴,是axis的复数形式)。
例如,3D空间中的一个点[1,2,1]有1个axis,此axis有3个elements,所以我们说它长度length是3.下图的例子中,2行3列的矩阵有2个axes。第1个axis长度是2,第2个axis长度是3:
[
[1, 0, 0],
[0, 1, 2]
]
NumPy 类 ;
NumPy的矩阵类叫做ndarray,它的别名是array(numpy.array),注意这个numpy.array和python标准库里的array(array.array)是不同的,标准库里的array只处理1维数组并且功能有限。ndarray类里比较重要的属性attributes有:
. ndarray.ndim 整数, 矩阵的维数(dimensions),也就是axes的数目
. ndarray.shape tuple类型,每个元素对对应该矩阵在每1维上的长度,也是矩阵的行列数等
. ndarray.size 整数,矩阵元素总个数,也就是shape里各元素的乘积
. ndarray.dtype 是描述矩阵里元素类型的一个对象,不是数值类型。可以使用标准Python类型来创见或指定dtype。另外NumPy还提供它自己的类型,例如numpy.int32, numpy.int16,numpy.float64等
. ndarray.itemsize 整数,数组中每个元素所占的字节数。例如,float64类型的元素的itemsize是8(=64/8)。它等效于ndarray.dtype.itemsize
. ndarray.data 包含数组中实际元素的缓冲区。通常,我们不需要使用该属性因为我们将会通过索引的方式来访问矩阵元素。
import numpy as np
a = np.arange(15).reshape((3,5)) # same as reshape(3,5) without tuple format
print(a)
print(a.ndim, type(a.ndim))
print(a.shape, type(a.shape))
print(a.size, type(a.size))
print(a.dtype, type(a.dtype)) #注意,dtype是个类对象,
print(a.itemsize, type(a.itemsize))
print(a.data, type(a.data)) # a.data是个buffer,不是不同数据类型
创建1个NumPy矩阵 ;
有多种方法可以创建1个NumPy矩阵:
. 使用类的构造函数方法,numpy.array(...若干参数配置...), 输入数据来源可以是list或tuple,也可以是np的子类,如np.mat等
. 使用NumPy提供的一些API快速生成全零zeros,全1 ones,或空矩阵 empty
. 使用NumPy提供的一些序列API快速生成序列 arange, linspace..
使用array方法创建1个矩阵 ;
help(np.array)
a = np.array([2,3,4]) # 输入是1个list
print(a, type(a))
b = np.array((1.1, 2.2, 3.3, 4.4))#输入是一个tuple
print(b, '\n', b.dtype)
使用API来创建特殊矩阵 ;
#help(np.zeros) #3个输入参数,第1个是tuple,第2个是默认float,第3个是顺序
#zeros(shape, dtype=float, order='C')
c = np.zeros((3,5)) #输入必须是tuple形式,因为函数要求该参数是shape,一个tuple类型
print(c, c.dtype) # 默认是浮点型
d = np.ones((3,5)) # 默认是浮点型
print(d, d.dtype)
e = np.empty((3,5)) #uninitialized, value might vary
print(e, e.dtype)
使用API创建一个连续数字序列矩阵 ;
NumPy提供了一个类似range的方法:
arange([start,] stop[, step,], dtype=None)
它生成的是矩阵而不是list。start可选,默认是0,stop不包含在内,step可选,默认是1,但如果显式指定step,则也必须指定start
m = np.arange(10,60,5)
print(m, m.dtype)
np.arange也接受浮点型参数,
n1 = np.arange(0,2,0.3)
print(n1, n1.dtype)
当arange使用浮点参数类型时,由于浮点数的精度有限,通常是不可能预测元素个数的。基于此原因,使用函数linspace来获得1个指定长度的数组而不是指定步长的方法更好
from numpy import pi
n2 = np.linspace(0,2,9) # try to get 9 numbers from [0,2]
print(n2, n2.size, n2.dtype)
n2
x = np.linspace(0,2*pi, 100) #useful to evaluate function with lots of points
y = np.sin(x)
print(x.size, y.size)
See also
array, zeros, zeros_like, ones, ones_like, empty, empty_like, arange, linspace, numpy.random.rand, numpy.random.randn, fromfunction, fromfile
生成随机矩阵 ;
可以生成两类随机数
- 平均分布 uniform distribution over [0,1)
- 正态分布 Normal distribution over [0, 1) with mean =0, var=1
从上述两类得到的变体:
- [a, b]区间内的均匀分布 a+ np.random.rand(...)*(b-a)
- 其他高斯分布 For random samples from N(μ, σ2), use:
σ * np.random.randn(...) + μ
r1 = np.random.rand(3,2)#矩阵维度直接指定。生成3行2列的平均分布的随机数矩阵,元素值[0, 1)
print(r1, r1.ndim, r1.shape)
r2 = np.random.random_sample((5,))#此函数接受tuple型输入参数,5x1的数组
print(r2, r2.size)
#如输入为空,则返回1个float型值
r3 = np.random.random_sample()
print(r3, type(r3))
r4 = np.random.randn(3, 2)#矩阵维度直接指定
print(r4, r4.shape)
r5 = np.random.standard_normal((3,2)) #接受tuple类型尺寸, 功能同上
print(r5, r5.shape)
矩阵打印 ;
矩阵可以直接用print函数打印,如果矩阵过大,则默认只打印出角落里的值,如果要强制显示全部值,则可以使用如下函数来改变打印选项:
np.set_printoptions(threshold=np.nan)
矩阵基本操作 ;
算数元素作用于每一个元素, Arithmatic operators on array apply elementwise. 会生成一个新的矩阵来保存计算结果。注意矩阵乘法是用dot函数来实现的,*表示标量乘法
a = np.array([20, 30, 40, 50])
b = np.arange(4)
c = a-b
d= b**2
e = 10* np.sin(a)
f = a < 35
g = a * 2
print('b=',b)
print("c=",c)
print("d=",d)
print("e=",e)
print("f=",f)
print("g=",g)
A = np.arange(9).reshape(3,3)
B = A + 2
print('A=', A)
print('B=', B)
print("A.dot(b) = ", A.dot(B)) #矩阵乘法
print("np.dot(A,B) = ", np.dot(A, B))#也可以这样用矩阵乘法
有些矩阵自操作,如+=, *= 则不会生成新的结果矩阵,而是原位代替, replace in place
单目操作符 unary operations ;
很多都被定义成ndarry类的函数,如max,min,sum等,不指定axis则整体作为1个数组处理,若指定axis则沿该axis处理。
a = np.random.rand(2,3)
print(a)
print("a.max()=", a.max())
print('a.min()=', a.min())
print('a.sum()=', a.sum())
print('a.sum(axis=0) = ', a.sum(axis=0)) # axis =0, first dimision, len=2, each col
print('a.sum(axis=1) = ', a.sum(axis=1)) # axis =1, second dimision, len=3, each row
print('a.cumsum(axis=1) = ',a.cumsum(axis=1))#沿着row进行逐个累加计算
矩阵通用函数 ;
NumPy提供了一系列的通用库函数来加速处理,它们基本上都是作用于每一个元素的,然后生成一个新的结果矩阵。此类函数有sin, cos, exp,sqrt, add
See also
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
索引,分片,遍历 ;
1维数组类似Python的list,可以用索引,分片,遍历来访问元素
a = np.arange(10) **3 #10 items,
print(a)
print(a[0], a[2])
print(a[2:5]) #[2,5)
a[:6:2] = -1000
print(a[:6:2]) # same as print(a[0:6:2])
print(a)
for i in a:
print(i**(1/3.)," ")
fraction power of a negative number is **NOT** defined in some languages(C) and not accurate in python
(-1000)**(1.0/3) # we expect this to output -10, but it will not.
(5+8.660254037844384j) **3 #very close to 1000
因此,针对复数的分数阶指数运算,比如开立方,可以根据实际情况,先用它的绝对值进行运算,然后再进行相应处理:
x = -1000.0
y = np.sign(x)*np.abs(x)**(1.0/3)
print(y)
但注意不是总是适应,如开平方根,上述公式就不可以了
b = a[::-1] #reverse a
print(b)
多维数组可以用tuple形式的索引来访问 ;
def f(x,y):
return 10*x+y
b = np.fromfunction(f, (5,4), dtype = int)#
print(b.shape)
print(b)
print(b[2,3], b[2][3], b[(2,3)]) #三种访问方式都可以,注意索引从0开始。
print(b[0:5, 1]) #print 2nd col
print(b[:, 1]) #similar as in matlab, but index starts from zero
几点特殊标记 ;
- 当索引数值个数少于矩阵维数是,缺少的索引被认为全选:(complete slices)
print(b[-1])
print(b[-1,:]) #same, access last row
- 用省略号(3个连续点号)表示任意多的冒号
print(b[-1,...]) #same as below
print(b[-1,:])
遍历多维数组 ;
for row in b:
print(row)
可以使用ndarry的flat属性访问所有element
#for element in b.flat:
# print(element)
改变矩阵形状 ;
如下三个函数都返回一个修改过的矩阵,而原矩阵不变。 ravel,reshape,T
a = np.floor(10*np.random.random((3,4)))
print(a, a.dtype, a.shape)
print(id(a),id(a.ravel()))
print(id(a),id(a.reshape(6,2)))
print(id(a),id(a.T), id(a.transpose()))
和reshape不同, resize函数返回一个新矩阵的同时,也修改了原矩阵自身,**注意**
print(id(a),id(a.resize(2,6)))#id不同,表明生成了新矩阵
print(a.shape, a)#可以发现原矩阵也被改变了
如果某个维度参数为-1, 则reshape函数会自动计算出该维度所需数值
print (a.reshape(3, -1))
叠加多个矩阵 ;
多个矩阵可以沿不同维度叠加,可以采用vstack, hstack
a = np.floor(10*np.random.rand(2,2))
b = np.floor(10*np.random.rand(2,2))
print("a=",a,"\nb=",b)
print(np.vstack((a,b, a))) #输入参数是tuple形式,这样可以叠加多个
print(np.hstack((a,b)))
c = np.column_stack((a,b,[9,9],[11,11]))#将1D数组作为2D数组的列加入
print(c)
但当两个都是1D数组时,column_stack会先把它们都转换成列向量,然后再叠加
print(np.column_stack((np.array([1,2]), np.array([3,4]))))
将1个矩阵分成多个小矩阵 split ;
a = np.arange(24).reshape(2,12)
print(a)
使用hsplit将a矩阵分割成几个小矩阵,结果是一个array,沿水平风向分隔。它可以接受1个整数参数来表示想均分的块数,也可以加1个包含多个整数列索引的tuple来指定从哪几个列后开始分割。vsplit类似,只是沿竖直方向分割。
b = np.hsplit(a, 3) #将a矩阵沿水平方向分割为3块,每块列数为4
print(type(b)) #b是个普通的python list类型
print(b)
print(type(b[0]))#b的元素每一个都是numpy.ndarray类型
print(b[0])
c = np.hsplit(a, (3,4))#将a矩阵从第3列后和第4列后分割,即c[0]包含前3列,c[1]包含1列, c[2]包含剩下的。
print(c[0])
print(c[1])
print(c[2])
矩阵的复制和视图 ;
当操作矩阵时,它的数据有时会被复制到1个新的矩阵,而有时不会。这是初学者经常遇到的一个困扰。有如下三种情况:
1. 完全不复制。 ;
简单的赋值操作不会复制array的data,而是使用同一个内存区域,因此当改变1个矩阵时,另一个也会改变。类似C语言的指针或者C++里的reference。
a = np.arange(12)
b = a # simple assignment, no copy at all
b is a
id(b) == id(a)
b.shape
b.shape = (3,4)#改变b的形状,本质也改变了a
a
函数调用时作为参数传入时,不复制。python函数调用时,可改变对象只传入它的参照reference,类似id或指针。
def f(x):
x[0,0] = 15 #inside this function, the first element is changed
print(id(x))#打印id验证
print(id(a))
print(a)
f(a)#调用函数,修改第1个元素的值为15
print(a) #函数执行完毕,值已经被更新。
2. View或浅copy View or shallow copy 我觉得可以翻译成影像和浅复制 ;
不同的矩阵对象可以共享同样数据。view方法创建了1个新的矩阵对象来look at same data,类似于映射。 View的shape改变不影响原矩阵,但是View的值改变时则会影响原矩阵做相应更新
print(a)
可以显式调用view方法来获取1个新的view(ndarry对象),也可以通过slicing(分片索引)得到。
c = a.view() # c is a new ndarray object,不同于a
print(id(c), id(a), id(c)==id(a), c is a)
print(type(c))
c.base is a #c是a的一个view,所以base是a
c.flags #OWNDATA: False 表明它本身没有数据,而是查看别人数据。
c.shape = (2,6)#改变c的shape,打印后发现c的形式确实改变了。
print("c=",c)
print('a.shape=', a.shape)#但原矩阵形状不受影响,值也不受影响
print("a=", a)
c[0,0] = 1234 #改变c的值,原矩阵也受影响。
print("c=",c)
print("a=", a)
矩阵的分块索引slicing就是获取了它的一个view
s = a[:, 1:3] #s包含了a的所有行和第2到第3列
print(s)
改变s的值会影响原矩阵a的值:
s[:] = 10 #将s的所有值都赋值为10,
print(s)
print(a)
3. 深度复制 Deep copy ;
通过调用ndarray的copy方法来生成1个全新的对象,此对象和原矩阵互相独立,互不影响。
d = a.copy()
print("a=",a)
d[0,0] = 999
print("d=",d)
print("a=",a)
print(id(a), id(d), d.base is a)
高级矩阵索引方法和技巧 ;
看了下,就是类似Matlab的索引方法,索引可以不是整数数值,而是1个矩阵,如果该索引矩阵元素值是整数,则结果矩阵和该索引矩阵形状相同,值对应映射过去;如果该索引矩阵元素是逻辑值,则只有True的位置对应的元素被取出。
a = np.arange(12)**2
print(a)
i = np.array([1,1,3,8,5])
print(a[i])
j = np.array([[3,4],[9,7]])
print("j=", j)
print("a[j]=", a[j])