将2D numpy数组转换为结构化数组
我正在尝试把一个二维数组转换成一个有结构的数组,并且每个字段都有名字。我希望二维数组中的每一行都能变成结构数组中的一条新记录。可惜的是,我尝试的办法都没有达到我想要的效果。
我现在开始的数组是:
>>> myarray = numpy.array([("Hello",2.5,3),("World",3.6,2)])
>>> print myarray
[['Hello' '2.5' '3']
['World' '3.6' '2']]
我想转换成的样子是:
>>> newarray = numpy.array([("Hello",2.5,3),("World",3.6,2)], dtype=[("Col1","S8"),("Col2","f8"),("Col3","i8")])
>>> print newarray
[('Hello', 2.5, 3L) ('World', 3.6000000000000001, 2L)]
我尝试过的方法有:
>>> newarray = myarray.astype([("Col1","S8"),("Col2","f8"),("Col3","i8")])
>>> print newarray
[[('Hello', 0.0, 0L) ('2.5', 0.0, 0L) ('3', 0.0, 0L)]
[('World', 0.0, 0L) ('3.6', 0.0, 0L) ('2', 0.0, 0L)]]
>>> newarray = numpy.array(myarray, dtype=[("Col1","S8"),("Col2","f8"),("Col3","i8")])
>>> print newarray
[[('Hello', 0.0, 0L) ('2.5', 0.0, 0L) ('3', 0.0, 0L)]
[('World', 0.0, 0L) ('3.6', 0.0, 0L) ('2', 0.0, 0L)]]
这两种方法都试图把myarray中的每个条目转换成一个有指定数据类型的记录,所以多出来的零被插入进来了。我搞不清楚怎么才能把每一行转换成一条记录。
我还有另一个尝试:
>>> newarray = myarray.copy()
>>> newarray.dtype = [("Col1","S8"),("Col2","f8"),("Col3","i8")]
>>> print newarray
[[('Hello', 1.7219343871178711e-317, 51L)]
[('World', 1.7543139673493688e-317, 50L)]]
这次没有进行实际的转换。内存中已有的数据只是被重新解释成了新的数据类型。
我开始的数组是从一个文本文件中读取的。因为数据类型事先并不知道,所以在创建时无法设置数据类型。我需要一个高效且优雅的解决方案,这样在各种情况下都能很好地工作,因为我会在很多不同的应用中多次进行这种类型的转换。
谢谢!
5 个回答
我想这就是你想要的。
new_array = np.core.records.fromrecords([("Hello",2.5,3),("World",3.6,2)],
names='Col1,Col2,Col3',
formats='S8,f8,i8')
对吧。
如果数据最开始是一个元组的列表,那么创建一个结构化数组就很简单:
In [228]: alist = [("Hello",2.5,3),("World",3.6,2)]
In [229]: dt = [("Col1","S8"),("Col2","f8"),("Col3","i8")]
In [230]: np.array(alist, dtype=dt)
Out[230]:
array([(b'Hello', 2.5, 3), (b'World', 3.6, 2)],
dtype=[('Col1', 'S8'), ('Col2', '<f8'), ('Col3', '<i8')])
这里的复杂之处在于,这个元组的列表已经变成了一个二维字符串数组:
In [231]: arr = np.array(alist)
In [232]: arr
Out[232]:
array([['Hello', '2.5', '3'],
['World', '3.6', '2']],
dtype='<U5')
我们可以使用大家熟悉的 zip*
方法来“转置”这个数组——实际上我们想要的是双重转置:
In [234]: list(zip(*arr.T))
Out[234]: [('Hello', '2.5', '3'), ('World', '3.6', '2')]
zip
方便地给我们提供了一个元组的列表。现在我们可以用想要的数据类型重新创建数组:
In [235]: np.array(_, dtype=dt)
Out[235]:
array([(b'Hello', 2.5, 3), (b'World', 3.6, 2)],
dtype=[('Col1', 'S8'), ('Col2', '<f8'), ('Col3', '<i8')])
被接受的答案使用了 fromarrays
:
In [236]: np.rec.fromarrays(arr.T, dtype=dt)
Out[236]:
rec.array([(b'Hello', 2.5, 3), (b'World', 3.6, 2)],
dtype=[('Col1', 'S8'), ('Col2', '<f8'), ('Col3', '<i8')])
在内部, fromarrays
采用了一种常见的 recfunctions
方法:创建目标数组,并按字段名称复制值。实际上,它的工作原理是:
In [237]: newarr = np.empty(arr.shape[0], dtype=dt)
In [238]: for n, v in zip(newarr.dtype.names, arr.T):
...: newarr[n] = v
...:
In [239]: newarr
Out[239]:
array([(b'Hello', 2.5, 3), (b'World', 3.6, 2)],
dtype=[('Col1', 'S8'), ('Col2', '<f8'), ('Col3', '<i8')])
你可以使用 numpy.core.records.fromarrays 来“从一个(扁平的)数组列表创建一个记录数组”,方法如下:
>>> import numpy as np
>>> myarray = np.array([("Hello",2.5,3),("World",3.6,2)])
>>> print myarray
[['Hello' '2.5' '3']
['World' '3.6' '2']]
>>> newrecarray = np.core.records.fromarrays(myarray.transpose(),
names='col1, col2, col3',
formats = 'S8, f8, i8')
>>> print newrecarray
[('Hello', 2.5, 3) ('World', 3.5999999046325684, 2)]
我之前也想做类似的事情。我发现当 numpy 从一个已有的二维数组创建结构化数组时(使用 np.core.records.fromarrays),它把二维数组的每一列(而不是每一行)当作一个记录来处理。所以你需要先把数组转置一下。numpy 这种行为看起来不是很直观,但可能是有它的原因的。