Скопируйте субрепарат в стабильном NumPy - PullRequest
0 голосов
/ 30 апреля 2018

Предположим, у меня есть данные в 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, потому что он вернет представление вместо копии - см. Примечания к выпуску.

вернуть obj.view (dtype = (self.dtype.type, obj.dtype))

Это предупреждение очень приветствуется, потому что я не хочу иметь критических изменений при обновлении NumPy. Однако, даже просматривая примечания к выпуску, неясно, что является лучшим решением для написания чего-либо, которое реализует такое поведение при копировании при извлечении столбцов на сегодняшний день и которое будет стабильным в следующих выпусках.

В моем конкретном случае требуется почти оптимальная эффективность, а Pandas недоступен. В этих условиях, что было бы лучшим обходным путем для этой ситуации?

Ответы [ 2 ]

0 голосов
/ 30 апреля 2018

Вы можете проверить, является ли результат представлением с

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()

Обратите внимание, что при таком вызове копии используется ненужная память (полная память)

0 голосов
/ 30 апреля 2018

Как уже отмечалось, выбор мультиполя находится в состоянии изменения. Недавно я обновился до 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')])

или другая функция повторного вызова:

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, но она, по-видимому, фактически никогда не добавлялась.

...