Предотвратить numy от создания многомерного массива
NumPy действительно полезен при создании массивов. Если первый аргумент для numpy.array
имеет __getitem__
а также __len__
Метод они используются на основе того, что это может быть допустимой последовательностью.
К сожалению, я хочу создать массив, содержащий dtype=object
без NumPy быть "полезным".
Класс, разбитый на минимальный пример, будет выглядеть так:
import numpy as np
class Test(object):
def __init__(self, iterable):
self.data = iterable
def __getitem__(self, idx):
return self.data[idx]
def __len__(self):
return len(self.data)
def __repr__(self):
return '{}({})'.format(self.__class__.__name__, self.data)
и если "итерации" имеют разную длину, все в порядке, и я получаю именно тот результат, который хочу получить:
>>> np.array([Test([1,2,3]), Test([3,2])], dtype=object)
array([Test([1, 2, 3]), Test([3, 2])], dtype=object)
но NumPy создает многомерный массив, если они имеют одинаковую длину:
>>> np.array([Test([1,2,3]), Test([3,2,1])], dtype=object)
array([[1, 2, 3],
[3, 2, 1]], dtype=object)
К сожалению, есть только ndmin
аргумент, так что мне было интересно, если есть способ обеспечить соблюдение ndmax
или как-то помешать NumPy интерпретировать пользовательские классы как другое измерение (без удаления __len__
или же __getitem__
)?
3 ответа
Обходной путь - это, конечно, создать массив желаемой формы и затем скопировать данные:
In [19]: lst = [Test([1, 2, 3]), Test([3, 2, 1])]
In [20]: arr = np.empty(len(lst), dtype=object)
In [21]: arr[:] = lst[:]
In [22]: arr
Out[22]: array([Test([1, 2, 3]), Test([3, 2, 1])], dtype=object)
Обратите внимание, что в любом случае я не удивлюсь, если NumPy поведение по отношению к интерпретации итерируемых объектов (что вы хотите использовать, не так ли?) Зависит от NumPy версии. И возможно глючит. Или, возможно, некоторые из этих ошибок на самом деле функции. Во всяком случае, я бы остерегался поломки, когда изменялась версия.
Напротив, копирование в предварительно созданный массив должно быть более надежным.
Это поведение обсуждалось несколько раз ранее (например, переопределить диктовку с поддержкой numey). np.array
пытается сделать размерный массив настолько высоким, насколько это возможно. Модельный случай - это вложенные списки. Если он может повторяться, а подсписки равны по длине, он "сверлит" вниз.
Здесь он прошел 2 уровня, прежде чем столкнулся со списками разной длины:
In [250]: np.array([[[1,2],[3]],[1,2]],dtype=object)
Out[250]:
array([[[1, 2], [3]],
[1, 2]], dtype=object)
In [251]: _.shape
Out[251]: (2, 2)
Без параметра shape или ndmax невозможно определить, хочу ли я (2,)
или же (2,2)
, Оба из них будут работать с dtype.
Это скомпилированный код, поэтому непросто увидеть, какие именно тесты он использует. Он пытается перебирать списки и кортежи, но не наборы и словари.
Самый надежный способ создать массив объектов с заданным измерением - начать с пустого и заполнить его.
In [266]: A=np.empty((2,3),object)
In [267]: A.fill([[1,'one']])
In [276]: A[:]={1,2}
In [277]: A[:]=[1,2] # broadcast error
Другой способ - начать с хотя бы одного другого элемента (например, None
), а затем заменить это.
Есть более примитивный создатель, ndarray
это принимает форму:
In [280]: np.ndarray((2,3),dtype=object)
Out[280]:
array([[None, None, None],
[None, None, None]], dtype=object)
Но это в основном так же, как np.empty
(если я не дам ему буфер).
Это выдумки, но они не дорогие (по времени).
================ (редактировать)
https://github.com/numpy/numpy/issues/5933, Enh: Object array creation function.
это запрос на улучшение Также https://github.com/numpy/numpy/issues/5303 the error message for accidentally irregular arrays is confusing
,
Похоже, что настроение разработчика поддерживает отдельную функцию для создания dtype=object
массивы, один с большим контролем начальных размеров и глубины итерации. Они могут даже усилить проверку ошибок, чтобы сохранить np.array
от создания "неправильных" массивов.
Такая функция может определять форму регулярного вложенного элемента, повторяемого до заданной глубины, и создавать массив типов объектов для заполнения.
def objarray(alist, depth=1):
shape=[]; l=alist
for _ in range(depth):
shape.append(len(l))
l = l[0]
arr = np.empty(shape, dtype=object)
arr[:]=alist
return arr
С различной глубиной:
In [528]: alist=[[Test([1,2,3])], [Test([3,2,1])]]
In [529]: objarray(alist,1)
Out[529]: array([[Test([1, 2, 3])], [Test([3, 2, 1])]], dtype=object)
In [530]: objarray(alist,2)
Out[530]:
array([[Test([1, 2, 3])],
[Test([3, 2, 1])]], dtype=object)
In [531]: objarray(alist,3)
Out[531]:
array([[[1, 2, 3]],
[[3, 2, 1]]], dtype=object)
In [532]: objarray(alist,4)
...
TypeError: object of type 'int' has no len()
Обход с помощью панд
Это может быть не то, что ищет OP. Но, на всякий случай, если кто-то ищет способ предотвратить создание многомерных массивов numpy, это может быть полезно.
Передайте свой список pd.Series
а затем получить элементы в виде массива с помощью .values
,
import pandas as pd
pd.Series([Test([1,2,3]), Test([3,2,1])]).values
# array([Test([1, 2, 3]), Test([3, 2, 1])], dtype=object)
Или, если вы имеете дело с массивами numpy:
np.array([np.random.randn(2,2), np.random.randn(2,2)]).shape
(2, 2, 2)
С помощью pd.Series
:
pd.Series([np.random.randn(2,2), np.random.randn(2,2)]).values.shape
#(2,)
Этот обходной путь может быть не самым эффективным, но мне нравится его ясность:
test_list = [Test([1,2,3]), Test([3,2,1])]
test_list.append(None)
test_array = np.array(test_list, dtype=object)[:-1]
Описание: Вы берете свой список, добавляете None, затем преобразуете в массив numpy, предотвращая преобразование numpy в многомерный массив. Наконец, вы просто удаляете последнюю запись, чтобы получить желаемую структуру.