numpy:如何向现有的结构化数组添加列?
我有一个起始数组,内容如下:
[(1, [-112.01268501699997, 40.64249414272372])
(2, [-111.86145708699996, 40.4945008710162])]
这个数组的第一列是一个整数(int
),第二列是一个浮点数的列表(list
of floats
)。我需要添加一个名为 'USNG'
的字符串列(str
)。
接着,我创建了一个结构化的 numpy 数组,像这样:
dtype = numpy.dtype([('USNG', '|S100')])
x = numpy.empty(array.shape, dtype=dtype)
我想把这个 x
的 numpy 数组作为新的一列添加到现有的数组中,这样我就可以为每一行输出一些信息到这一列。
当我这样做的时候:
numpy.append(array, x, axis=1)
我遇到了以下错误:
'TypeError: invalid type promotion'
7 个回答
问题是:“有没有人能建议一下为什么会发生这种情况?”
从根本上说,这其实是一个bug——这个问题自2012年以来就在numpy上没有解决了。
- 如果可以使用 pandas,那么给
recarray
添加一列会简单很多。- 而且,数据会以一种容易分析的形式呈现。
- numpy 是 pandas 的一个依赖库,它能让很多操作变得更简单。
- 你也可以看看 如何给 numpy recarray 添加一列,这也是一个例子。
- 用
pandas.DataFrame
或pandas.DataFrame.from_records
来读取当前的recarray
。 - 把新数据列添加到数据框(dataframe)中。
- 用
pandas.DataFrame.to_records
将数据框导出为recarray
。
import pandas as pd
import numpy as np
# current recarray
data = np.rec.array([(1, list([-112.01268501699997, 40.64249414272372])), (2, list([-111.86145708699996, 40.4945008710162]))], dtype=[('i', '<i8'), ('loc', 'O')])
# create dataframe
df = pd.DataFrame(data)
# display(df)
i loc
0 1 [-112.01268501699997, 40.64249414272372]
1 2 [-111.86145708699996, 40.4945008710162]
# add new column
df['USNG'] = ['Note 1', 'Note 2']
# display(df)
i loc USNG
0 1 [-112.01268501699997, 40.64249414272372] Note 1
1 2 [-111.86145708699996, 40.4945008710162] Note 2
# write the dataframe to recarray
data = df.to_records(index=False)
print(data)
[out]:
rec.array([(1, list([-112.01268501699997, 40.64249414272372]), 'Note 1'),
(2, list([-111.86145708699996, 40.4945008710162]), 'Note 2')],
dtype=[('i', '<i8'), ('loc', 'O'), ('USNG', 'O')])
在处理超过200万的数组时,我立刻注意到Warren Weckesser的解决方案和Tonsic的方案之间有很大的不同(非常感谢你们俩)
使用
first_array
[out]
array([(1633046400299000, 1.34707, 1.34748),
(1633046400309000, 1.347 , 1.34748),
(1633046400923000, 1.347 , 1.34749), ...,
(1635551693846000, 1.36931, 1.36958),
(1635551693954000, 1.36925, 1.36952),
(1635551697902000, 1.3692 , 1.36947)],
dtype=[('timestamp', '<i8'), ('bid', '<f8'), ('ask', '<f8')])
和
second_array
[out]
array([('2021-10-01T00:00:00.299000',), ('2021-10-01T00:00:00.309000',),
('2021-10-01T00:00:00.923000',), ...,
('2021-10-29T23:54:53.846000',), ('2021-10-29T23:54:53.954000',),
('2021-10-29T23:54:57.902000',)], dtype=[('date_time', '<M8[us]')])
我得到的结果是
%timeit rfn.merge_arrays((first_array, second_array), flatten=True)
[out]
13.8 s ± 1.11 s per loop (mean ± std. dev. of 7 runs, 1 loop each)
而且
%timeit rfn.append_fields(first_array, 'date_time', second_array, dtypes='M8[us]').data
[out]
2.12 s ± 146 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
效果更好(注意最后的.data
,这样可以避免得到mask
和fill_value
)
而使用类似于
def building_new(first_array, other_array):
new_array = np.zeros(
first_array.size,
dtype=[('timestamp', '<i8'), ('bid', '<f8'), ('ask', '<f8'), ('date_time', '<M8[us]')])
new_array[['timestamp', 'bid', 'ask']] = first_array[['timestamp', 'bid', 'ask']]
new_array['date_time'] = other_array
return new_array
(注意在结构化数组中,每一行都是一个元组,所以大小处理得很好)
我得到的结果是
%timeit building_new(first_array, second_array)
[out]
67.2 ms ± 3.56 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
这三者的输出是一样的
[out]
array([(1633046400299000, 1.34707, 1.34748, '2021-10-01T00:00:00.299000'),
(1633046400309000, 1.347 , 1.34748, '2021-10-01T00:00:00.309000'),
(1633046400923000, 1.347 , 1.34749, '2021-10-01T00:00:00.923000'),
...,
(1635551693846000, 1.36931, 1.36958, '2021-10-29T23:54:53.846000'),
(1635551693954000, 1.36925, 1.36952, '2021-10-29T23:54:53.954000'),
(1635551697902000, 1.3692 , 1.36947, '2021-10-29T23:54:57.902000')],
dtype=[('timestamp', '<i8'), ('bid', '<f8'), ('ask', '<f8'), ('date_time', '<M8[us]')])
最后想说一句:
创建新数组而不是使用recfunctions时,第二个数组甚至不需要是结构化的
third_array
[out]
array(['2021-10-01T00:00:00.299000', '2021-10-01T00:00:00.309000',
'2021-10-01T00:00:00.923000', ..., '2021-10-29T23:54:53.846000',
'2021-10-29T23:54:53.954000', '2021-10-29T23:54:57.902000'],
dtype='datetime64[us]')
%timeit building_new(first_array, third_array)
[out]
67 ms ± 1.58 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
你试过用numpy的recfunctions吗?
import numpy.lib.recfunctions as rfn
这个工具有一些非常实用的函数,专门用来处理结构化数组。
对于你的情况,我觉得可以用下面的方法来实现:
a = rfn.append_fields(a, 'USNG', np.empty(a.shape[0], dtype='|S100'), dtypes='|S100')
我在这里测试过,确实有效。
合并数组
正如GMSL在评论中提到的,可以使用rfn.merge_arrays来做到这一点,像下面这样:
a = np.array([(1, [-112.01268501699997, 40.64249414272372]),
(2, [-111.86145708699996, 40.4945008710162])],
dtype=[('i', '<i8'), ('loc', '<f8', (2,))])
a2 = np.full(a.shape[0], '', dtype=[('USNG', '|S100')])
a3 = rfn.merge_arrays((a, a2), flatten=True)
a3的值将会是:
array([(1, [-112.01268502, 40.64249414], b''),
(2, [-111.86145709, 40.49450087], b'')],
dtype=[('i', '<i8'), ('loc', '<f8', (2,)), ('USNG', 'S100')])
你需要创建一个新的数据类型,这个数据类型里要包含新的字段。
比如,这里有一个叫 a
的例子:
In [86]: a
Out[86]:
array([(1, [-112.01268501699997, 40.64249414272372]),
(2, [-111.86145708699996, 40.4945008710162])],
dtype=[('i', '<i8'), ('loc', '<f8', (2,))])
a.dtype.descr
的内容是 [('i', '<i8'), ('loc', '<f8', (2,))]
;也就是说,这是一个字段类型的列表。我们要通过在这个列表的末尾添加 ('USNG', 'S100')
来创建一个新的数据类型:
In [87]: new_dt = np.dtype(a.dtype.descr + [('USNG', 'S100')])
接下来,创建一个新的结构化数组 b
。我这里用的是 zeros
,所以字符串字段一开始的值会是 ''
。你也可以用 empty
,这样字符串里会有一些垃圾值,但如果你马上给它们赋值,那就没关系了。
In [88]: b = np.zeros(a.shape, dtype=new_dt)
把现有的数据从 a
复制到 b
:
In [89]: b['i'] = a['i']
In [90]: b['loc'] = a['loc']
现在 b
的样子是这样的:
In [91]: b
Out[91]:
array([(1, [-112.01268501699997, 40.64249414272372], ''),
(2, [-111.86145708699996, 40.4945008710162], '')],
dtype=[('i', '<i8'), ('loc', '<f8', (2,)), ('USNG', 'S100')])
给新字段填入一些数据:
In [93]: b['USNG'] = ['FOO', 'BAR']
In [94]: b
Out[94]:
array([(1, [-112.01268501699997, 40.64249414272372], 'FOO'),
(2, [-111.86145708699996, 40.4945008710162], 'BAR')],
dtype=[('i', '<i8'), ('loc', '<f8', (2,)), ('USNG', 'S100')])