(5)一起用python之基础篇——numpy

写在前面

目前关于numpy的书有两本,一本是《Numpy Cookbook》,另一本是《Numpy Beginner’s Guide》,两本书都有读过,但是,这两本书读起来实在不对胃口,按照我个人的理解,numpy这种东西,没必要通过一大堆数据分析的实例来学习,重点放在技巧上就行了。先通读NumPy Tutorial掌握一些技巧后针对自己的具体应用方向,多看别人的代码中是怎么使用numpy的,遇到没见过的函数再去查手册,简单,快速。我推荐一个用python做科学计算的文档,github上的,Python Scientific Lecture Notes(个人感觉写得非常好,简洁,易懂,上手快。)

本文的写作目的并非要代替上面提到的参考文档,所以,在继续往下阅读之前,建议完整浏览一遍NumPy Tutorial然后再来看回头来看看,相信会更有收获。以下内容是自己使用过程中的一些经验总结。有待完善~~~

在准备了解numpy之前

在准备了解numpy之前,有个概念需要深入理解下。Python的变量名(Identifier)

在python中,标志符本身可以指代任意类型数据,包括None,不过,标志符所指代代内容到数据类型必须是确定的。比如temperature这个标志符可以指代任意类型的数据,如果要将该标志符指代一个特定的浮点型数据,可以通过赋值符号完成。 temperature = 98.6

假设现在有另外一个变量 original,它也要指代98.6这个浮点数据,那么,一种方法是这么做: original = 98.6 在内存中这两个标志符是相互独立的,系统分配一块内存,存储98.6这个数据,然后将original指向它。二者的关系如下图,它们指向的是不同的内存区域,两个区域存储的是相同的数据98.6,对两个变量的任何操作都是相互独立的。

当然,还有另外一种做法, original = temperature 此时系统不再重新分配存储空间,而是直接将original指向temperature所指向的内容。如下图。

然后我们temperature作如下修改:temperature = temperature + 5,此时系统所做的不是在原temperature所指向内存处修改值,而是新建一块内存区域存放修改后的值,然后将temperature的引用指向该区域。如下图。

这是因为在python中,float类型属于immutable(不可变)类型数据,系统不能直接修改该变量名所指向的内容,而是新开辟一块地方存储数据,然后将该变量名指向它。

对于list列表这类 mutable(可变)类型变量,还有另外一种赋值方式。 通过 += 赋值

1234
a = [1]b = aa += [2]a = a + [3]

对于 a += [2]这种方式,计算机所做的是将原来a所指向的列表[1]改为[1,2],a的指向不变。而对于a = a + [3]这种方式,计算机所做的是开辟一块新的内存来存放 a + [3] 的结果[1,2,3],然后将a指向该区域。试想下这两种赋值后,b的取值分别是多少呢?

下面用代码实际演示下,便于理解:

 1 2 3 4 5 6 7 8 91011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162
In [1]: temperature = 98.6In [2]: original = 98.6In [3]: id(temperature) Out[3]: 140507266764736In [4]: id(original) Out[4]: 140507218038832In [5]: temperature is original Out[5]: FalseIn [6]: temperature == original Out[6]: TrueIn [7]: original = temperatureIn [8]: id(original) Out[8]: 140507266764736In [9]: temperature is original Out[9]: TrueIn [10]: temperature = temperature + 5In [11]: id(temperature) Out[11]: 140507218039048 In [12]: a = [1]In [13]: b = aIn [14]: id(a) Out[14]: 140507217949832In [15]: a += [2]In [16: a Out[16]: [1, 2]In [17]: b Out[17]: [1, 2]In [18]: id(a) Out[18]: 140507217949832In [19]: id(b) Out[19]: 140507217949832In [20]: a = a + [3]In [21]: a Out[21]: [1, 2, 3]In [22]: b Out[22]: [1, 2]In [22]: id(a) Out[23]: 140507217979944In [24]: id(b) Out[24]: 140507217949832

开始用Numpy吧~

之所以在接触 Numpy 之前先介绍上面的内容,是因为在使用numpy的过程中,会频繁涉及到数据格式的转换,赋值等操作,而numpy的基础数据类型array正是属于mutable可变类型的。

赋值

numpy的初始化很容易理解,numpy.array()函数接收iterable类型的变量(列表,tuple等),同时可以转换成相应的维度。初始化之后,数据之间的传递通过赋值来完成。主要有以下三种方式:

 1 2 3 4 5 6 7 8 91011121314151617
import numpy as npa = np.arange(10)b = a  # 第一种方式c = a.view()  # 第二种方式d = a.copy()  # 第三种方式#注意以下操分别对a,b,c,d的影响print(b.shape)  # (10,)b.shape = (2, 5)  # 改变b的维度print(a)  # array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]])print(b)  # array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]])print(c)  # array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) print(d)  # array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) c[0] = 888  print(a)  # array([[888, 1, 2, 3, 4], [5, 6, 7, 8, 9]])print(b)  # array([[888, 1, 2, 3, 4], [5, 6, 7, 8, 9]])print(c)  # array([888, 1, 2, 3, 4, 5, 6, 7, 8, 9]) print(d)  # array([888, 1, 2, 3, 4, 5, 6, 7, 8, 9])

可以这么理解着三种方式,对于第一种赋值方式,b和a的指向是完全相同的,对a或b种任一变量的任何处理都会影响到其它变量。对于第二种赋值方式,view()赋值的是视图,一开始我也不太理解,后来想到来一种描述它的方式,array这种数据结构和其它数据结构没有什么差异,都包含各种成员变量,其中有个data成员指向内存中的实际数据,同时还包含shape,item,length等等其它成员(如下图所示),view()操作所做的就是在新的内存复制了这些成员变量的信息,但是data变量的指向不变。因此,shape等成员是自己私有的,而由于data变量指向的内容是与其它变量共享的,对data所指向内容的改变会相互影响。对于第三种方式,不仅仅复制了成员变量,同时还新开辟一块内存复制了data所指向的内容,因而是完全独立的。

在numpy中,很重要的一点是要知道某一函数操作后是否对原始的数据有影响,比如np.resize,np.reshape;np.ravel,np.flatten;转置操作等等,如果你很好地理解了python中 sort函数和sorted函数的区别,那么这些函数的理解应该也不在话下。

切片

接着上面的内容继续,切片操作实际上有点像上面的view()操作的结果,它的成员变量是独立的,但是data变量指向的是原来的内存空间中的一部分,下图可以解释这点。因此在对切片后的数据作处理的时候,一定要记得对它的改变都会影响到其它数据。

array的特殊之处在于可以对其元素做掩模处理。所谓掩模是指一组布尔值,如下所示:

 1 2 3 4 5 6 7 8 910111213141516171819202122232425262728293031323334353637383940414243444546
In [56]: bool_list = [True]*5+[False]*5In [57]: bool_listOut[57]: [True, True, True, True, True, False, False, False, False, False]#将该序列作为array的index,我们就可以得到经过该布尔序列过滤后的元素::::pythonIn [63]: a[np.array(bool_list)]Out[63]: array([0, 1, 2, 3, 4])#试想一下,如果输入 a[bool_list]会得到什么呢#(注意这里bool_list是列表,不是bool类型的array)?#试试看,后面会解释这个结果。 为了得到上面的掩模,#一般可以对array做比较运算,如In [64]:a[a%3 == 0]Out[64]:array([0, 3, 6, 9])#array的index可以接受对象为整数的列表作为输入,#其得到的切片结果与输入列表的维度一致,具体如下:In [65]: a = np.array(list('abcd'))In [66]: aOut[66]: array(['a', 'b', 'c', 'd'], dtype='|S1')In [67]: a[  [0,1,2,3,2,1,0]  ]Out[67]:array(['a', 'b', 'c', 'd', 'c', 'b', 'a'], dtype='|S1')#array的index还可以接受对象为整数的array为输入,#得到的结果与 输入array的维度一致:In [69]: a[ np.array([[0,1,2],[0,1,2]]) ]Out[69]:array([['a', 'b', 'c'],        ['a', 'b', 'c']], dtype='|S1')

回到上面 a[bool_array]的例子,其结果应该是 array([1,1,1,1,1,0,0,0,0,0])因为这里的True被转换成了整数(1),而False被转换成了整数(0),然后再根据取序号[1],[0]的操作得到。

numpy的赋值比如赋值的时候,要对某一维度赋值,假设x=np.arange(10),这里灵活使用冒号,令 x[:]=5,

 1 2 3 4 5 6 7 8 91011
In [87]: x= np.arange(10)In [88]: xOut[88]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])In [89]: x[:]=5In [90]: xOut[90]: array([5, 5, 5, 5, 5, 5, 5, 5, 5, 5])

注意这里 index中 冒号的作用,指代x中的每一个元素。对于二维的x该方法同样适用。

broadcast

broadcast在numpy中随处可见,借用上面提到的文档中的一张图就可以清晰了解broadcast是怎么用的。

其它

numpy的一些高级操作在上面推荐的那个教程中写得挺好的,我就不重复了,最后介绍几个自己使用过程中发现的很好用的函数,但大多数教程中把它忽略了,需要自己去查看文档。

numpy.c_ (这里c_理解为clumn的缩写,这个有时候会省下很多代码,用的好的话非常方便,注意这个不是函数,用[]来接收变量而不是括号())numpy.r_ (与上面的类似,不过这里r_理解为row的缩写)numpy.vectorize (与python内置的map函数很像,不过由于它能够支持broadcast,所以可以看作是map的增强版)numpy.where (这个一定要熟练掌握,筛选数据的时候非常好用!!!)

(5)一起用python之基础篇——numpy

相关文章:

你感兴趣的文章:

标签云: