在NumPy中使用动态数组

列表对象的内存动态分配

Python的列表对象实际上是一个动态指针数组。当列表中没有空间储存新的元素时,列表会动态地改变其大小,以容纳新的元素。每次改变大小时,它都会预留一部分空间,以降低改变大小的频率。下面的程序可以观察列表的这一行为。

import sys
import pylab as pl

size = []
for i in xrange ( 10000 ):
    size . append ( sys . getsizeof ( size ))

pl . plot ( size , lw = "2" )
pl . show ()

程序的输出如下图所示,图中每个阶梯跳变的位置都表示一次内存分配,而每个阶梯的高度表示额外分配的内存的大小。

在NumPy中使用动态数组

因此由于往列表中添加新元素时,基本上时间复杂度都为O(1),只有在重新分配内存时,时间复杂度才变为O(n)。由于每次额外分配的内存和列表的长度成正比,因此随着列表的增大,重新分配内存的次数会减少,从而整体上append()方法的平均时间复杂度为O(1)。这种动态数组很适合用来做数据采集,然而由于列表中的每个元素都是对象,比较浪费内存,因此用列表做大量数据的采集并不划算。我们希望通过类似NumPy数组的对象采集数据。

NumPy数组的动态分配

NumPy的数组没有这种动态改变大小的功能,numpy.append()函数每次都会重新分配整个数组,并把原来的数组复制到新数组中。下面的程序模拟列表的动态分配,从而实现动态数组:

import numpy as np
class DynamicArray ( object ):
    def __init__ ( self , item_type ):
        self . _data = np . zeros ( 10 , dtype = item_type )
        self . _size = 0

    def get_data ( self ):
        return self . _data [: self . _size ]

    def append ( self , value ):
        if len ( self . _data ) == self . _size :
            self . _data = np . resize ( self . _data , int ( len ( self . _data ) * 1.25 ))
        self . _data [ self . _size ] = value
        self . _size += 1

item_type = np . dtype ({
    "names" :[ "id" , "x" , "y" , "z" ],
    "formats" :[ "i4" , "f8" , "f8" , "f8" ]})

da = DynamicArray ( item_type )

for i in xrange ( 100 ):
    da . append (( i , i * 0.1 , i * 0.2 , i * 0.3 ))

data = da . get_data ()
用array数组采集数据

Python标准库中的array数组也提供了动态分配内存的功能,而且它和NumPy数组一样直接将数值的二进制数据保存在一块内存中,因此我们可以先用array数组收集数组,然后通过np.frombuffer()将array数组的数据内存直接转换为一个NumPy数组。下面是一个例子:

>>> import numpy as np
>>> from array import array
>>> a = array ( "d" , [ 1 , 2 , 3 , 4 ]) # 创建一个array数组
>>> a
array('d', [1.0, 2.0, 3.0, 4.0])
>>> na = np . frombuffer ( a , dtype = np . float ) # 通过np.frombuffer()创建一个和a共享内存的NumPy数组
>>> na
array([ 1., 2., 3., 4.])
>>> na [ 1 ] = 20 # 修改NumPy数组中的第一个元素
>>> a
array('d', [1.0, 20.0, 3.0, 4.0]) # array数组中的第一个元素也同时改变

array数组只支持一维,如果我们需要采集多个频道的数据,可以将这些数据依次添加进array数组,然后通过reshape()方法将np.frombuffer()所创建的NumPy数组改为二维数组。下面是一个例子:

buf = array ( "d" )
for i in range ( 100 ):
    buf . append ( math . sin ( i * 0.1 ))
    buf . append ( math . cos ( i * 0.1 ))

data = np . frombuffer ( buf , dtype = np . float ) . reshape ( - 1 , 2 )
print data

在这个例子中,我们通过array数组buf采集两个频道的数据,数据采集完毕之后,我们通过np.frombuffer()将其转换为NumPy数组,并通过reshape()将其形状改为(100,2)。

用bytearray采集数据

当每个频道的数据类型不同时,就不能采用上节所介绍的方法了。这时我们可以使用bytearray收集数据。bytearray是字节数组,因此我们首先需要通过struct模块将Python的数值转换成其字节表示形式。如果数据来自二进制文件或者硬件,那么我们得到得已经是字节数据,这个步骤可以省略。下面是使用bytearray进行数据采集的例子:

buf = bytearray ()
for i in range ( 100 ):
    buf . extend ( struct . pack ( "=hdd" , i , math . sin ( i * 0.1 ), math . cos ( i * 0.1 )))

dtype = np . dtype ({ "names" :[ "id" , "sin" , "cos" ], "formats" :[ "h" , "d" , "d" ]})
data = np . frombuffer ( buf , dtype = dtype )
print data

采集三个频道的数据,其中频道1是短整型整数,其类型符号为”h”,频道2和3为双精度浮点数,其类型符号为”d”。类型格式字符串中的”=”表示输出得字节数据不进行内存对齐。即一条数据的字节数为2+8+8=16,如果没有”=”,那么一条数据的字节数则为8+8+8=24。

定义一个dtype对象表示一条数据的结构,dtype对象缺省不进行内存对齐,如果采集数据用的bytearray中的数据是内存对齐的话,只需要设置dtype()的align参数为True即可。

最后通过np.frombuffer()将bytearray转换为NumPy的结构数组。然后我们就可以通过data[“id”]、data[“sin”]和data[“cos”]访问三个频道的数据了。

np.frombuffer()还可以从字符串创建数组,数组也和字符串共享数据内存,但由于字符串是不可变对象,因此所创建的数组是只读的。如果不需要修改数据,这种方法比np.fromstring()更快、更节省内存。

猜你喜欢

转载自www.linuxidc.com/Linux/2016-11/136795.htm