Добавить и получить доступ к полю типа объекта в простом структурированном массиве - PullRequest
2 голосов
/ 17 апреля 2019

Я использую numpy 1.16.2.

Вкратце, Мне интересно, как добавить поле типа объекта в структурированный массив. Стандартным способом через модуль recfunctions выдается ошибка, и я полагаю, что для этого есть причина. Поэтому мне интересно, есть ли что-то не так с моим обходным путем. Кроме того, я хотел бы понять, почему этот обходной путь необходим и нужно ли проявлять особую осторожность при доступе к вновь созданному массиву.

Теперь вот подробности:

У меня есть простой структурный массив:

import numpy as np
a = np.zeros(3, dtype={'names':['A','B','C'], 'formats':['int','int','float']})
for i in range(len(a)):
    a[i] = i

Я хочу добавить другое поле «test» типа object в массив a. Стандартный способ сделать это - использовать модуль numpy recfunctions:

import numpy.lib.recfunctions as rf
b = rf.append_fields(a, "test", [None]*len(a)) 

Этот код выдает ошибку:

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-38-4a7be4f94686> in <module>
----> 1 rf.append_fields(a, "test", [None]*len(a))

D:\_Programme\Anaconda3\lib\site-packages\numpy\lib\recfunctions.py in append_fields(base, names, data, dtypes, fill_value, usemask, asrecarray)
    718     if dtypes is None:
    719         data = [np.array(a, copy=False, subok=True) for a in data]
--> 720         data = [a.view([(name, a.dtype)]) for (name, a) in zip(names, data)]
    721     else:
    722         if not isinstance(dtypes, (tuple, list)):

D:\_Programme\Anaconda3\lib\site-packages\numpy\lib\recfunctions.py in <listcomp>(.0)
    718     if dtypes is None:
    719         data = [np.array(a, copy=False, subok=True) for a in data]
--> 720         data = [a.view([(name, a.dtype)]) for (name, a) in zip(names, data)]
    721     else:
    722         if not isinstance(dtypes, (tuple, list)):

D:\_Programme\Anaconda3\lib\site-packages\numpy\core\_internal.py in _view_is_safe(oldtype, newtype)
    492 
    493     if newtype.hasobject or oldtype.hasobject:
--> 494         raise TypeError("Cannot change data-type for object array.")
    495     return
    496 

TypeError: Cannot change data-type for object array.

Подобная ошибка обсуждалась здесь , хотя проблема устарела, и я не знаю, действительно ли наблюдаемое мной поведение является ошибкой. Здесь Мне сообщили, что представления структурированных массивов, содержащих общие объекты, не поддерживаются.

Поэтому я создал обходной путь:

b = np.empty(len(a), dtype=a.dtype.descr+[("test", object)])
b[list(a.dtype.names)] = a

Это работает. Тем не менее, у меня есть следующие вопросы:

Вопросы

  • Почему этот обходной путь необходим? Это просто ошибка?
  • Работа с новым массивом b, похоже, ничем не отличается от работы с a. Переменная c = b[["A", "test"]] явно соответствует данным b. Так почему же они сказали бы , что представления в массиве b не поддерживаются? Нужно ли относиться к c с особой осторожностью?

1 Ответ

3 голосов
/ 17 апреля 2019
In [161]: a = np.zeros(3, dtype={'names':['A','B','C'], 'formats':['int','int','
     ...: float']}) 
     ...: for i in range(len(a)): 
     ...:     a[i] = i 
     ...:                                                                       
In [162]: a                                                                     
Out[162]: 
array([(0, 0, 0.), (1, 1, 1.), (2, 2, 2.)],
      dtype=[('A', '<i8'), ('B', '<i8'), ('C', '<f8')])

определить новый тип dtype:

In [164]: a.dtype.descr                                                         
Out[164]: [('A', '<i8'), ('B', '<i8'), ('C', '<f8')]
In [165]: a.dtype.descr+[('test','O')]                                          
Out[165]: [('A', '<i8'), ('B', '<i8'), ('C', '<f8'), ('test', 'O')]
In [166]: dt= a.dtype.descr+[('test','O')]                                      

новый массив нужного размера и dtype:

In [167]: b = np.empty(a.shape, dt)                                             

копировать значения из a в b по имени поля:

In [168]: for name in a.dtype.names: 
     ...:     b[name] = a[name] 
     ...:                                                                       
In [169]: b                                                                     
Out[169]: 
array([(0, 0, 0., None), (1, 1, 1., None), (2, 2, 2., None)],
      dtype=[('A', '<i8'), ('B', '<i8'), ('C', '<f8'), ('test', 'O')])

Многие из функций rf делают это поле копией поля:

rf.recursive_fill_fields(a,b)

rf.append_fields использует это после инициализации массива output.

В более ранних версиях мультипольный индекс создавал копию, поэтому выражения вроде b[list(a.dtype.names)] = a не работали бы.


Я не знаю, стоит ли пытаться выяснить, что делает rf.append_fields. Эти функции несколько устарели и мало используются (обратите внимание на специальный импорт). Поэтому вполне вероятно, что у них есть ошибки или крайние случаи, которые не работают. Функции, которые я исследовал, работают так же, как я продемонстрировал, - создают новый тип d, массив результатов и копируют данные по имени поля.

В последних выпусках произошли изменения в доступе к нескольким полям. В recfunctions появилось несколько новых функций, облегчающих работу со структурированными массивами, таких как repack_fields.

.

https://docs.scipy.org/doc/numpy/user/basics.rec.html#accessing-multiple-fields

Я не знаю, относится ли это к проблеме append_fields. Я также вижу раздел о структурированных массивах с объектами, но я этого не изучал:

https://docs.scipy.org/doc/numpy/user/basics.rec.html#viewing-structured-arrays-containing-objects

В целях предотвращения удушения указателей объектов в полях типа numpy.object, в настоящее время numpy не позволяет просматривать структурированные массивы, содержащие объекты.

Эта строка, по-видимому, относится к использованию метода view. Представления, созданные с помощью индексации полей, будь то одно имя или несколько полей, не затрагиваются.


Ошибка в append_fields исходит из этой операции:

In [183]: data = np.array([None,None,None])                                          
In [184]: data                                                                       
Out[184]: array([None, None, None], dtype=object)
In [185]: data.view([('test',object)])                                               
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-185-c46c4464b53c> in <module>
----> 1 data.view([('test',object)])

/usr/local/lib/python3.6/dist-packages/numpy/core/_internal.py in _view_is_safe(oldtype, newtype)
    492 
    493     if newtype.hasobject or oldtype.hasobject:
--> 494         raise TypeError("Cannot change data-type for object array.")
    495     return
    496 

TypeError: Cannot change data-type for object array.

Нет проблем при создании составного dtype с объектными dtypes:

In [186]: np.array([None,None,None], dtype=[('test',object)])                        
Out[186]: array([(None,), (None,), (None,)], dtype=[('test', 'O')])

Но я не вижу ни одного recfunctions, способного объединить a и data.


view может использоваться для изменения имен полей a:

In [219]: a.view([('AA',int),('BB',int),('cc',float)])                               
Out[219]: 
array([(0, 0, 0.), (1, 1, 1.), (2, 2, 2.)],
      dtype=[('AA', '<i8'), ('BB', '<i8'), ('cc', '<f8')])

, но попытка сделать это для b не удалась по той же причине:

In [220]: b.view([('AA',int),('BB',int),('cc',float),('d',object)])                  
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-220-ab0a6e4dd57f> in <module>
----> 1 b.view([('AA',int),('BB',int),('cc',float),('d',object)])

/usr/local/lib/python3.6/dist-packages/numpy/core/_internal.py in _view_is_safe(oldtype, newtype)
    492 
    493     if newtype.hasobject or oldtype.hasobject:
--> 494         raise TypeError("Cannot change data-type for object array.")
    495     return
    496 

TypeError: Cannot change data-type for object array.

Я начинаю с массива dtype объекта и пытаюсь view с i8 (dtype того же размера), я получаю ту же ошибку. Таким образом, ограничение на view объекта dtype не ограничивается структурированными массивами. Необходимость такого ограничения в случае указателя объекта на i8 имеет смысл. Необходимость такого ограничения в случае встраивания указателя объекта в составной тип d не может быть столь убедительной. Это может быть даже излишним, или просто случай, когда вы играете безопасно и просто.

In [267]: x.dtype                                                                    
Out[267]: dtype('O')
In [268]: x.shape                                                                    
Out[268]: (3,)
In [269]: x.dtype.itemsize                                                           
Out[269]: 8
In [270]: x.view('i8')                                                               
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-270-30c78b13cd10> in <module>
----> 1 x.view('i8')

/usr/local/lib/python3.6/dist-packages/numpy/core/_internal.py in _view_is_safe(oldtype, newtype)
    492 
    493     if newtype.hasobject or oldtype.hasobject:
--> 494         raise TypeError("Cannot change data-type for object array.")
    495     return
    496 

TypeError: Cannot change data-type for object array.

Обратите внимание, что тест в строке 493 проверяет свойство hasobject как нового, так и старого dtypes. Более детальный тест может проверить, оба ли hasobject, но я подозреваю, что логика может быть довольно сложной. Иногда простой запрет безопаснее (и проще) сложного набора тестов.


В дальнейшем тестировании

In [283]: rf.structured_to_unstructured(a)                                           
Out[283]: 
array([[ 3.,  3.,  0.],
       [12., 10.,  1.],
       [ 2.,  2.,  2.]])

, но попытка сделать то же самое на b, или даже на подмножестве его полей, приводит к знакомой ошибке:

rf.structured_to_unstructured(b)
rf.structured_to_unstructured(b[['A','B','C']]) 

Сначала я должен использовать repack, чтобы сделать копию без объекта:

rf.structured_to_unstructured(rf.repack_fields(b[['A','B','C']])) 
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...