NumPy-快速处理数据--ndarray对象--多维数组的存取、结构体数组存取、内存对齐、Numpy内存结构

本文摘自《用Python做科学计算》,版权归原作者所有。

上一篇讲到:NumPy-快速处理数据--ndarray对象--数组的创建和存取

接下来接着介绍多维数组的存取、结构体数组存取、内存对齐、Numpy内存结构

一、多维数组的存取

多维数组的存取和一维数组类似,因为多维数组有多个轴,因此它的下标需要用多个值来表示,NumPy采用组元(tuple)作为数组的下标。如二维数组需要(x, y)的元组标记一个数组元素;三维数组需要(x, y, z)的元组标记一个元素。

如下图所示,a为一个6x6的二维数组,图中用颜色区分了各个下标以及其对应的选择区域。

 1 >>> a
 2 array([[ 0,  1,  2,  3,  4,  5],
 3        [10, 11, 12, 13, 14, 15],
 4        [20, 21, 22, 23, 24, 25],
 5        [30, 31, 32, 33, 34, 35],
 6        [40, 41, 42, 43, 44, 45],
 7        [50, 51, 52, 53, 54, 55]])
 8 >>> a[0, 3:5]#黄色部分
 9 array([3, 4])
10 >>> a[4:, 4:]#蓝色部分
11 array([[44, 45],
12        [54, 55]])
13 >>> a[:, 2]#棕色部分
14 array([ 2, 12, 22, 32, 42, 52])
15 >>> a[2::2, ::2]#青色部分
16 array([[20, 22, 24],
17        [40, 42, 44]])

如何创建这个6×6的二维数组?

数组a实际上是一个加法表,纵轴的值为0, 10, 20, 30, 40, 50;横轴的值为0, 1, 2, 3, 4, 5。纵轴的每个元素都和横轴的每个元素求和,就得到图中所示的数组a。你可以用下面的语句创建它:

1 np.arange(0, 60, 10).reshape(-1, 1) + np.arange(0, 6)

多维数组同样也可以使用整数序列和布尔数组进行存取。

 1 >>> a[(0, 1, 2, 3, 4), (1, 2, 3, 4, 5)]#取出坐标为(0, 1), (1, 2), (2, 3), (3, 4), (4, 5)的元素
 2 array([ 1, 12, 23, 34, 45])
 3 >>> a[3:, [0, 2, 5]]#取出第3, 4, 5行,第0, 2, 5列的元素
 4 array([[30, 32, 35],
 5        [40, 42, 45],
 6        [50, 52, 55]])
 7 >>> mask = np.array([1, 0, 1, 0, 0, 1], dtype=np.bool)#先制造出一个布尔数组
 8 >>> mask
 9 array([ True, False,  True, False, False,  True], dtype=bool)
10 >>> a[mask, 2]      #取出第二列下标为True的元素
11 array([ 2, 22, 52])

二、结构体数组

类似C语言中的结构体数组,在NumPy中也很容易对这种结构数组进行操作。只要NumPy中的结构定义和C语言中的定义相同,NumPy就可以很方便地读取C语言的结构数组的二进制数据,转换为NumPy的结构数组。

 1 >>> personType = np.dtype({'names':['name', 'age', 'weight'],\
 2                          'formats':['S32', 'i', 'f']})#创建personType数据类型
 3 #字典有两个关键字:names,formats。每个关键字对应的值都是一个列表。
 4 #names定义结构中的每个字段名,而formats则定义每个字段的类型:
 5 #    S32 : 32个字节的字符串类型
 6 #    i   :32bit的整数类型,相当于np.int32 
 7 #    f   :32bit的单精度浮点数类型,相当于np.float32 
 8 >>> personType
 9 dtype([('name', 'S32'), ('age', '<i4'), ('weight', '<f4')])
10 #描述结构类型的方法: 一个包含多个组元的列表,其中形如 (字段名, 类型描述) 
11 #的组元描述了结构中的每个字段。类型描述前面的 '|', '<' 等字符用来描述字段
12 #值的字节顺序:
13 #  | :忽视字节顺序
14 #  < : 低位字节在前
15 #  > : 高位字节在前
16 >>> a = np.array([('zhang', 32, 75.5), ('wang', 24, 65.2)], dtype=personType )
17 >>> a[0]        #读取结构体数组的第0个结构元素
18 ('zhang', 32, 75.5)
19 >>> a[1]        #读取结构体数组的第1个结构元素
20 ('wang', 24, 65.19999694824219)
21 >>> a[0].dtype
22 dtype([('name', 'S32'), ('age', '<i4'), ('weight', '<f4')])
23 >>> c = a[1]    #c和a[1]共享同一块内存
24 >>> c['name'] = 'li'#修改c的字段
25 >>> a[1]['name']#则a[1]相应的字段也被修改
26 'li'
27 >>> a[0]['name']#读取a[0]字段的name成员
28 'zhang'
29 >>> b = a[:]['age']#或者 a['age']
30 >>> b
31 array([32, 24])
32 >>> b[0] = 40   #通过b[0]修改a[0]['age']
33 >>> a[0]['age']
34 40
35 >>> a.tofile('d:\\test.bin')
36 #调用a.tostring或者a.tofile方法,可以直接输出数组a的二进制形式

利用下面的C语言程序可以将test.bin文件中的数据读取出来。

三、内存对齐

C语言的结构体为了内存寻址方便,会自动的添加一些填充用的字节,这叫做内存对齐。内存对齐与操作系统以及编译器有关。例如如果把下面的name[32]改为name[30]的话,由于内存对齐问题,在name和age中间会填补两个字节,最终的结构体大小不会改变。因此如果numpy中的所配置的内存大小不符合C语言的对齐规范的话,将会出现数据错位。为了解决这个问题,在创建dtype对象时,可以传递参数align=True,这样numpy的结构数组的内存对齐和C语言的结构体就一致了。

 1 #include <stdio.h>
 2 
 3 struct person
 4 {
 5     char name[32];
 6     int age;
 7     float weight;
 8 };//创建结构体数据类型
 9 
10 struct person p[2];//定义长度为2的一维结构体数组
11 
12 int main (void)
13 {
14     FILE *fp;
15     int i;
16     fp=fopen("d:\\test.bin","rb");//以二进制只读方式打开文件
17     fread(p, sizeof(struct person), 2, fp);//读取的内容放在结构体数组p[2]中
18     fclose(fp);
19     for(i=0;i<2;i++)
20         printf("%s %d %f\n", p[i].name, p[i].age, p[i].weight);
21     getchar();
22     return 0;
23 }
24 /*
25 在VC++6.0输出结果是:
26 ----------------------------
27 zhang 40 75.500000
28 li 24 65.199997
29 
30 Press any key to continue
31 ----------------------------
32 */

结构类型中可以包括其它的结构类型,下面的语句创建一个有一个字段f1的结构,f1的值是另外一个结构,它有字段f2,其类型为16bit整数。

1 >>> np.dtype([('f1', [('f2', np.int16)])])#结构体套结构体
2 dtype([('f1', [('f2', '<i2')])])
3 #用dtype([ ])来定义结构体,[('f2', np.int16)]是一个结构体
4 #('f1', [('f2', np.int16)])是一个元组
5 #最外层用dtype([ ])再定义一层结构体

当某个字段类型为数组时,用组元的第三个参数表示,下面描述的f1字段是一个shape为(2,3)的双精度浮点数组:

1 >>> np.dtype([('f0', 'i4'), ('f1', 'f8', (2, 3))])
2 dtype([('f0', '<i4'), ('f1', '<f8', (2, 3))])

用下面的字典参数也可以定义结构类型,字典的关键字为结构中字段名,值为字段的类型描述,但是由于字典的关键字是没有顺序的,因此字段的顺序需要在类型描述中给出,类型描述是一个组元,它的第二个值给出字段的字节为单位的偏移量,例如age字段的偏移量为25个字节:

1 >>> np.dtype({'surname':('S25',0),'age':(np.uint8,25)})
2 dtype([('surname', 'S25'), ('age', 'u1')])

四、Numpy内存结构

下面让我们来看看ndarray数组对象是如何在内存中储存的。

dtype对象则知道如何将元素的二进制数据转换为可用的值。如上图每32位表示一个有用数据

dim count表示数组维数,上图为2维数组

dimmension 3×3给出数组的shape

strides中保存的是当每个轴的下标增加1时,数据存储区中的指针所增加的字节数。例如图中的strides为12,4,即第0轴的下标增加1时,数据的地址增加12个字节:即a[1,0]的地址比a[0,0]的地址要高12个字节,正好是3个单精度浮点数的总字节数;第1轴下标增加1时,数据的地址增加4个字节,正好是单精度浮点数的字节数。

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

如果strides中的数值正好和对应轴所占据的字节数相同的话,那么数据在内存中是连续存储的。然而数据并不一定都是连续储存的,前面介绍过通过下标范围得到新的数组是原始数组的视图,即它和原始视图共享数据存储区域:

1 >>> b = a[::2,::2]
2 >>> b
3 array([[ 0.,  2.],
4        [ 6.,  8.]], dtype=float32)
5 >>> b.strides
6 (24, 8)

由于数组b和数组a共享数据存储区,而b中的第0轴和第1轴都是数组a中隔一个元素取一个,因此数组b的strides变成了24,8,正好都是数组a的两倍。 对照前面的图很容易看出数据0和2的地址相差8个字节,而0和6的地址相差24个字节。

元素在数据存储区中的排列格式有两种:C语言格式和Fortan语言格式。在C语言中,多维数组的第0轴是最上位的,即第0轴的下标增加1时,元素的地址增加的字节数最多;而Fortan语言的多维数组的第0轴是最下位的,即第0轴的下标增加1时,地址只增加一个元素的字节数。在NumPy中,元素在内存中的排列缺省是以C语言格式存储的,如果你希望改为Fortan格式的话,只需要给数组传递order="F"参数:

1 >>> c = np.array([[0,1,2],[3,4,5],[6,7,8]], dtype=np.float32, order="F")
2 >>> c.strides
3 (4, 12)

 

posted @ 2015-11-08 01:33  moon1992  阅读(12612)  评论(0编辑  收藏  举报