Python科学计算库Numpy的使用 - small_kid的博客 - 我的搜狐
Python科学计算库Numpy的使用
1. 业界习惯用如下语句导入numpy的package
import numpy as np
2. np支持的数据类型:int8, int16, int32, int64, unit8(无符号的整数,unit8是0~255), unit16~64, float32, 64, complex64, 128, boolean。字母表示数字类型(dtype),后面的数字表示位数,即数值在内存中所占的bit的数目(不明白~)。还有int, float和complex,其位数取决于平台类型,即16或者32位,complex也是,不过因为是两个float表示的,所以位数是64或者 128。还有更高级的类型。
3. 转换数据类型:y=np.int_([1,2,3]),返回一个np的array。
4. 生成array时指定数据类型:z=np.arange(3,dtype='f'/float),推荐后一种写法。此处虽然写的float,但np会自动 把dtype转换成np.flaot。z的转换有两种做法,或者通过类方法:z.astype(int8),或者通过函数:np.int8(z)。
5. 查看数据类型。也有两种做法,或通过类方法:z.dtype(),或通过函数:np.dtype(z),或者np.issubtype(d,int),返回bool值。
6. 创建array的五种方法:a. 从list转换(tuple特殊对待,不转换,见26);b. 内建函数创建(arange,ones,zeros(这个构建structured array时常用,见30)等等);c. 从文件读取;d. 直接从raw bytes构建(啥意思?);e. 借助某些库里的函数生成。
7. 通过转换生成array:x=np.array([1,2,3],[4,5,6])
8. 通过内建方法生成array:np.zeros((2,3)),返回array([0,0,0],[0,0,0]),即维度为2*3的矩阵。
9. arange,类似range,参数可以是小数或者负数
10. np.linspaces,规定起点、终点(包含)、返回array的长度,返回一个两端点间数值平均分布的array。
11. np.indices没看懂,似乎某些情况下可以很有用。
12. 直接从文件读取需借用其他库里的方法
13. x.shape = (2,5),指定维度,相当于R里的c(2,5) <- dim(x)。也可以这样用:y = x.reshape(5,7)。
14. array indexing和slicing跟Python里的一样也是从0开始的。不同的是如果用index array去做reference的话返回的是一个复本,而不是目标array里的原数。这个原则适用于下面的数值和mask index array。这个特性使得赋值(assignment)运算有点奇特,见24。
15. 说到indexing,记一笔前几天新学到的逆序排列的一招:x[::-1],其中x不限于list,只要是能slice的都行。其原理是 x[[int1]:[int2]:[int3]],其中int1是起点,int2终点(不包含),int3是步长,负数则表示倒着来。如果 int3<0 and int1<int2,那么返回一个len为0的对象,type和x一样。
16. array indexing的输入对象也要求是一个array,比如:x[np.array([3, 3, 1, 8])]。也可以是:x[np.array([[1,1],[2,3]])],返回一个同样维度的array。
17. 多维array的indexing比较麻烦一些,个人认为基本方法就不大直观:y[np.array([0,2,4]), np.array([0,1,2])]返回的是y[0,0],y[2,1]和y[4,2]。
18. 当然可能也有好处,比如如果取一行/列就为其中每个数指定行/列数,因为有自动broadcasting(不知道啥意思,猜测不止扩展或补齐的意思)功能:y[np.array([0,2,4]), 1]。
19. 不指定某一个维度的index则取该维度所有值,如:y[np.array([0,2,4])],返回array的shape是index array的shape串联上所有没指定的维度的部分的shape。如这个例子中,y = np.arange(35).reshape(5,7),shape是5*7,没有指定第二个维度则返回array的shape是3(index array的shape)*7(所有没指定的维度的部分,即第二个维度,的shape)。
20. array数据筛选。我管这个叫filtering,User Guide上叫mask index arrays。一个指功用,一个指手段。用法:y[y>=20],跟R里的用法一样。也可以绕一点,b=y>20,这样生成一个 dtype=bool的array,然后又套在y里:y[b]。还可以对b进行slicing,作为index array指定位置,如:y[b[:,5]],其中b[:,5]返回第一维度不限,第二维度是5的array,由于y是5*7的,返回array长度是 5,shape是5*1。把这个array作为index array对y筛值,由于是个一维array,对于y来说第二维没有指定,所以取全部的。b是array([False, False, False, True, True], dtype=bool),所以仅取第一维度的最后两个,对于二维array来说就是最后两行,每行7个数,返回的是一个2*7的array。
21. index array和slicing的混搭:y[np.array([0,2,4]),1:3],工作原理还是跟index array的差不多,只不过1:3这个部分对第一个array里的每个数所代表的行都起作用(broadcast了)。也可以跟mask index array一起混搭。
22. 插入维度:y[:,np.newaxis,:].shape成了(5,1,7)。新增的维度里面没有元素,但运算时行为遵照新的array的适用原则,合 并array时很有用:(x原为1*5的array)x[:,np.newaxis] + x[np.newaxis,:] ,返回的是一个5*5的array。不然得赋值z,再指定z的shape等等,较为麻烦。还有一种比较无语的写法:(z的shape是 (3,3,3,3))z[1,...,2],相当于z[1,:,:,2]。
23. array slice赋值,跟Python差不多,需要注意的是每个array都有自己的dtype
24. 赋值(assignment)的时候和reference时不一样,总是array里的原数发生变化(不然还赋个啥劲儿的呀),不过规则有点奇特。比 如:x[np.array([1, 1, 3, 1]) += 1,array并非直接赋值,而是先被提取出来(extracted),然后逐个赋值,然后assign回去,在1这个位置的那个值被赋值3次,每次都 是+=1之后的那个值,所以结果是1和3位置的值+=1。
25. 也可以将index array赋值给变量,然后用变量做indexing,就像20里的那样。可以混搭,可以懒人:indices = (1, Ellipsis, 1),然后z[indices]
26. list和tuple做indexing结果是不一样的,比如z[(1,1,1,1)]相当于z[1,1,1,1],仅返回一个数,z[[1,1,1,1]]则返回一个4*3*3*3的array)。
27. broadcast的作用是使得不同shape的array在算术运算的时候shape相符,机制上broadcast使得循环在C中发生,这样就比在Python里发生要快。缺点是额外占用内存,所以不要滥用。
28. 想要能够利用broadcast必须满足两个条件:从后向前匹配,同一维度上的数值相等,或者至少其中为1。维度数目不一定要求一样。如果是一维的则会自动往另外一个array数值相等或者为1的那个维度上靠。
29. 预设结构模板的array(structured array或称record array):元素是一组对象组成的tuple,类型已经指定。
30. structured array的dtype参数类型有四种:a. 字符串,包括b1, i1, i2, i4, i8, u1, u2, u4, u8, f4, f8, c8, c16, a<n>,分别代表bytes,整数,无符号整数,浮点数和byte长度一定的字符串,还有一些 数:int8,...,uint8,...,float32, float64, complex64, complex128。定义的时候很灵活,比如:x = np.zeros(3, dtype=’3int8, float32, (2,3)float64’),返回的array长度为3,元素是array([([0, 0, 0], 0.0, [[0.0, 0.0, 0.0], [0.0, 0.0, 0.0]])。
31. 第二个参数类型是tuple,仅在将structure映射到一个已有的数据类型上时使用,即在tuple里已有的数据类型以及数据类型定义,比如:x = zeros(3, dtype=(’i4’,[(’r’,’u1’), (’g’,’u1’), (’b’,’u1’), (’a’,’u1’)])),这样就覆盖了默认名称(f0,f1之类),不过返回没搞明白,为啥是一个1*3的array?行为有点像字典,比如可以这样 引用:x[’r’](默认的话是x[’f1’]),返回的是原数据,而非拷贝。
32. 第三个类型是列表,元素是tuple,每个tuple里,可以有名称、类型、shape(可选)的定义。用法:x = np.zeros(3, dtype=[(’x’,’f4’),(’y’,np.float32),(’value’,’f4’,(2,2))])。
33. 更加复杂的是字典,有两种定义方式。一是给键名names和formats指定长度相等的列表为键值,其中names必须为字符串。还有两个可选的键名 offsets和titles。例子:x = np.zeros(3, dtype={’names’:[’col1’, ’col2’], ’formats’:[’i4’,’f4’]})。另一种方式是键名是name的值, 键值其他键名的值是组成的tuple。这里offset是必需的。例子:x = np.zeros(3, dtype={’col1’:(’i1’,0,’title 1’), ’col2’:(’f4’,1,’title 2’)})。
34. 查看name:x.dtype.names(dtype是个对象)。也可以通过赋值进行调整:x.dtype.names = (’x’, ’y’)。还可以像字典一样查看field:x.dtype.fields[’x’][2]。
35. 接下来的内容跟“类”这个概念有关,我到现在也没搞明白,当然也没怎么真正努力去试图理解它。这次就硬着头皮逼一逼自己,看能不能得到一鳞半爪的。首先,子类化ndarray返回的是一个拷贝,正因为此子类化的方法与标准的做法不太一样。
36. 比如初始化时最开始用__new__,然后通常是调用__init__,二者都可以初始化类实例,不过ndarray没有__init__方法,所有初始化都是由__new__完成的。使用__new__的好处是可以返回其他类的实例,而__init__不行。
37. 子类对象的属性需要继承原对象的,但是__new__不能完成这个功能,所以需要__array_finalize__方法把原对象的属性转移过来。
38. 如果用ufunc(通用函数,universal function)处理自定的和ndarray类实例的话,会调用到__array_wrap__方法,把结果转换成子类的实例。缺省状态下 __array_wrap__调用__array_finalize__。这个方法需要至少一个参数,是ufunc处理的对象,还有一个可选的参数 parameter。有的ufunc返回一个三元tuple,包括ufunc名称、参数和domain,作为parameter。
39. np通过base属性来追踪对象在内存中的位置,这样可以使得在删除view的赋值是不删除原数据本事。原来的array的base一般是None,如果是view,那么其base是所view的原array的名称。
40. 两个科学计算Python合集:Python(x,y)和Enthought Python Distribution (EPD)。交互解释器:iPython(装了一个,偶尔用用,还没体会到比Python自带的shell的强大所在及程度)、 spyder(Python(x,y)自带的IDE,模仿MATLAB的workspace功能)。比较有用的函数 库:Numpy,SciPy,SymPy(符号运算系虾米?),Traits(界面设计库,说得我都想弃wxPython开始学这个了。在计算机领域学什 么是个很重要的问题。),TVTK(用Traits库封装的标准VTK库),Visual(制作3D动画演示),OpenCV(这个前几天还碰到过,感觉 跟图像生成渲染之类的有关,对于Python,也提供了API)。
41. 调整shape时数组(就是官方文档里的array)元素在内存中的位置并没有改变,也就是提供了一种view而非copy。如果某一维数值是-1,则按数组的长度和其他维度的数值自动计算。
42. reshape方法也是,返回一个shape修改过的数组,但是还是view而非copy。
43. 和linspace相似,logspace创建在log尺度均匀分布的数值组成的数列,即等比数列。
44. 此外,还可以使用frombuffer, fromstring, fromfile等函数可以从字节序列创建数组,也就是上次没有深入的领域。以fromstring为例,在内存中以字节方式存储。一个字节(byte) 占8个bit(终于搞明白byte和bit的关系啦,呼瑞!),如果要转化成int16的话有个两个字节串联顺序的问题。Python里是以little endian(又见little endian!当年写tiqho的时候没整明白的问题终于搞明白了,happy)方式在内存中保存数据的,即低位字节在前,故int16值等于之前的字 节*256(应该是因为是一个byte占8个bit,故用2^8,不过还是没彻底明白)加上后一个字节。
45. 还可以使用fromfunction利用自己写的函数进行转换。比如:np.fromfunction(func, (10,)),其中func是自写函数,(10,)是返回数组的shape。func的参数默认从0开始,直到填满指定的shape。
46. 数组的reference还可以用boolean array(一般不是手动一个个敲出来的,见9),此时必须是array,不能再用列表蒙混了,broadcast时都按False算。返回其中为True的元素的相应位置上的目标array中的元素。
47. np.random.rand([int]) :产生一个长度为[int],元素值为0-1的随机数的数组。
48. 对于数组,如果进行大小比较,和Python里类似,返回的也是bool,不过是个数组。这个数组就可以用来做7里的reference。见7。
49. seriously?——“组元不需要圆括号虽然我们经常在Python中用圆括号将组元括起来,但是其实组元的语法定义只需要用逗号隔开即可。
50. 结构数组(structured array)有点像类(或者我理解的类),定义一个dtype对象,有一定模式,然后指定其他对象的dtype是它。
51. 查看dtype:obj.dtype,可能返回如下:dtype([('name', '|S32'), ('age', '<i4'), ('weight', '<f4')]),其中“|”, “<”等字符,这些字符用来描述字段值的字节顺序:“|”:忽视字节顺序,“<”:little endian,“>”:big endian。
52. 作者认为“在C语言中我们可以通过struct关键字定义结构类型,结构中的字段占据连续的内存空间,每个结构体占用的内存大小都相同”。受教了。
53. 通过调用数组的tostring或者tofile方法,可以直接输出数组的二进制形式。比如:a.tofile("test.bin")。
54. 和C语言协作使用时需要注意的是C语言的结构体为了内存寻址方便,会自动的添加一些填充用的字节,即内存对齐,以保持最终的结构体大小变。因此如果 NumPy中的所配置的内存大小不符合C语言的对齐规范的话,将会出现数据错位。在创建dtype对象时,可以传递参数align=True以确保不发生 数据错位。
55. 用字典参数也可以定义dtype对象时,因为字典的关键字是没有顺序的,所以需要在类型描述中给出字段的顺序。用法是在字段(field)后给出偏移量 (即offset)的值,单位是byte,如:dtype([('surname', '|S25'), ('age', '|u1')])。
56. ndarray在内存中的数据结构太深,不过为了将来万一和其他语言的串接,硬着头皮看一看吧。ndarray数据结构引用两个对象:数据存储区和 dtype对象存储区,具体包括dtype,dim count,dimensions,strides和data。dim count指维度的数目;dimesion指各维度的数字;strides指每个轴的下标增加1时数据存储区中的指针所增加的字节数,比如有个3*3的数 组,元素类型是float32,那么每个数占4(32/8)字节。在C语言格式中,第二个维度的数字比第一个变得快,所以第二个维度数字增加1指针增加4 字节,第一个的话就是12(4*3)个字节,所以strides(本身就有步伐”的意思)分别为12和4。不过这要求数据在内存中连续存储。另一种数字跳 的方式是Fortran语言格式的,需要重设参数值,改变默认设定:order=''F''。
57. ufunc原来是说能对数组里每个元素都操作的函数,ft~
58. ufunc创建新数组并返回,如果要覆盖的话可以在第二个参数里指定被覆盖的对象,比如:t = np.sin(x,x),x表现为经过了更新。
59. 算数计算运算符和函数,其中[, y]是可选参数,用于指定覆盖对象,除号运算符的处理根据是否激活__future__.division有所不同:x1+x2,相当于add(x1, x2 [, y]);x1-x2,相当于subtract(x1, x2 [, y]);x1*x2,multiply(x1, x2 [, y]);x1/x2: divide(x1, x2 [, y]),整数除法;x1/x2: true divide (x1, x2 [, y]), 返回精确的商;x1//x2,floor divide (x1, x2 [, y]),地板除;-x,negative(x [,y]);x1**x2,power(x1, x2 [, y]);x1%x2,remainder(x1, x2 [, y]), mod(x1, x2, [, y])。
60. 复杂的算式处理大数组会产生大量的中间结果而导致速度减缓,一个小技巧是手工拆分算式分解,以尽量减少内存分配,提高速度。
61. 下一个是应该算是神器级别的函数:frompyfunc,把Python里的函数(可以是自写的)转化成ufunc,用法是 frompyfunc(func, nin, nout),其中func是需要转换的函数,nin是函数的输入参数的个数,nout是此函数的返回值的个数。注意frompyfunc函数无法保证返回 的数据类型都完全一致,因此返回一个中间类型object,需要再次obj.astype(np.float64)之类将其元素类型强制调齐。
62. 作者对broadcast的处理是直接翻成“广播”。补齐的规则除了以前记下的,还有:维度差异的部分通过在已有的维度前面加1进行补齐;输出数组的 shape是输入数组shape的各个轴上的最大值;当输入数组的某个轴的长度为1时,沿着此轴运算时都用此轴上的第一组值。
63. repeat方法:b.repeat(6,axis=0),在第0轴(shape里的第一个维度)的方向上长度扩展为6,也就是第2个维度重复5次(有点绕,呵呵)。
64. ogrid对象:用切片组元作为下标进行存取,返回一到多个可以用来广播计算的数组。有两种用法:开始值:结束值:步长,和np.arange类似(不包 含结束值喽); 开始值:结束值:长度j,当第三个参数为虚数时,它表示返回的数组的长度,和np.linspace类似(包含结束值)。
65. ufunc的reduce 方法:和Python的reduce函数类似,沿着一个指定的轴将运算符插入这根轴上所有子数组或者元素当中,并返回结果,比 如:np.add.reduce([[1,2,3],[4,5,6]], axis=1),其中axis为1,指的是第2个维度上的元素,即[1,2,3]和[4,5,6]。
66. accumulate与reduce类似,不同之处在于返回的数组里包括所有中间结果,致使其shape与输入数组的相同。比 如:np.add.accumulate([[1,2,3],[4,5,6]], axis=1),返回array([[1,3,6],[4,9,15]]),其中1(1),3(1+2),4(4),9(4+5)是中间结果。
67. reduceat方法通过indices参数(列表形式)指定一系列插入reduce的起始和终止位置(reduce at嘛),规则如下:如果indice中某元素小于其后元素,则相应结果为对以这两个元素为位置产生的slice里的数组元素进行reduce;否则结果 是这个元素对应的数组元素。对于最后一个元素,因为其后再没元素,结果为对所有元素进行reduce。例 子:np.add.reduceat(np.array([1,2,3,4]),indices=[0,1,0,2,0,3,0]),返回 array([1,2,3,3,6,4,10])。
68. outer方法,<op>.outer(a,b)方法的计算等同于如下程序:a.shape += (1,)*b.ndim <op>(a,b) a = a.squeeze()。例子:np.multiply.outer([1,2,3,4,5],[2,3,4])。在这个例子中,首先,a.shape += (1,)*1,即a.shape变成了(5,1),然后multiply(a,b),然后,a = a.squeeze()剔除数组a中长度为1的轴,也就是恢复a的shape,把a给还原了。
69. 终于进入矩阵部分了,NumPy和Matlab不一样,对于多维数组的运算,缺省情况下并不使用矩阵运算。但因为NumPy中同时存在ndarray和 matrix类,用户很容易将两者弄混,有违the Zen of Python(import this!),因此matrix类的优先状态是不启用。
70. 矩阵乘法:如果需要将一维数组当作列矢量或者行矢量进行矩阵运算时,推荐先reshape之。
71. dot函数:对于一维数组,计算内积,就是两个数组对应下标元素的乘积(应该就是点积);对于二维数组计算矩阵乘积(嘛意思?);对于更高维数组,规则 是:dot(a, b)[i,j,k,m] = sum(a[i,j,:] * b[k,:,m]),即结果数组中的每个元素都是数组a的最后一维上的所有元素与数组b的倒数第二维上的所有元素的乘积之和。
72. np.alltrue(),check是否数组每个元素均为True。
73. inner : 对于两个一维数组计算内积;对于多维数组,它计算的结果数组中的每个元素都是:数组a和b的最后一维的内积,因此数组a和b的最后一维的长度必须相同, 即:inner(a, b)[i,j,k,m] = sum(a[i,j,:]*b[k,m,:])。
74. outer : 只按照一维数组进行计算,如果传入参数是多维数组,则先将此数组展平为一维数组之后再进行运算,结果是列向量和行向量的矩阵乘积。感觉跟multiply.outer没啥区别啊,见29。
75. np.linalg库还有很多线性代数领域的函数和功能,比如以前用过的np.linalg.det。这本书里提到了可求逆矩阵的inv函数,和求解多元 一次方程组的solve函数。后者用法:np.linalg.solve(a,b),其中a是一个N*N的二维数组,而b是一个长度为N的一维数 组,solve函数找到一个长度为N的一维数组x,使得a和x的矩阵乘积正好等于b,数组x就是多元一次方程组的解。
76. 现在进入文件存取了。使用tofile可以方便地将数组中数据以二进制的格式写进文件。tofile输出的数据不带格式,shape和dtype都没有,且使用C语言格式输出,不管数组本身的格式。因此,用fromfile读回来的时候需要自己格式化数据。
77. tofile方法还可以输出为文本格式,此时需要指定sep参数。用fromfile读取时也需要指定此参数。
78. 较为方便的方法是用load和save函数进行数据存取,文件名后缀是.npy,格式是NumPy专用的二进制类型,不用再管shape和dtype这些,不过代价是很难与其他语言的程序协作。通用和专有的矛盾!
79. savez:多个数组保存在同一文件中,第一个参数是输出文件名,后面是其后的参数都是需要保存的数组,自动起名为arr_0, arr_1, ...,也可以使用关键字参数为数组起一个名字。输出文件扩展名为.npz,是一个压缩包,其中每个文件都是.npy,内含一个数组,文件名即内涵数组 名。
80. 对于.npz文件,load函数自动识别并且返回一个类似于字典的对象,可以通过数组名作为关键字获取数组的内容。
81. savetxt和loadtxt分别存取txt文件,与tofile不同的是数字此时不是以二进制的方式保存的。用 法:np.savetxt("a.txt", a, fmt="%d", delimiter=","),fmt默认为'%.18e',delimiter默认为' '。显然如果以非默认设置输出了读取时也要修改相应参数。
82. load和save函数还可以对已经打开的文件对象进行操作,比如:f = file("result.npy", "wb") np.save(f, a) f.close(),其中a是一个数组。读取时也是先定义一个句柄f,然后多次np.load(f),可以顺序(save时的顺序)读取数组。
From:http://blog.chinaunix.net/uid-7596337-id-125813.html