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

15

我有一个起始数组,例如:

[(1, [-112.01268501699997, 40.64249414272372])
 (2, [-111.86145708699996, 40.4945008710162])]

第一列是一个int,第二列是一个floats列表。我需要添加一个名为'USNG'str列。

然后我创建了一个结构化的NumPy数组,如下所示:

dtype = numpy.dtype([('USNG', '|S100')])
x = numpy.empty(array.shape, dtype=dtype)
我想将numpy数组x作为新列添加到现有的数组中,以便我可以为每一行输出一些信息到该列。当我执行以下操作时:
numpy.append(array, x, axis=1)
我得到以下错误:

在这里输入错误的内容:

'TypeError: invalid type promotion'

我也尝试过使用 vstackhstack

7个回答

16
你需要创建一个包含新字段的新数据类型。
例如,这是代码中的 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')来创建一个新的dtype:

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

一个性能问题,如果你在数组数据更新上做一个for循环,你会向量化这个函数吗? - code base 5000
这取决于您正在进行的更新类型以及您将如何对其进行向量化。最好启动一个新问题,而不是在这些评论中讨论它。 - Warren Weckesser

5

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

1
在这种情况下,recfunction函数“merge_arrays()”会更简单。 - GMSL
1
很棒,之前不知道这个。需要一些摸索才能找到必须使用“flatten”参数才能获得适当的行为 :)。已相应地编辑了答案。 - Tonsic

3

有超过2百万个数组可供使用,我立即注意到了Warren Weckesser的solution和Tonsic的ones之间的巨大差异(非常感谢两位)

使用

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)

谢谢。我希望这个答案能得到更高的投票。它在时间上最有用。字段数量对这些不同方法有什么影响?更多的字段意味着更宽的结构数组。 - undefined

2
  • 如果您使用pandas,那么将列添加到recarray会变得更加容易。
  1. 使用pandas.DataFramepandas.DataFrame.from_records读取当前的recarray
  2. 将新数据列添加到数据框中。
  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')])

1
这是一个实现Warren解决方案的函数:
def happend(x, col_data,col_name:str):
    if not x.dtype.fields:  return None                                     # Not a structured array
    y = np.empty(x.shape, dtype=x.dtype.descr+[(col_name,col_data.dtype)])  # 0) create new structured array
    for name in x.dtype.fields.keys():  y[name] = x[name]                   # 1) copy old array
    y[col_name] = col_data                                                  # 2) copy new column
    return y

y = happend(x, np.arange(x.shape[0]),'idx')  # assuming `x` is a structured array

嗨,迪亚哥。我记不清我们之前是否有过这样的对话,抱歉。在这里,一个严格的编辑准则是,与帖子无关的内容可以被删除。我们有一群松散的策展人和编辑,他们喜欢将帖子改进为技术写作,所以这里的问答材料有点类似于文档或维基材料。 - halfer
因此,政治/宗教/神论的内容通常会被立即删除 - 这是我们的风格(不幸或幸运地!)。但你在个人资料中可以自由添加你喜欢的内容。 - halfer

1
这个问题的确切描述是:“为什么会出现这种情况?”基本上,这是一个错误——自2012年以来一直是numpy的一个未解决问题。

0
Tonsic提到了通过import numpy.lib.recfunctions as rfn导入recfunctions。在这种情况下,适合您的更简单的recfunction函数是rfn.merge_arrays()docs)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接