2016-10-27 8 views
4

В настоящее время у меня есть список 3D-Python в формате jagged array.
A = [[[0, 0, 0], [0, 0, 0], [0, 0, 0]], [[0], [0], [0]]]Преобразование 3D-списка в массив 3D NumPy

Есть ли способ, которым я мог бы преобразовать этот список в массив NumPy, для того, чтобы использовать некоторые операторы NumPy массива, такие как добавление номера к каждому элементу.
A + 4 дал бы [[[4, 4, 4], [4, 4, 4], [4, 4, 4]], [[4], [4], [4]]].

Присвоение B = numpy.array(A), затем попытка B + 4 вызывает ошибку типа.
TypeError: can only concatenate list (not "float") to list

Является ли преобразование из списка зубчатого Python в массив NumPy возможно, сохраняя при этом структуре (мне нужно будет преобразовать его позже) или цикл по массиву и добавляя требуемую лучшее решение в этом случае ?

+1

Преобразование в массив NumPy имеет смысл, когда предполагаемые операции вычисляются тяжелыми и выполняются с использованием некоторого регулярного шаблона. Таким образом, просто добавить скаляр «4» может не стоить проблем. – Divakar

+0

Является ли зубчатая природа результатом какого-то процесса? Мне было бы полезно, если бы ваши элементы списков из [0] могли быть расширены, чтобы включить значение nodata, например [0, -1, -1], что привело к созданию более однородной структуры списка. Затем это можно было бы легко преобразовать в массив с маской размером. Значение nodata для маскированного массива будет установлено равным -1, тогда все последующие вычисления исключают эти ячейки. Вы можете найти это полезным, если легче «исправить» списки, чем играть с исправлением этой структуры данных для удовлетворения требований к массиву. – NaN

ответ

4

Ответы @SonderingNarcissit и @MadPhysicist уже довольно приятные.

Это быстрый способ добавить число к каждому элементу в вашем списке и сохранить структуру. Вы можете заменить функцию return_number на все, что вы хотите, если вы хотите не только добавить номер, но сделать что-то еще с ним:

def return_number(my_number): 
    return my_number + 4  

def add_number(my_list): 

    if isinstance(my_list, (int, float)): 
     return return_number(my_list) 
    else: 
     return [add_number(xi) for xi in my_list] 

A = [[[0, 0, 0], [0, 0, 0], [0, 0, 0]], [[0], [0], [0]]] 

Тогда

print(add_number(A)) 

дает желаемый результат:

[[[4, 4, 4], [4, 4, 4], [4, 4, 4]], [[4], [4], [4]]] 

Итак, что он делает, так это то, что он выглядит рекурсивно через список списков и каждый раз, когда он находит номер, он добавляет значение 4; это должно работать для сколь угодно глубоких вложенных списков. Это в настоящее время работает только для чисел и списков; если у вас есть, например, также словари в ваших списках, тогда вам нужно будет добавить другое if-clause.

+0

Для целей ОП это работает очень хорошо. Мое решение - довольно полное обновление до вашего, что я работаю итеративно с помощью ряда связанных ответов. –

+0

@MadPhysicist: Не видел ваших изменений; ваше решение действительно намного более общее, чем мое (теперь его поддерживает), в то время как мое может быть легче понять, если у вас не много фона Python/programming. – Cleb

+1

Согласовано. По этому вопросу я бы поднял свою кандидатуру, но определенно выбрал бы вашу. –

1

Цитирование и добавление, скорее всего, лучше, поскольку вы хотите сохранить структуру оригинала. Кроме того, указанная вами ошибка указывает, что вам нужно будет сгладить массив numpy и затем добавить к каждому элементу. Несмотря на то, что операции с numpy, как правило, быстрее, чем операции с списками, преобразование, выравнивание и обратная связь являются громоздкими и, вероятно, будут компенсировать любые выгоды.

2

Поскольку 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 всегда принимает два входа, например.

+0

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

+0

@Wintro. Я думаю, вы поступили правильно. Это скорее всего лишь фрагмент, который я с удовольствием улучшил итеративно для ряда подобных вопросов. –

1

Это мы превратить ваш список в массив, мы получим 2d массив объектов

In [1941]: A = [[[0, 0, 0], [0, 0, 0], [0, 0, 0]], [[0], [0], [0]]] 
In [1942]: A = np.array(A) 
In [1943]: A.shape 
Out[1943]: (2, 3) 
In [1944]: A 
Out[1944]: 
array([[[0, 0, 0], [0, 0, 0], [0, 0, 0]], 
     [[0], [0], [0]]], dtype=object) 

При попытке A+1 он перебирает элементы A и пытается сделать +1 для каждого. В случае числового массива он может сделать это в быстро скомпилированном коде. С массивом объектов он должен вызывать операцию + для каждого элемента.

In [1945]: A+1 
... 
TypeError: can only concatenate list (not "int") to list 

Давайте попробуем, что снова с плоской итерации над A:

In [1946]: for a in A.flat: 
     ...:  print(a+1) 
.... 
TypeError: can only concatenate list (not "int") to list 

Элементы A списки; + для списка является конкатенация:

In [1947]: for a in A.flat: 
     ...:  print(a+[1]) 
     ...:  
[0, 0, 0, 1] 
[0, 0, 0, 1] 
[0, 0, 0, 1] 
[0, 1] 
[0, 1] 
[0, 1] 

Если элементы A были сами массивы, я думаю, что +1 будет работать.

In [1956]: for i, a in np.ndenumerate(A): 
     ...:  A[i]=np.array(a) 
     ...:  
In [1957]: A 
Out[1957]: 
array([[array([0, 0, 0]), array([0, 0, 0]), array([0, 0, 0])], 
     [array([0]), array([0]), array([0])]], dtype=object) 
In [1958]: A+1 
Out[1958]: 
array([[array([1, 1, 1]), array([1, 1, 1]), array([1, 1, 1])], 
     [array([1]), array([1]), array([1])]], dtype=object) 

И чтобы вернуться к чистой форме списка, мы должны применить tolist к обоим элементам массива объекта и самого массива:

In [1960]: A1=A+1 
In [1961]: for i, a in np.ndenumerate(A1): 
     ...:  A1[i]=a.tolist() 

In [1962]: A1 
Out[1962]: 
array([[[1, 1, 1], [1, 1, 1], [1, 1, 1]], 
     [[1], [1], [1]]], dtype=object) 
In [1963]: A1.tolist() 
Out[1963]: [[[1, 1, 1], [1, 1, 1], [1, 1, 1]], [[1], [1], [1]]] 

это довольно кружным путем добавления значение для всех элементов вложенных списков. Я мог бы сделать это с одной итерацией:

In [1964]: for i,a in np.ndenumerate(A): 
     ...:  A[i]=[x+1 for x in a] 
     ...:  
In [1965]: A 
Out[1965]: 
array([[[1, 1, 1], [1, 1, 1], [1, 1, 1]], 
     [[1], [1], [1]]], dtype=object) 

Так делать математику на массивах объектов ударили и промах. Некоторые операции распространяются на элементы, но даже они зависят от того, как ведут себя элементы.

+0

Я не думаю, что это действительно относится к фактическому вопросу OPs. –

+1

Я конвертирую 'из неровного списка Python в массив NumPy ... сохраняя структуру (мне нужно будет ее перевести позже)'. Пребывание с чистыми списками может быть быстрее из-за избыточных издержек массива, но если вы хотите пройти маршрут массива, это способ/способ сделать это. – hpaulj

1

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

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

def num_46(): 
    """(num_46)... Masked array from ill-formed list 
    : http://stackoverflow.com/questions/40289943/ 
    : converting-a-3d-list-to-a-3d-numpy-array 
    : A =[[[0, 0, 0], [0, 0, 0], [0, 0, 0]], 
    :  [[0, 0, 0], [0, 0, 0], [0, 0, 0]], [[0], [0], [0]]] 
    """ 
    frmt = """ 
    :Input list... 
    {}\n 
    :Masked array data 
    {}\n 
    :A sample calculations: 
    : a.count(axis=0) ... a.count(axis=1) ... a.count(axis=2) 
    {}\n 
    {}\n 
    {}\n 
    : and finally: a * 2 
    {}\n 
    :Return it to a list... 
    {} 
    """ 
    a_list = [[[0, 1, 2], [3, 4, 5], [6, 7, 8]], 
       [[9, 10, 11], [12, 13, 14], [15, 16, 17]], 
       [[18, -1, -1], [21, -1, -1], [24, -1, -1]]] 
    mask_val = -1 
    a = np.ma.masked_equal(a_list, mask_val) 
    a.set_fill_value(mask_val) 
    final = a.tolist(mask_val) 
    args = [a_list, a, 
      a.count(axis=0), a.count(axis=1), a.count(axis=2), 
      a*2, final] 
    print(dedent(frmt).format(*args)) 
    return a_list, a, final 


#---------------------- 
if __name__ == "__main__": 
    """Main section... """ 
    A, a, c = num_46() 

Некоторые результаты, которые показывают, что использование замаскированных массивов может быть предпочтительным, чтобы зазубренным/искаженной структуры списка.

:Input list... 
[[[0, 1, 2], [3, 4, 5], [6, 7, 8]], 
[[9, 10, 11], [12, 13, 14], [15, 16, 17]], 
[[18, -1, -1], [21, -1, -1], [24, -1, -1]]] 

:Masked array data 
[[[0 1 2] 
    [3 4 5] 
    [6 7 8]] 

[[9 10 11] 
    [12 13 14] 
    [15 16 17]] 

[[18 - -] 
    [21 - -] 
    [24 - -]]] 

:A sample calculations: 
: a.count(axis=0) ... a.count(axis=1) ... a.count(axis=2) 
[[3 2 2] 
[3 2 2] 
[3 2 2]] 

[[3 3 3] 
[3 3 3] 
[3 0 0]] 

[[3 3 3] 
[3 3 3] 
[1 1 1]] 

: and finally: a * 2 
[[[0 2 4] 
    [6 8 10] 
    [12 14 16]] 

[[18 20 22] 
    [24 26 28] 
    [30 32 34]] 

[[36 - -] 
    [42 - -] 
    [48 - -]]] 

:Return it to a list... 
[[[0, 1, 2], [3, 4, 5], [6, 7, 8]], [[9, 10, 11], [12, 13, 14], [15, 16, 17]], [[18, -1, -1], [21, -1, -1], [24, -1, -1]]] 

Надеюсь, это поможет кому-то.

+0

Маскирование моих данных с помощью каких-то значений «Нет» может быть хорошей идеей, но позже я запускаю многорекурсивно через список тысячи раз, а список намного больше, чем показано здесь. Поэтому я считаю, что проверка «Нет» для каждого отдельного значения и добавленных значений в списке серьезно затруднит производительность. –

+0

@Wintro Мне было любопытно, как в первую очередь попали лингвисты. Я добавил их во время построения, затем маскированный массив не проверяет их. Однако это было бы полезно для обработки списка. Это было бы более полезно, если вы планировали провести анализ с помощью массива, а не списка. В любом случае преобразование в массив имеет свои накладные расходы, которые должны быть взвешены против обработки только в виде списка. – NaN

Смежные вопросы