结构化数组

简介

Numpy提供了强大的功能来创建结构化数据类型的数组。这些数组允许通过命名字段来操作数据。一个简单的例子将演示它是什么意思:

>>> x = np.array([(1,2.,'Hello'), (2,3.,"World")],
...              dtype=[('foo', 'i4'),('bar', 'f4'), ('baz', 'S10')])
>>> x
array([(1, 2.0, 'Hello'), (2, 3.0, 'World')],
     dtype=[('foo', '>i4'), ('bar', '>f4'), ('baz', '|S10')])

这里我们创建了一个长度为2的一维数组。该数组的每个元素都是一个包含三个项目的结构,一个32位整数,一个32位浮点数和一个长度小于等于10的字符串。如果我们在第二个位置索引这个数组,我们得到第二个结构:

>>> x[1]
(2,3.,"World")

方便的是,可以通过使用命名该字段的字符串进行索引来访问数组的任何字段。

>>> y = x['bar']
>>> y
array([ 2.,  3.], dtype=float32)
>>> y[:] = 2*y
>>> y
array([ 4.,  6.], dtype=float32)
>>> x
array([(1, 4.0, 'Hello'), (2, 6.0, 'World')],
      dtype=[('foo', '>i4'), ('bar', '>f4'), ('baz', '|S10')])

在这些例子中,y是一个简单的float数组,由结构化类型的第二个字段组成。但是,它并不是结构化数组中数据的副本,而是一个视图,即它共享完全相同的内存位置。因此,当我们通过将其值加倍来更新此数组时,结构化数组显示的对应值也加倍。同样,如果更改结构化数组,字段视图也会更改:

>>> x[1] = (-1,-1.,"Master")
>>> x
array([(1, 4.0, 'Hello'), (-1, -1.0, 'Master')],
      dtype=[('foo', '>i4'), ('bar', '>f4'), ('baz', '|S10')])
>>> y
array([ 4., -1.], dtype=float32)

定义结构化数组

通过dtype对象定义一个结构化数组。There are several alternative ways to define the fields of a record. 其中一些变体提供与Numeric,numarray或其他模块的向后兼容性,除此之外不应使用它们。这些将如此注意。使用参数(如提供给dtype函数关键字或dtype对象构造函数本身)通过四种可选方法之一指定记录结构。这个参数必须是下列之一:1)字符串,2)元组,3)列表或4)字典。下面简要介绍其中的每一个。

1)字符串参数。在这种情况下,构造函数需要一个逗号分隔的类型说明符列表,可选地带有额外的形状信息。这些字段被赋予默认名称'f0','f1','f2'等等。类型说明符可以采用4种不同的形式:

a) b1, i1, i2, i4, i8, u1, u2, u4, u8, f2, f4, f8, c8, c16, a<n>
   (representing bytes, ints, unsigned ints, floats, complex and
    fixed length strings of specified byte lengths)
b) int8,...,uint8,...,float16, float32, float64, complex64, complex128
   (this time with bit sizes)
c) older Numeric/numarray type specifications (e.g. Float32).
   Don't use these in new code!
d) Single character type specifiers (e.g H for unsigned short ints).
   Avoid using these unless you must. Details can be found in the
   Numpy book

这些不同的样式可以在同一个字符串中混合使用(但为什么要这样做?)。此外,每种类型说明符都可以用重复号码或形状作为前缀。在这些情况下,创建一个数组元素,即一个记录中的数组。该数组仍然被称为单个字段。一个例子:

>>> x = np.zeros(3, dtype='3int8, 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, 0.0, 0.0]]),
       ([0, 0, 0], 0.0, [[0.0, 0.0, 0.0], [0.0, 0.0, 0.0]])],
      dtype=[('f0', '|i1', 3), ('f1', '>f4'), ('f2', '>f8', (2, 3))])

通过使用字符串来定义记录结构,它排除了能够命名原始定义中的字段。但是,名称可以改变,如下所示。

2)元组参数:适用于记录结构的唯一相关元组情况是结构映射到现有数据类型的时候。这是通过在元组中配对来完成的,现有数据类型与匹配的dtype定义(使用这里描述的任何变体)相匹配。作为一个例子(使用列表使用一个定义,所以见3)以获得进一步的细节):

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

在这种情况下,生成的数组看起来和行为就像一个简单的int32数组,但也具有仅使用int32的一个字节(有点像Fortran等同性)的字段的定义。

3)列表参数:在这种情况下,记录结构是用元组列表定义的。每个元组有2或3个元素指定:1)字段的名称(''是允许的),2)字段的类型,以及3)形状(可选)。例如:

>>> 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, 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))])

4)字典参数:允许两种不同的形式。第一个包含一个带有两个必需键('名称'和'格式')的字典,每个键都有相同大小的值列表。格式列表包含在其他上下文中允许的任何类型/形状说明符。名称必须是字符串。有两个可选键:'偏移'和'标题'。每个字段都必须是相应的匹配列表,其中偏移量包含每个字段的整数偏移量,而标题是包含每个字段的元数据的对象(这些字符不必是字符串),其中值的值是允许的。举个例子:

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

允许的其他字典形式是一个名称键的字典,其中的元组值指定了类型,偏移量和可选标题。

>>> x = np.zeros(3, dtype={'col1':('i1',0,'title 1'), 'col2':('f4',1,'title 2')})
>>> x
array([(0, 0.0), (0, 0.0), (0, 0.0)],
      dtype=[(('title 1', 'col1'), '|i1'), (('title 2', 'col2'), '>f4')])

访问和修改字段名称

字段名称是定义结构的dtype对象的属性。对于最后一个例子:

>>> x.dtype.names
('col1', 'col2')
>>> x.dtype.names = ('x', 'y')
>>> x
array([(0, 0.0), (0, 0.0), (0, 0.0)],
     dtype=[(('title 1', 'x'), '|i1'), (('title 2', 'y'), '>f4')])
>>> x.dtype.names = ('x', 'y', 'z') # wrong number of names
<type 'exceptions.ValueError'>: must replace all names at once with a sequence of length 2

访问字段标题

字段标题提供了一个标准位置来放置字段的相关信息。他们不必是字符串。

>>> x.dtype.fields['x'][2]
'title 1'

一次访问多个字段

您可以使用字段名称列表一次访问多个字段:

>>> x = np.array([(1.5,2.5,(1.0,2.0)),(3.,4.,(4.,5.)),(1.,3.,(2.,6.))],
        dtype=[('x','f4'),('y',np.float32),('value','f4',(2,2))])

请注意,x是使用元组列表创建的。

>>> x[['x','y']]
array([(1.5, 2.5), (3.0, 4.0), (1.0, 3.0)],
     dtype=[('x', '<f4'), ('y', '<f4')])
>>> x[['x','value']]
array([(1.5, [[1.0, 2.0], [1.0, 2.0]]), (3.0, [[4.0, 5.0], [4.0, 5.0]]),
      (1.0, [[2.0, 6.0], [2.0, 6.0]])],
     dtype=[('x', '<f4'), ('value', '<f4', (2, 2))])

这些字段按请求的顺序返回。:

>>> x[['y','x']]
array([(2.5, 1.5), (4.0, 3.0), (3.0, 1.0)],
     dtype=[('y', '<f4'), ('x', '<f4')])

填充结构化数组

结构化数组可以按字段或逐行填充。

>>> arr = np.zeros((5,), dtype=[('var1','f8'),('var2','f8')])
>>> arr['var1'] = np.arange(5)

如果你逐行填写它,它需要一个元组(但不是一个列表或数组!):

>>> arr[0] = (10,20)
>>> arr
array([(10.0, 20.0), (1.0, 0.0), (2.0, 0.0), (3.0, 0.0), (4.0, 0.0)],
     dtype=[('var1', '<f8'), ('var2', '<f8')])

记录数组

为了方便起见,numpy提供了“记录数组”,允许用户通过属性而不是索引来访问结构化数组的字段。记录数组是使用ndarray子类打包的结构化数组,numpy.recarray允许通过数组对象的属性访问字段,记录数组也使用特殊的数据类型numpy.record

创建记录数组的最简单方法是使用numpy.rec.array

>>> 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.0, '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
'World'

numpy.rec.array可以将各种各样的参数转换为记录数组,包括普通的结构化数组:

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

numpy.rec模块为创建记录数组提供了许多其他便利功能,请参阅record array creation routines

可以使用适当的视图获得结构化数组的记录数组表示形式:

>>> arr = np.array([(1,2.,'Hello'),(2,3.,"World")], 
...                dtype=[('foo', 'i4'),('bar', 'f4'), ('baz', 'a10')])
>>> recordarr = arr.view(dtype=dtype((np.record, arr.dtype)), 
...                      type=np.recarray)

为方便起见,查看一个类型为np.recarray的ndarray会自动转换为np.record数据类型,因此dtype可以不在视图中:

>>> recordarr = arr.view(np.recarray)
>>> recordarr.dtype
dtype((numpy.record, [('foo', '<i4'), ('bar', '<f4'), ('baz', 'S10')]))

要回到纯粹的ndarray中,dtype和type都必须重置。下面的观点是这样做的,考虑到记录者不是结构化类型的不寻常情况:

>>> arr2 = recordarr.view(recordarr.dtype.fields or recordarr.dtype, np.ndarray)

如果字段具有结构化类型,则以索引或属性访问的记录数组字段作为记录数组返回,否则返回为普通的ndarray。

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

请注意,如果某个字段的名称与ndarray属性相同,则ndarray属性优先。这些字段将无法通过属性访问,但仍可以通过索引访问。