zoukankan      html  css  js  c++  java
  • NumPy之:结构化数组详解

    简介

    普通的数组就是数组中存放了同一类型的对象。而结构化数组是指数组中存放不同对象的格式。

    今天我们来详细探讨一下NumPy中的结构化数组。

    结构化数组中的字段field

    因为结构化数组中包含了不同类型的对象,所以每一个对象类型都被称为一个field。

    每个field都有3部分,分别是:string类型的name,任何有效dtype类型的type,还有一个可选的title

    看一个使用filed构建dtype的例子:

    In [165]: np.dtype([('name', 'U10'), ('age', 'i4'), ('weight', 'f4')])
    Out[165]: dtype([('name', '<U10'), ('age', '<i4'), ('weight', '<f4')])
    

    我们可以使用上面的dtype类型来构建一个新的数组:

    In [166]: x = np.array([('Rex', 9, 81.0), ('Fido', 3, 27.0)],
         ...:     dtype=[('name', 'U10'), ('age', 'i4'), ('weight', 'f4')])
         ...:
    
    In [167]: x
    Out[167]:
    array([('Rex', 9, 81.), ('Fido', 3, 27.)],
          dtype=[('name', '<U10'), ('age', '<i4'), ('weight', '<f4')])
    

    x是一个1维数组,每个元素都包含三个字段,name,age和weight。并且分别指定了他们的数据类型。

    可以通过index来访问一行数据:

    In [168]: x[1]
    Out[168]: ('Fido', 3, 27.)
    

    也可以通过name来访问一列数据 :

    In [170]: x['name']
    Out[170]: array(['Rex', 'Fido'], dtype='<U10')
    

    还可以给所有的列统一赋值:

    In [171]: x['age']
    Out[171]: array([9, 3], dtype=int32)
    
    In [172]: x['age'] = 10
    
    In [173]: x
    Out[173]:
    array([('Rex', 10, 81.), ('Fido', 10, 27.)],
          dtype=[('name', '<U10'), ('age', '<i4'), ('weight', '<f4')])
    

    结构化数据类型

    上面的例子让我们对结构化数据类型有了一个基本的认识。结构化数据类型就是一系列的filed的集合。

    创建结构化数据类型

    结构化数据类型是从基础类型创建的,主要有下面几种方式:

    从元组创建

    每个元组都是(fieldname, datatype, shape)这样的格式,其中shape 是可选的。fieldname 是 field的title。

    In [174]: np.dtype([('x', 'f4'), ('y', np.float32), ('z', 'f4', (2, 2))])
    Out[174]: dtype([('x', '<f4'), ('y', '<f4'), ('z', '<f4', (2, 2))])
    

    如果fieldname是空字符的话,会以f开头的形式默认创建。

    In [177]: np.dtype([('x', 'f4'), ('', 'i4'), ('z', 'i8')])
    Out[177]: dtype([('x', '<f4'), ('f1', '<i4'), ('z', '<i8')])
    

    从逗号分割的dtype创建

    可以选择从逗号分割的dtype类型创建:

    In [178]: np.dtype('i8, f4, S3')
    Out[178]: dtype([('f0', '<i8'), ('f1', '<f4'), ('f2', 'S3')])
    
    In [179]: np.dtype('3int8, float32, (2, 3)float64')
    Out[179]: dtype([('f0', 'i1', (3,)), ('f1', '<f4'), ('f2', '<f8', (2, 3))])
    

    从字典创建

    从字典创建是这样的格式: {'names': ..., 'formats': ..., 'offsets': ..., 'titles': ..., 'itemsize': ...}

    这种写法可以指定name列表和formats列表。

    offsets 指的是每个字段的byte offsets。titles 是字段的title,itemsize 是整个dtype的size。

    In [180]: np.dtype({'names': ['col1', 'col2'], 'formats': ['i4', 'f4']})
    Out[180]: dtype([('col1', '<i4'), ('col2', '<f4')])
    
    In [181]: np.dtype({'names': ['col1', 'col2'],
         ...: ...           'formats': ['i4', 'f4'],
         ...: ...           'offsets': [0, 4],
         ...: ...           'itemsize': 12})
         ...:
    Out[181]: dtype({'names':['col1','col2'], 'formats':['<i4','<f4'], 'offsets':[0,4], 'itemsize':12})
    

    操作结构化数据类型

    可以通过dtype 的 names 和fields 字段来访问结构化数据类型的属性:

    >>> d = np.dtype([('x', 'i8'), ('y', 'f4')])
    >>> d.names
    ('x', 'y')
    
    >>> d.fields
    mappingproxy({'x': (dtype('int64'), 0), 'y': (dtype('float32'), 8)})
    

    Offsets 和Alignment

    对于结构化类型来说,因为一个dtype中包含了多种数据类型,默认情况下这些数据类型是不对齐的。

    我们可以通过下面的例子来看一下各个类型的偏移量:

    >>> def print_offsets(d):
    ...     print("offsets:", [d.fields[name][1] for name in d.names])
    ...     print("itemsize:", d.itemsize)
    >>> print_offsets(np.dtype('u1, u1, i4, u1, i8, u2'))
    offsets: [0, 1, 2, 6, 7, 15]
    itemsize: 17
    

    如果在创建dtype类型的时候,指定了align=True,那么这些类型之间可能会按照C-struct的结构进行对齐。

    对齐的好处就是可以提升处理效率。我们看一个对齐的例子:

    >>> print_offsets(np.dtype('u1, u1, i4, u1, i8, u2', align=True))
    offsets: [0, 1, 4, 8, 16, 24]
    itemsize: 32
    

    Field Titles

    每个Filed除了name之外,还可以包含title。

    有两种方式来指定title,第一种方式:

    In [182]: np.dtype([(('my title', 'name'), 'f4')])
    Out[182]: dtype([(('my title', 'name'), '<f4')])
    

    第二种方式:

    In [183]: np.dtype({'name': ('i4', 0, 'my title')})
    Out[183]: dtype([(('my title', 'name'), '<i4')])
    

    看一下fields的结构:

    In [187]: d.fields
    Out[187]:
    mappingproxy({'my title': (dtype('float32'), 0, 'my title'),
                  'name': (dtype('float32'), 0, 'my title')})
    

    结构化数组

    从结构化数据类型创建结构化数组之后,我们就可以对结构化数组进行操作了。

    赋值

    我们可以从元组中对结构化数组进行赋值:

    >>> x = np.array([(1, 2, 3), (4, 5, 6)], dtype='i8, f4, f8')
    >>> x[1] = (7, 8, 9)
    >>> x
    array([(1, 2., 3.), (7, 8., 9.)],
         dtype=[('f0', '<i8'), ('f1', '<f4'), ('f2', '<f8')])
    

    还可以从标量对结构化数组进行赋值:

    >>> x = np.zeros(2, dtype='i8, f4, ?, S1')
    >>> x[:] = 3
    >>> x
    array([(3, 3., True, b'3'), (3, 3., True, b'3')],
          dtype=[('f0', '<i8'), ('f1', '<f4'), ('f2', '?'), ('f3', 'S1')])
    >>> x[:] = np.arange(2)
    >>> x
    array([(0, 0., False, b'0'), (1, 1., True, b'1')],
          dtype=[('f0', '<i8'), ('f1', '<f4'), ('f2', '?'), ('f3', 'S1')])
    

    结构化数组还可以赋值给非机构化数组,但是前提是结构化数组只有一个filed:

    >>> twofield = np.zeros(2, dtype=[('A', 'i4'), ('B', 'i4')])
    >>> onefield = np.zeros(2, dtype=[('A', 'i4')])
    >>> nostruct = np.zeros(2, dtype='i4')
    >>> nostruct[:] = twofield
    Traceback (most recent call last):
    ...
    TypeError: Cannot cast array data from dtype([('A', '<i4'), ('B', '<i4')]) to dtype('int32') according to the rule 'unsafe'
    

    结构化数组还可以互相赋值:

    >>> a = np.zeros(3, dtype=[('a', 'i8'), ('b', 'f4'), ('c', 'S3')])
    >>> b = np.ones(3, dtype=[('x', 'f4'), ('y', 'S3'), ('z', 'O')])
    >>> b[:] = a
    >>> b
    array([(0., b'0.0', b''), (0., b'0.0', b''), (0., b'0.0', b'')],
          dtype=[('x', '<f4'), ('y', 'S3'), ('z', 'O')])
    

    访问结构化数组

    之前讲到了,可以通过filed的名字来访问和修改一列数据:

    >>> x = np.array([(1, 2), (3, 4)], dtype=[('foo', 'i8'), ('bar', 'f4')])
    >>> x['foo']
    array([1, 3])
    >>> x['foo'] = 10
    >>> x
    array([(10, 2.), (10, 4.)],
          dtype=[('foo', '<i8'), ('bar', '<f4')])
    

    返回的数值是原始数组的一个视图,他们是共享内存空间的,所以修改视图同时也会修改原数据。

    看一个filed是多维数组的情况:

    In [188]: np.zeros((2, 2), dtype=[('a', np.int32), ('b', np.float64, (3, 3))])
    Out[188]:
    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.]]),
            (0, [[0., 0., 0.], [0., 0., 0.], [0., 0., 0.]])]],
          dtype=[('a', '<i4'), ('b', '<f8', (3, 3))])
    

    上面构建了一个2 * 2 的矩阵,这个矩阵中的第一列是int类型,第二列是一个3 * 3 的float矩阵。

    我们可以这样来查看各个列的shape值:

    >>> x = np.zeros((2, 2), dtype=[('a', np.int32), ('b', np.float64, (3, 3))])
    >>> x['a'].shape
    (2, 2)
    >>> x['b'].shape
    (2, 2, 3, 3)
    

    除了单列的访问之外,我们还可以一次访问多列数据:

    >>> a = np.zeros(3, dtype=[('a', 'i4'), ('b', 'i4'), ('c', 'f4')])
    >>> a[['a', 'c']]
    array([(0, 0.), (0, 0.), (0, 0.)],
         dtype={'names':['a','c'], 'formats':['<i4','<f4'], 'offsets':[0,8], 'itemsize':12})
    

    多列同时赋值:

    >>> a[['a', 'c']] = (2, 3)
    >>> a
    array([(2, 0, 3.), (2, 0, 3.), (2, 0, 3.)],
          dtype=[('a', '<i4'), ('b', '<i4'), ('c', '<f4')])
    

    简单的交换列的数据:

    >>> a[['a', 'c']] = a[['c', 'a']]
    

    Record Arrays

    结构化数组只能通过index来访问,很不方便,为此NumPy提供了一个多维数组的子类 numpy.recarray, 然后可以通过属性来访问。

    我们来看几个例子:

    >>> recordarr = np.rec.array([(1, 2., 'Hello'), (2, 3., "World")],
    ...                    dtype=[('foo', 'i4'),('bar', 'f4'), ('baz', 'S10')])
    >>> recordarr.bar
    array([ 2.,  3.], dtype=float32)
    >>> recordarr[1:2]
    rec.array([(2, 3., b'World')],
          dtype=[('foo', '<i4'), ('bar', '<f4'), ('baz', 'S10')])
    >>> recordarr[1:2].foo
    array([2], dtype=int32)
    >>> recordarr.foo[1:2]
    array([2], dtype=int32)
    >>> recordarr[1].baz
    b'World'
    

    recarray返回的结果是一个rec.array。除了使用np.rec.array来创建之外,还可以使用view:

    In [190]: arr = np.array([(1, 2., 'Hello'), (2, 3., "World")],
         ...: ...                dtype=[('foo', 'i4'),('bar', 'f4'), ('baz', 'a10')])
         ...:
    
    In [191]: arr
    Out[191]:
    array([(1, 2., b'Hello'), (2, 3., b'World')],
          dtype=[('foo', '<i4'), ('bar', '<f4'), ('baz', 'S10')])
    
    In [192]: arr.view(dtype=np.dtype((np.record, arr.dtype)),
         ...: ...                      type=np.recarray)
         ...:
    Out[192]:
    rec.array([(1, 2., b'Hello'), (2, 3., b'World')],
              dtype=[('foo', '<i4'), ('bar', '<f4'), ('baz', 'S10')])
    

    如果是rec.array对象,它的dtype类型会被自动转换成为np.record类型:

    In [200]: recordarr.dtype
    Out[200]: dtype((numpy.record, [('foo', '<i4'), ('bar', '<f4'), ('baz', 'S10')]))
    

    想要转换回原始的np.ndarray类型可以这样:

    In [202]: recordarr.view(recordarr.dtype.fields or recordarr.dtype, np.ndarray)
    Out[202]:
    array([(1, 2., b'Hello'), (2, 3., b'World')],
          dtype=[('foo', '<i4'), ('bar', '<f4'), ('baz', 'S10')])
    

    如果通过index或者field来访问rec.array对象的字段,如果字段是结构类型,那么会返回numpy.recarray,如果是非结构类型,则会返回numpy.ndarray:

    >>> recordarr = np.rec.array([('Hello', (1, 2)), ("World", (3, 4))],
    ...                 dtype=[('foo', 'S6'),('bar', [('A', int), ('B', int)])])
    >>> type(recordarr.foo)
    <class 'numpy.ndarray'>
    >>> type(recordarr.bar)
    <class 'numpy.recarray'>
    

    本文已收录于 http://www.flydean.com/05-python-structured-arrays/

    最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!

    欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!

  • 相关阅读:
    真香!PySpark整合Apache Hudi实战
    Apache Hudi又双叕被国内顶级云服务提供商集成了!
    Apache Hudi集成Apache Zeppelin实战
    实战 | 将Apache Hudi数据集写入阿里云OSS
    实战|使用Spark Structured Streaming写入Hudi
    Apache Hudi 设计与架构最强解读
    【Flink】Flink作业调度流程分析
    【Flink】深入理解Flink-On-Yarn模式
    【Flink】Flink 底层RPC框架分析
    【MyBatis】MyBatis自动生成代码之查询爬坑记
  • 原文地址:https://www.cnblogs.com/flydean/p/14733788.html
Copyright © 2011-2022 走看看