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

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