numpy:如何向现有的结构化数组添加列?

16 投票
7 回答
14360 浏览
提问于 2025-04-18 18:03

我有一个起始数组,内容如下:

[(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'

我还尝试过 vstackhstack

7 个回答

1

问题是:“有没有人能建议一下为什么会发生这种情况?”

从根本上说,这其实是一个bug——这个问题自2012年以来就在numpy上没有解决了。

2
  • 如果可以使用 pandas,那么给 recarray 添加一列会简单很多。
  1. pandas.DataFramepandas.DataFrame.from_records 来读取当前的 recarray
  2. 把新数据列添加到数据框(dataframe)中。
  3. 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')])
3

在处理超过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,这样可以避免得到maskfill_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)
6

你试过用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')])
17

你需要创建一个新的数据类型,这个数据类型里要包含新的字段。

比如,这里有一个叫 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')])

撰写回答