Поскольку numpy может работать только с массивами регулярной формы, он проверяет, что все элементы вложенного итерабельного имеют одинаковую длину для данного измерения. Если их нет, она по-прежнему создает массив, но типа np.object
вместо np.int
, как можно было бы ожидать:
>>> B = np.array(A)
>>> B
array([[[0, 0, 0], [0, 0, 0], [0, 0, 0]],
[[0], [0], [0]]], dtype=object)
В этом случае, «объекты» списки. Дополнение определено для списков, но только в терминах других списков, которые расширяют оригинал, следовательно, ваша ошибка. [0, 0] + 4
- ошибка, а [0, 0] + [4]
- [0, 0, 4]
. Ни то, что вы хотите.
Может быть интересно, что numpy сделает объектную часть вашего гнезда массива как можно более низкой. Массив вы создали фактически 2D NumPy массив, содержащий списки, а не массив, содержащий 1D вложенные списки:
>>> B[0, 0]
[0, 0, 0]
>>> B[0, 0, 0]
Traceback (most recent call last):
File "<ipython-input-438-464a9bfa40bf>", line 1, in <module>
B[0, 0, 0]
IndexError: too many indices for array
Как вы отметили, у вас есть два варианта, когда дело доходит до рваных массивов. Во-первых, чтобы поместить массив таким образом, чтобы он не был оборван, преобразуйте его в numpy и используйте только те элементы, которые вам интересны. В вашем случае это не очень удобно.
Другой метод - применить функции к вашему вложенному массиву напрямую. К счастью для вас, я написал snippet/recipe в ответ на this question, который делает именно то, что вам нужно, вплоть до поддержки произвольных уровней вложенности и вашего выбора операторов. Я модернизировал его здесь, чтобы принять без итерации вложенных элементов в любом месте в список, в том числе первоначального входа и сделать примитивную форму вещания:
from itertools import repeat
def elementwiseApply(op, *iters):
def isIterable(x):
"""
This function is also defined in numpy as `numpy.iterable`.
"""
try:
iter(x)
except TypeError:
return False
return True
def apply(op, *items):
"""
Applies the operator to the given arguments. If any of the
arguments are iterable, the non-iterables are broadcast by
`itertools.repeat` and the function is applied recursively
on each element of the zipped result.
"""
elements = []
count = 0
for iter in items:
if isIterable(iter):
elements.append(iter)
count += 1
else:
elements.append(itertools.repeat(iter))
if count == 0:
return op(*items)
return [apply(op, *items) for items in zip(*elements)]
return apply(op, *iters)
Это довольно общее решением, которое будет работать с любым видом ввода. Вот несколько из образца прогонов, показывающий, как это имеет отношение к вашему вопросу:
>>> from operator import add
>>> elementwiseApply(add, 4, 4)
8
>>> elementwiseApply(add, [4, 0], 4)
[8, 4]
>>> elementwiseApply(add, [(4,), [0, (1, 3, [1, 1, 1])]], 4)
[[8], [4, [5, 7, [5, 5, 5]]]]
>>> elementwiseApply(add, [[0, 0, 0], [0, 0], 0], [[4, 4, 4], [4, 4], 4])
[[4, 4, 4], [4, 4], 4]
>>> elementwiseApply(add, [(4,), [0, (1, 3, [1, 1, 1])]], [1, 1, 1])
[[5], [1, [2, 4, [2, 2, 2]]]]
Результат всегда новый список или скаляр, в зависимости от типов входов. Количество входов должно быть числом, принятым оператором. operator.add
всегда принимает два входа, например.
Преобразование в массив NumPy имеет смысл, когда предполагаемые операции вычисляются тяжелыми и выполняются с использованием некоторого регулярного шаблона. Таким образом, просто добавить скаляр «4» может не стоить проблем. – Divakar
Является ли зубчатая природа результатом какого-то процесса? Мне было бы полезно, если бы ваши элементы списков из [0] могли быть расширены, чтобы включить значение nodata, например [0, -1, -1], что привело к созданию более однородной структуры списка. Затем это можно было бы легко преобразовать в массив с маской размером. Значение nodata для маскированного массива будет установлено равным -1, тогда все последующие вычисления исключают эти ячейки. Вы можете найти это полезным, если легче «исправить» списки, чем играть с исправлением этой структуры данных для удовлетворения требований к массиву. – NaN