在稳定的NumPy中复制子重组

时间:2018-04-30 14:57:26

标签: python-3.x numpy recarray

假设我有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']]

但是,它从NumPy中提出了以下FutureWarning

  

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

     

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

     

return obj.view(dtype =(self.dtype.type,obj.dtype))

这个警告非常受欢迎,因为我不想在更新NumPy时进行重大更改。但是,即使阅读发行说明,也不清楚在今天提取列的同时编写实现此复制行为的内容的最佳解决方案是什么,并且通过即将发布的版本将保持稳定。

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

2 个答案:

答案 0 :(得分: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')])

但是,如果不深入研究开发讨论,我就无法说出长期解决方案的内容。理想情况下,它应该具有获取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,简单的字段名称迭代就足够了。

通常,结构化数组(和重新排列)具有许多记录和有限数量的字段。因此,按名称复制字段相对有效。

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函数,但显然从未实际添加过。

答案 1 :(得分: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()

请注意,像这样调用副本会耗尽不必要的内存(完整数组的内存)