2

假设我在 a 中有数据numpy.recarray,并且我想提取其中的一些列。我希望这是一个有效的副本,因为数据可能很大(我不想复制所有内容),但我可能会更改这些功能而不想更改data(我不想要视图)。

今天,我将执行以下操作:

data = np.array([(1.0, 2.0, 0), (3.0, 4.0, 1)], 
            dtype=[('feature_1', float), ('feature_2', float), ('result', int)])
data = data.view(np.recarray)

features = data[['feature_1', 'feature_2']]

然而,它FutureWarning从 NumPy 中提出了以下几点:

/path/to/numpy/core/records.py:513:FutureWarning:Numpy 检测到您可能正在查看或写入通过选择结构化数组中的多个字段返回的数组。

此代码可能会在 numpy 1.15 中中断,因为这将返回视图而不是副本——有关详细信息,请参阅发行说明。

返回 obj.view(dtype=(self.dtype.type, obj.dtype))

这个警告非常受欢迎,因为我不想在更新 NumPy 时进行重大更改。但是,即使浏览了发行说明,也不清楚什么是最好的解决方案来编写实现这种复制行为的东西,同时提取今天的列,并且在即将发布的版本中保持稳定。

在我的特殊情况下,需要接近最佳的效率,而 Pandas 不可用。在这些情况下,这种情况的最佳解决方法是什么?

4

2 回答 2

2

你可以检查结果是否是一个视图

features = data[['feature_1', 'feature_2']]
if np.may_share_memory(features, data):
    features = features.copy()

更脆弱的是检查版本号:

features = data[['feature_1', 'feature_2']]
if np.lib.NumpyVersion(np.__version__) < np.lib.NumpyVersion('1.15.0'):
    features = features.copy()

请注意,像这样调用副本确实会占用不必要的内存(整个数组的内存)

于 2018-04-30T18:09:14.487 回答
2

如前所述,多场选择处于不断变化的状态。我最近更新到 1.14.2,行为又回到了 1.14.0 之前的状态。

In [114]: data = np.array([(1.0, 2.0, 0), (3.0, 4.0, 1)], 
     ...:             dtype=[('feature_1', float), ('feature_2', float), ('resul
     ...: t', int)])
     ...:             
In [115]: data
Out[115]: 
array([(1., 2., 0), (3., 4., 1)],
      dtype=[('feature_1', '<f8'), ('feature_2', '<f8'), ('result', '<i8')])
In [116]: features = data[['feature_1', 'feature_2']]
In [117]: features
Out[117]: 
array([(1., 2.), (3., 4.)],
      dtype=[('feature_1', '<f8'), ('feature_2', '<f8')])

(我省略了额外的recarray转换层。)

在 1.14.0 中,此 dtype 将包含一个offset值,表明这features是一个视图,而不是一个副本。

我可以在features不更改的情况下更改值data

In [124]: features['feature_1']
Out[124]: array([1., 3.])
In [125]: features['feature_1'] = [4,5]
In [126]: features
Out[126]: 
array([(4., 2.), (5., 4.)],
      dtype=[('feature_1', '<f8'), ('feature_2', '<f8')])
In [127]: data
Out[127]: 
array([(1., 2., 0), (3., 4., 1)],
      dtype=[('feature_1', '<f8'), ('feature_2', '<f8'), ('result', '<i8')])

但是如果不深入研究开发讨论,我不能说长期解决方案是什么。理想情况下,它应该具有获取 a view(维护到原始数据缓冲区的链接)和副本的能力,一个独立且可自由修改的数组。

我怀疑该copy版本将遵循recfunctions使用新 dtype 构造新数组,然后逐字段复制数据的做法。

In [132]: data.dtype.descr
Out[132]: [('feature_1', '<f8'), ('feature_2', '<f8'), ('result', '<i8')]
In [133]: dt = data.dtype.descr[:-1]
In [134]: dt
Out[134]: [('feature_1', '<f8'), ('feature_2', '<f8')]
In [135]: arr = np.zeros(data.shape, dtype=dt)
In [136]: arr
Out[136]: 
array([(0., 0.), (0., 0.)],
      dtype=[('feature_1', '<f8'), ('feature_2', '<f8')])
In [137]: for name in arr.dtype.fields:
     ...:     arr[name] = data[name]
     ...:     
In [138]: arr
Out[138]: 
array([(1., 2.), (3., 4.)],
      dtype=[('feature_1', '<f8'), ('feature_2', '<f8')])

或另一个recfunctions函数:

In [159]: rf.drop_fields(data, 'result')
Out[159]: 
array([(1., 2.), (3., 4.)],
      dtype=[('feature_1', '<f8'), ('feature_2', '<f8')])

recfunctions具有可以复制复杂 dtypes 的代码,具有嵌套 dtypes 等的代码。但是对于像这样简单的一层 dtype,简单的字段名称迭代就足够了。

一般来说,结构化数组(和recarray)有很多记录和有限数量的字段。所以按名称复制字段是比较高效的。

In [150]: import numpy.lib.recfunctions as rf
In [154]: arr = np.zeros(data.shape, dtype=dt)
In [155]: rf.recursive_fill_fields(data, arr)
Out[155]: 
array([(1., 2.), (3., 4.)],
      dtype=[('feature_1', '<f8'), ('feature_2', '<f8')])

但请注意它的代码以:

output = np.empty(base.shape, dtype=newdtype)
output = recursive_fill_fields(base, output)

开发说明在某些时候提到了一个recfunctions.compress_fields功能,但显然从未真正添加过。

于 2018-04-30T16:55:56.753 回答