Конвертируйте фрагмент структурированного массива в обычный массив NumPy в NumPy 1.14

Примечание 1: Ни один из ответов на этот вопрос не работает в моем случае.

Примечание 2: решение должно работать в NumPy 1.14.

Предположим, у меня есть следующий структурированный массив:

arr = np.array([(105.0, 34.0, 145.0, 217.0)], dtype=[('a', 'f4'), ('b', 'f4'), ('c', 'f4'), ('d', 'f4')]),

Теперь я врезался в структурированный тип данных примерно так:

arr2 = arr[['a', 'b']]

И теперь я пытаюсь преобразовать этот фрагмент в обычный массив:

out = arr2[0].view((np.float32, 2))

что приводит к

ValueError: Changing the dtype of a 0d array is only supported if the itemsize is unchanged

То, что я хотел бы получить, это просто обычный массив, например, так:

[105.0, 34.0]

Обратите внимание, что этот пример упрощен, чтобы быть минимальным. В моем реальном случае использования я, очевидно, не имею дело с массивом, который содержит один элемент.

Я знаю, что это решение работает:

out = np.asarray(list(arr2[0]))

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

1 ответ

Решение

1d массив конвертирует с view:

In [270]: arr = np.array([(105.0, 34.0, 145.0, 217.0)], dtype=[('a', 'f4'), ('b','f4'), ('c', 'f4'), ('d', 'f4')])
In [271]: arr
Out[271]: 
array([(105., 34., 145., 217.)],
      dtype=[('a', '<f4'), ('b', '<f4'), ('c', '<f4'), ('d', '<f4')])
In [272]: arr.view('<f4')
Out[272]: array([105.,  34., 145., 217.], dtype=float32)

Когда мы пытаемся преобразовать один элемент, мы получаем эту ошибку:

In [273]: arr[0].view('<f4')
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-273-70fbab8f61ba> in <module>()
----> 1 arr[0].view('<f4')

ValueError: Changing the dtype of a 0d array is only supported if the itemsize is unchanged

Ранее view часто требуется настройка в размерах. Я подозреваю, что с недавними изменениями в обработке структурированных массивов (наиболее очевидно при индексировании нескольких полей одновременно), эта ошибка является результатом, преднамеренно или нет.

Во всем случае массива он изменил массив из 1d, 4 полей в массив из 1d, 4 элемента, (1,) в (4,). Но при изменении элемента происходит переход от () к (4,).

В прошлом я рекомендовал tolist как самый верный способ обойти view (а также astype):

In [274]: arr[0].tolist()
Out[274]: (105.0, 34.0, 145.0, 217.0)
In [279]: list(arr[0].tolist())
Out[279]: [105.0, 34.0, 145.0, 217.0]
In [280]: np.array(arr[0].tolist())
Out[280]: array([105.,  34., 145., 217.])

item это также хороший способ вытащить элемент из его структуры numpy:

In [281]: arr[0].item()
Out[281]: (105.0, 34.0, 145.0, 217.0)

Результат от tolost а также item это кортеж

Вы беспокоитесь о скорости. Но вы просто конвертируете один элемент. Одно дело беспокоиться о скорости при использовании tolist на массиве из 1000 элементов, совсем другое при работе с 1 элементом.

In [283]: timeit arr[0]
131 ns ± 1.31 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
In [284]: timeit arr[0].tolist()
1.25 µs ± 11.9 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
In [285]: timeit arr[0].item()
1.27 µs ± 2.39 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
In [286]: timeit arr.tolist()
493 ns ± 17.2 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
In [287]: timeit arr.view('f4')
1.74 µs ± 18.7 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

Вы можете индексировать элемент так, чтобы размер не уменьшался до 0 (не то, чтобы он сильно помогал со скоростью):

In [288]: arr[[0]].view('f4')
Out[288]: array([105.,  34., 145., 217.], dtype=float32)
In [289]: timeit arr[[0]].view('f4')
6.54 µs ± 15.9 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
In [290]: timeit arr[0:1].view('f4')
2.63 µs ± 105 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
In [298]: timeit arr[0][None].view('f4')
4.28 µs ± 160 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

view все еще требует изменения формы; рассмотрим большой массив:

In [299]: arrs = np.repeat(arr, 10000)
In [301]: arrs.view('f4')
Out[301]: array([105.,  34., 145., ...,  34., 145., 217.], dtype=float32)
In [303]: arrs.shape
Out[303]: (10000,)
In [304]: arrs.view('f4').shape
Out[304]: (40000,)

Представление по-прежнему 1d, где, как мы, вероятно, хотели бы получить (10000,4) массив 2d.

Лучшее изменение вида:

In [306]: arrs.view(('f4',4))
Out[306]: 
array([[105.,  34., 145., 217.],
       [105.,  34., 145., 217.],
       [105.,  34., 145., 217.],
       ...,
       [105.,  34., 145., 217.],
       [105.,  34., 145., 217.],
       [105.,  34., 145., 217.]], dtype=float32)
In [307]: _.shape
Out[307]: (10000, 4)

Это работает с массивом из 1 элемента, будь то 1d или 0d:

In [308]: arr.view(('f4',4))
Out[308]: array([[105.,  34., 145., 217.]], dtype=float32)
In [309]: _.shape
Out[309]: (1, 4)
In [310]: arr[0].view(('f4',4))
Out[310]: array([105.,  34., 145., 217.], dtype=float32)
In [311]: _.shape
Out[311]: (4,)

Это было предложено в одном из ответов по вашей ссылке: /questions/46838138/konvertirovat-strukturirovannyij-massiv-v-obyichnyij-massiv-numpy/46838161#46838161

Вопреки вашему комментарию, у меня это работает:

In [312]: arr[0].view((np.float32, len(arr.dtype.names)))
Out[312]: array([105.,  34., 145., 217.], dtype=float32)
In [313]: np.__version__
Out[313]: '1.14.0'

С редактированием:

In [84]: arr = np.array([(105.0, 34.0, 145.0, 217.0)], dtype=[('a', 'f4'), ('b','f4'), ('c', 'f4'), ('d', 'f4')])
In [85]: arr2 = arr[['a', 'b']]
In [86]: arr2
Out[86]: 
array([(105., 34.)],
      dtype={'names':['a','b'], 'formats':['<f4','<f4'], 'offsets':[0,4], 'itemsize':16})

In [87]: arr2.view(('f4',2))
...
ValueError: Changing the dtype to a subarray type is only supported if the total itemsize is unchanged

Обратите внимание, что arr2dtype включает в себя offsets значение. В последней версии NumPy, выбор нескольких полей изменился. Теперь это истинное представление, сохраняющее исходные данные - все это, а не только выбранные поля. Размер предметов неизменен:

In [93]: arr.itemsize
Out[93]: 16
In [94]: arr2.itemsize
Out[94]: 16

arr.view(('f4',4) а также arr2.view(('f4',4)) производить то же самое.

Так что вы не можете view (изменить dtype) частичный набор полей. Вы должны сначала взять view всего массива, а затем выберите строки / столбцы или работать с tolist,

я использую 1.14.0, Примечания к выпуску для 1.14.1 говорит:

Изменение в 1.14.0 о том, что многополевая индексация структурированных массивов возвращает представление вместо копии, было отменено, но остается на пути к NumPy 1.15. Затронутые пользователи должны прочитать раздел "Основы / структурированные массивы / доступ к нескольким полям" в руководстве пользователя Numpy 1.14.1 для получения рекомендаций по управлению этим переходом.

https://docs.scipy.org/doc/numpy-1.14.2/user/basics.rec.html

Это все еще в стадии разработки. Этот документ упоминает repack_fields функция, но это еще не существует.

Другие вопросы по тегам