python科学计算之narray对象

来源:互联网 发布:安徒生 知乎 编辑:程序博客网 时间:2024/06/05 15:10

写在前面

最近在系统的看一些python科学计算开源包的内容,虽然以前是知道一些的,但都属于零零碎碎的,希望这次能把常用的一些函数、注意项整理下。小白的一些废话,高手请略过^ _ ^。文章中的函数仅仅是为了自己好理解,并没有按照官方文档上的函数声明形式记录。

numpy.narray

numpy.narray创建

numpy.narray的构造方式挺多的,这里就不一一说明,因为一般情况下,在进行科学计算时是通过给定的数据文件来读取的,而读取时使用的是pandas,具体可参考官方文档,或者参见这位大牛的文章。

numpy.narray.shape & numpy.narray.reshape

numpy.narray的形状可以用numpy.narray.shape属性来获取,它返回的是narray形状的tuple,反映了各个轴的长度。

na_a = np.array([1,2,3,4])  #长度为4的一维数组na_b = np.array([[1,2,3,4],[5,6,7,8]]) #24列的二维数组out:#print na_a[1 2 3 4]  #print na_b[[1 2 3 4]  [5 6 7 8]] #print na_a.shape(4,)#print na_b.shape   (2, 4) 

numpy.narray.shape相当于一个c++的public成员,不仅可以获取,还可以通过它改变narray的形状。当某一轴指定为-1时,该轴的长度将通过其他轴来计算。

na_b.shape = (4,2)na_a.shape = (2,-1)out:#print na_b[[1 2] [3 4] [5 6] [7 8]]#print na_a [[1 2] [3 4]]

需要注意的是:通过shape属性改变narray的形状时,并没对narray元素内存中的位置改变,只是改变了外观,仅仅是换了件马甲。

当然,如果你熟悉了使用函数,不想使用属性来改变形状时,可以通过narray.reshape(),注意的是该函数返回的结果和原narray共享内存空间,换句话说,一个narray对象的改变将导致另一个narray也发生相应的改变。

na_c = na_b.reshape((4,2))na_c[0][0] = 100out:#print na_c[[1 2] [3 4] [5 6] [7 8]]#print na_c[[100   2] [  3   4] [  5   6] [  7   8]]#print na_b[[100   2] [  3   4] [  5   6] [  7   8]]

numpy.narray.dtype

narray元素的类型可以使用dtype属性来获取,当然也可以在构造narray对象时指定元素的类型,需要注意的是,在numpy中float是64位,而complex是128位的。这里的float和complex是python内置的数据类型,numpy也定义了自己的float等数据类型,而且更加的细化,比如float类型,numpy就有float16、float32、float64和float128.

af = np.array([1,2,3,4], dtype=float)ac = np.array([5,6,7,8], dtype=complex)af.dtypeac.dtypedtype('float64')dtype('complex128')

python的完整数据类型中有大部分是所占字节来细化的,完整的数据在typeDict中定义,可通过numpy.typeDict.values()来获取完整类型。numpy在多维数组上的计算很高效,但是在数值对象计算上却比python慢很多,所以在进行数值计算时应避免使用numpy。

numpy.narray.astype() 类型转换

numpy.narray.astype() 可以对narray对象中元素逐一进行类型转换。

a = np.array([1,2,3,4], dtype=np.float)a.dtypedtype('float64')b = a.astype(np.int32)b.dtypedtype('int32')c.dtypec = a.astype(np.complex)c.dtypedtype('complex128')

numpy.narray的自动生成

  1. np.arange(start,end,step,dtype) 指定开始值、终值和步长生成一个等差数列,生成的narray中不包括end值。
  2. np.linspace(start, end, element_nums, endpoint,dtype) 指定开始值、终值和元素个数生成一个等差数列,通过endpoint指定包不包括end,默认为True。
  3. np.logspace(start, end, element_nums, base, endpoint,dtype) endpoint默认为True,base默认为10,该函数产生一个等比数列,元素为basep,p=endstartelement_nums
  4. np.zeros(dim,dtype)对维度为dim的narray对象初始化为0,数据类型为dtype
  5. np.ones(dim, dtype) 对维度为dim的narray对象初始化为1,数据类型为dtype
  6. np.empty(dim,dtype) 该函数相当于对narray对象分配内存空间,但不进行初始化,所以切不可认为产生的元素为0。
  7. np.full(dim, val, dtype) 产生维度为dim的narray对象并且全部初始为val,前面的zeros()和ones()可看作该函数的特殊形式。
  8. 上面函数的like版本,即np.zeros_like(object) 、np.ones_like(object) 、np.empty_like(object) 、np.full_like(object) ,这里的object是narray对象,这些函数产生的narray对象和上面一样,但narray的维度(shape)以及数据类型(dtype)是和传入的参数一样的,例如np.zeros_like(object) ==np.zeros(object.shape, object.dtype)。
  9. fromstring(s, dtype)这个函数是比较又意思的,将字符串转换为一个narray对象, 我们先看看这个函数输出情况。
s="abcdefgh"np.fromstring(s)array([  8.54088322e+194])  ## 这是什么鬼?###  输出一下dtype呢?dtype('float64')

显然,这里是按照float64类型存放的,那如果换一个数据类型呢?

np.fromstring(s,dtype=np.int8)array([ 97,  98,  99, 100, 101, 102, 103, 104], dtype=int8)

这个学过c语言的都应该知道是ASCII编码,也就可以看出,在python中一个字符是占8位,即一个字节的,继续看:

np.fromstring(s,dtype=np.int16)array([25185, 25699, 26213, 26727], dtype=int16)

这里改变数据类型位2个字节,那么应该是两个字符联立一起表示一个int16,25185=98*256+97,高位存放在低字节中,这是小端模式。OK,那如果我现在要存放的是[‘a’,’b’…’h’]这样的narray对象呢?数据类型是什么?应该是‘|s1’

np.fromstring(s,'|S1')array(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'],      dtype='|S1')

frombuffer()也可以创建的narray对象,但此时创建的narray对象和原字符串是内存共享的,学过c++的都知道,string类型是只读类型,无法修改,所以在frombuffer()创建的narray无法做修改,如果要修改,请用fromstring()。
10. fromfunction(fun, dim)通过自定义函数接口来创建narray对象,fun是一个函数名(看作指针),dim是一个创建对象的维度。

def fun(i):    return i%4+1np.fromfunction(fun,(10,))array([ 1.,  2.,  3.,  4.,  1.,  2.,  3.,  4.,  1.,  2.])

可见利用fromfunction()创建narray时,参数i是从0开始自增的,自增的次数是维度的大小给定的,上面给定的维度位(10,),是一个一维数组,i从0自增到9(共10次)。看另外一个例子:

def fun2(i,j):    return (i+1)*(j+1)np.fromfunction(fun2,(9,9))array([[  1.,   2.,   3.,   4.,   5.,   6.,   7.,   8.,   9.],       [  2.,   4.,   6.,   8.,  10.,  12.,  14.,  16.,  18.],       [  3.,   6.,   9.,  12.,  15.,  18.,  21.,  24.,  27.],       [  4.,   8.,  12.,  16.,  20.,  24.,  28.,  32.,  36.],       [  5.,  10.,  15.,  20.,  25.,  30.,  35.,  40.,  45.],       [  6.,  12.,  18.,  24.,  30.,  36.,  42.,  48.,  54.],       [  7.,  14.,  21.,  28.,  35.,  42.,  49.,  56.,  63.],       [  8.,  16.,  24.,  32.,  40.,  48.,  56.,  64.,  72.],       [  9.,  18.,  27.,  36.,  45.,  54.,  63.,  72.,  81.]])

行可以看作j的自增,列可以看作i的自增,所以可以看作是两个for循环。

numpy.narray的元素存取

可以按照python中list存取元素的方式。

a = np.arange(10) #array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])a[5]     #5a[3:5]   #[3,4]a[:5]    #[0,1,2,3,4]a[:-1]   #[0, 1, 2, 3, 4, 5, 6, 7, 8]###  也可以指定步长,通过第三个数指定a[1:-1:2]   #从开始到结束,没隔两个数取一次 array([1, 3, 5, 7])a[5:1:-2]   #反向取   array([5, 3])

注意,通过这种方式取的新narray和原始narray内存共享,所以任何一个改变,另一个也将改变。
除此之外,numpy还提供了一下几个存取方式:
使用list存取,list的每个元素作为下标,这种方式得到的新narray和原始narray不共享内存。

a = np.arange(10)a[[1,1,7,7]]  #array([1, 1, 7, 7])

使用narray提取新数组,narray的元素作为下标,得到的新narray维度和参数narray维度相同。这个有点绕,看个例子吧:

a[np.array([2,1,3,6])]  #array([2, 1, 3, 6])a[np.array([[2,1,3,6],[7,4,6,1]])] ## ## array([[2, 1, 3, 6],       [7, 4, 6, 1]])

使用bool数组提取,提取bool数组中为True的下标元素:

a[np.array([True, False,True,False,False,False,True,False,False,False])]##  array([0, 2, 6])a[np.array([True,False,True,False,False,False,True,False,False,False])]=-1,-2,-3print a  #array([-1,  1, -2,  3,  4,  5, -3,  7,  8,  9])

多维数组

请先看一张图:
这里写图片描述
左边是用切片方式提取右边6*6的narray对象元素。这种提取元素方式和原narray对象内存共享。

这里个人的理解,利用切片方式提取的新narray和原narray是内存共享的,而使用列表、bool数组等方式则不共享,原因很简单,利用切片方式,例如a[3:5],是从第三个元素到第5个元素(不包括),那么是用元素所占字节来移动的,就像c中的指针,这样比较便利。而其他方式是用下标,这些下标往往不连续,如果用指针方式的话则要来回移动,比较麻烦。所以不提供内存共享这种方式。

在这之前我们使用切片都是通过在[]中指定起点、终点以及步长,如果要单独创建切片对象呢?使用slice对象,创建的方式很简单,把[]中的:替换为None。

a = np.arange(0,60,10).reshape(-1,1)+np.arange(6)### 广播,后面会说到array([[ 0,  1,  2,  3,  4,  5],       [10, 11, 12, 13, 14, 15],       [20, 21, 22, 23, 24, 25],       [30, 31, 32, 33, 34, 35],       [40, 41, 42, 43, 44, 45],       [50, 51, 52, 53, 54, 55]])a[::2,2:]## array([[ 2,  3,  4,  5],       [22, 23, 24, 25],       [42, 43, 44, 45]])idx = slice(None,None,2),slice(2,None)a[idx]##array([[ 2,  3,  4,  5],       [22, 23, 24, 25],       [42, 43, 44, 45]])

同时,numpy提供了IndexExpression类的对象s_来方便slice的创建。

np.s_[::2,2:]## (slice(None, None, 2), slice(2, None, None))

结构体数组

numpy提供了和c类似的结构体数组,并且相互之间能够读取,以一个例子来说明:

## 创建persontype类型persontype=np.dtype({    'names':['name','age','weight'],    'formats':['S30','i','f']}, align=True)## 创建结构体数组a=np.array([("zhang",20,55.5), ("wang",24,65.2)], dtype=persontype)

persontype有两个key,names和formats,names是以字典的方式标明结构体所含的字段,formats是对相应字段的数据类型。S30表示长度不超过30个字节的字符串,i相当于np.int32,f相当于np.float32,下面创建结构体数组的时候指定数据类型是我们创建的persontype。

## print aarray([('zhang', 20,  55.5       ), ('wang', 24,  65.19999695)],      dtype={'names':['name','age','weight'], 'formats':['S30','<i4','<f4'], 'offsets':[0,32,36], 'itemsize':40, 'aligned':True})## print a[0]('zhang', 20,  55.5)## a[0].dtypedtype({'names':['name','age','weight'], 'formats':['S30','<i4','<f4'], 'offsets':[0,32,36], 'itemsize':40}, align=True)## a[0]["name"]'zhang'b=a['age']  ## 20  24b[0]=50## print a[0]('zhang', 50,  55.5)

通过tostring()和tofile()可以将结构体数组写入文件,并可以通过c读取,由于c中的结构体存在对齐,所以在python创建结构体数组时也需要使用相应的对齐方式,这一点通过align来指定,True为对齐,False不对齐。

narray内存结构

以二维narray对象为例,我们来看看narray对象在内存中的结构。

a = np.array([[0,1,2],[3,4,5],[6,7,8]], dtype=np.float32)

该二维数组的结构如下图:
这里写图片描述
左边时一个结构体,描述了该narray对象的基本信息,包括数据类型(np.float32)、维度(轴的数量)、每个轴的数据个数、每个轴相邻元素的字节差、最后是存放数据的内存地址。通过切片方式得到的新narray就是在data字段表示的内存上获得原始narray的视图,我们来看看:

b=a[::2,::2]## barray([[ 0.,  2.],       [ 6.,  8.]], dtype=float32)## b.strides(24, 8)

获得的新narray.strides发生了变化,而这个变化也刚好是每个轴相邻元素跨越的字节数。narray的数据在内存上的排列有两种形式,分别是C和FORTRAN格式,上面我们展示的就是C的格式,第0轴是最上位的,也就是说,第0轴作为行,所以第0轴的相邻元素跨越的字节数是最多的(比如这里为3*4=12),而FORTRAN结构与C格式相反,它是第1轴跨越的字节数最多。numpy默认是C格式,如果想改成FORTRAN的格式,只需要在创建narray的时候设置order=‘F’。narray的flags属性描述了存储区的一些属性:

## a.flagsC_CONTIGUOUS : True     #C语言格式F_CONTIGUOUS : False    #Fortran语言格式OWNDATA : True          #数组是否拥有该存储区,如果是一个narray的视图则不拥有WRITEABLE : True        #是否可写 (改变其中数据)ALIGNED : True          # 对齐UPDATEIFCOPY : False

OK,这里引出一个东东,如果要将a进行转置,我们是需要将需要将a顺时针旋转90°,那么还有上面简单的方法?我们先看看a.T.flags:

## a.T.flags C_CONTIGUOUS : False F_CONTIGUOUS : True OWNDATA : False WRITEABLE : True ALIGNED : True UPDATEIFCOPY : False

从这上面可以看到,对于a的转置,numpy是将其内存格式改变了,从C语言格式变成了Fortran的格式,并且转置是a的一个视图。这样的方式比旋转方式更加快速。

原创粉丝点击