美文网首页程序员工具癖我爱编程
Python科学计算神器之numpy-4:结构数组

Python科学计算神器之numpy-4:结构数组

作者: Li_Michael | 来源:发表于2017-08-14 11:30 被阅读424次

    一. 为什么需要结构数组

    数据分析过程中,经常会有多种不同数据类型同时出现,而不仅仅是期望的数值型数据,而Array只能含有一种数据类型,Numpy/pandas该如何处理呢?在C语言中经常通过结构体struct来定义不同数据类型形成结构类型,结构中的字段占据连续的内存空间,每个结构体占用的内存大小均相同,类似的Numpy可以很容易的定义结构数组。和C语言一样,在Numpy中也可以操作这些字段对这种结构数组进行操作。只要Numpy的结构和C语言中的定义相同,Numpy就可以很方便地读取C语言的结构数组的二进制数据,转换为Numpy的结构数组。

    例如,我们定义一个结构数组,它的每个元素有name,age和weight字段。在Numpy中可以如下定义:

    >>>import numpy as np
    >>>## 数据类型升级为string,其他类似C语言数据类型转换规则, 不会出现多种数据类型
    >>>np.array([1,2,'a'])  
    array(['1', '2', 'a'], dtype='|S21')
    

    二. 定义结构数组

    现在我们定义一个结构数组,它的每个元素有name,age和weight字段。在Numpy中可以如下定义:

    >>>persontype = np.dtype({
        "names":["name", "height", "weight"],
        "formats":["S32", "i", "f"]
        })
    >>>p = np.array([("Lee", 180, 74.5), ("Zhang", 170, 55)],
                 dtype=persontype)
    

    我们创建一个dtype对象persontype,通过其字典参数描述结构类型的各个字段。字典有两个关键字:names、formats. 每个关键字对应的值都是一个列表, names定义结构中的每个字段名,formats则定义每个字段的数据类型:

    • S32: 32个字节的字符串类型, 由于结构中每个元素的大小必须固定, 因此需要指定字符串的长度
    • i: 32bit的整数类型, 相当于np.int32
    • f: 32bit的单精度浮点数, 相当于np.float32

    然后我们调用array函数创建数组, 通过关键字参数dtype=persontype, 指定所创建的数组的元素类型维结构数组persontype. 可以看到数组p的元素类型:

    >>>p.dtype
    dtype([('name', 'S32'), ('height', '<i4'), ('weight', '<f4')])
    

    这里我们看到了另一种描述结构类型的方法: 一个包含多个组元的列表,其中如 (字段名, 类型描述) 的组元来描述结构数组中的每个字段. 类型描述前面的"|","<",">"描述了字段值的字节顺序:

    • |: 忽视字节顺序
    • <: 低位字节在前, 即大段序
    • >: 高位字节在前, 即小端序

    2.1 四种定义方法

    结构数组有四种定义方式,即表述dtype对象结构的参数方式:

      1. string
      1. tuple
      1. list
      1. dict

    上面例子即为dict定义的dtype结构类型。

    2.1.1 string参数

    dtype类型用一个逗号分割数据类型的string,每个类型对一个的数据采用默认名字‘f0’、‘f1’、‘f2’...,数据类型有四种形式:

    • a) b1, i1, i2, i4, i8, u1, u2, u4, u8, f2, f4, f8, c8, c16, a<n>
      (分别对应 bytes, ints, unsigned ints, floats, complex and
      fixed length strings of specified byte lengths-固定字节长度的字符串)
    • b) int8,...,uint8,...,float16, float32, float64, complex64, complex128 (这里是按位长计算bit sizes)
      此外还有 Numerric/numarray类型(如Float32)和单字符类型(如H代表usigned short ints),但已弃用,就不建议使用了。
    >>>x = np.zeros(3, dtype='3int, float32, (2,3)float64')
    >>>x
    array([([0, 0, 0],  0., [[ 0.,  0.,  0.], [ 0.,  0.,  0.]]),
            ([0, 0, 0],  0., [[ 0.,  0.,  0.], [ 0.,  0.,  0.]]),
            ([0, 0, 0],  0., [[ 0.,  0.,  0.], [ 0.,  0.,  0.]])],
            dtype=[('f0', '<i8', (3,)), ('f1', '<f4'), ('f2', '<f8', (2, 3))])
    

    2.1.2 tuple参数

    元组仅适用于结构的数据和现有的数据类型对应的结构数组,呈元组对出现.

    >>>x = np.zeros(3, dtype=('i4', [('r','u1'), ('g','u1'), ('b','u1'), ('a','u1')]))
    >>>x['r']
    array([0, 0, 0], dtype=uint8)
    

    2.1.3 list参数

    dtype由一组tuple的list定义,每个元组包含2-3个元素:1)数据结构域的名字,2)对应的数据类型,3)数据域的shape(可选).

    >>>x = np.zeros(3, dtype=[('x', 'f4'), ('y', np.float32), ('value', 'f4', (2,2))])
    >>>x
    array([( 0.,  0., [[ 0.,  0.], [ 0.,  0.]]),
           ( 0.,  0., [[ 0.,  0.], [ 0.,  0.]]),
           ( 0.,  0., [[ 0.,  0.], [ 0.,  0.]])],
           dtype=[('x', '<f4'), ('y', '<f4'), ('value', '<f4', (2, 2))])
    

    2.1.4 dict参数

    有两种不同的形式,一种字典是必须含'names'和'formats'关键字,对应一个相同长度的list,其中'names'必须是字符串. 另有两个可选关键字'offsets'和'titles', 'offsets'对应的list是每个数据域字节为单位偏移量, 'titles'对应的list是数据域元数据的对象. 开始的例子便是此种类型.

    >>>x1 = np.zeros(3, dtype={'names':['col1', 'col2'], 'formats':['i4', 'f4']})
    >>>x1
    array([(0,  0.), (0,  0.), (0,  0.)],
          dtype=[('col1', '<i4'), ('col2', '<f4')])
    

    另一种字典是以数据域name做keys, 对应的value为含有(type,offset,title)的tuple, 其中title可选. 由于dict是没有顺序的, 每个数据域的顺序需要在定义时给出, 这个即是offset. 如height字段的偏移量为25个字节:

    >>>np.dtype({"name":("S25", 0), "age":(np.uint8, 25)})
    dtype([('name', 'S25'), ('age', 'u1')])
    
    >>>x1 = np.zeros(3, dtype={'col1':('i1',0,'title 1'), 'col2':('f4',1,'title 2')})
    >>>x1
    array([(0,  0.), (0,  0.), (0,  0.)],
        dtype=[(('title 1', 'col1'), 'i1'), (('title 2', 'col2'), '<f4')])
    

    三. 混合结构数组

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

    >>>np.dtype([("f1", [("f2", np.int16)])])
    dtype([('f1', [('f2', '<i2')])])
    

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

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

    四. 结构数组的存取

    结构数组的存取和一般array相同, 通过下标即可, 需要注意的是元素的值虽然看着像组元, 但实际上是个结构数组:

    >>>>p[0], p[0].dtype.names
    (('Lee', 180,  74.5), ('name', 'height', 'weight'))
    
    >>>p[0].dtype
    dtype([('name', 'S32'), ('height', '<i4'), ('weight', '<f4')])
    

    p[0]是一个结构元素, 它和a共享内存数据, 因而可以通过修改元素字段来改变原始结构数组中的对应字段:

    >>>c = p[1]
    >>>c["name"] = "Li"
    >>>p[1].dtype
    dtype([('name', 'S32'), ('height', '<i4'), ('weight', '<f4')])
    

    结构数组可以像dict一样通过下标获取对应的字段值:

    >>>b = p[:]["height"]
    >>>b
    array([180, 170], dtype=int32)
    
    >>>b[0] = 200
    >>>p[0]["height"]
    200
    

    同样的, 结构数组也可以调用tostring()和tofile()方法, 直接输出数组p的二进制形式:

    p.tofile("test.bin")
    

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

    C语言的结构体为了内存寻址方便, 会自动对较短字段填充一些字节, 称为内存对齐. 如果把下面的name[32]改为name[30], 由于内存对齐的原因, 在name和height中间会填补两个字节, 结构体大小并不改变. 因此如果numpy中锁配置的内存大小不符合C语言的对齐规范, 将会出现数据错位. 为了解决这个问题, 在创建dtype对象时, 可以传递参数align=True, 这样numpy的结构数组就和C语言的结构体的内存对齐是一致的.

    #include <stdio.h>
    
    struct person{
        char name[32];
        int height;
        float weight;
    };
    
    struct person p[2];
    
    void main(){
        FILE *fp;
        int i;
        fp = fopen("test.bin", "rb");
        fread(p, sizeof(struct person), 2, fp);
        fclose(fp);
        for(i=0; i<2; i++)
            printf("%s %d %f\n", p[i].name, p[i].weight);
        getchar();
    }
    

    虽然上面罗嗦了这么多, 然而并没什么卵用, 对这类数据, 我们当然是交给将Numpy封装的更好的Pandas来处理了_

    Numpy系列

    相关文章

      网友评论

        本文标题:Python科学计算神器之numpy-4:结构数组

        本文链接:https://www.haomeiwen.com/subject/ipxurxtx.html