将3D列表转换为3D NumPy阵列

时间:2016-10-27 16:35:13

标签: python list python-3.x numpy

目前,我有一个锯齿状数组格式的3D Python列表 A = [[[0, 0, 0], [0, 0, 0], [0, 0, 0]], [[0], [0], [0]]]

有没有什么方法可以将这个列表转换为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数组(稍后我需要将其转换回来),或者循环遍历数组并在这种情况下添加所需的更好的解决方案?

5 个答案:

答案 0 :(得分:4)

@SonderingNarcissit和@MadPhysicist的答案已经非常好了。

以下是为列表中的每个元素添加数字并保留结构的快速方法。你可以用你喜欢的任何东西替换函数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]]]

为您提供所需的输出:

{{1}}

所以它的作用是它通过列表列表递归查看,每次找到一个数字时它会增加值4;这适用于任意深度嵌套列表。目前仅适用于数字和列表;如果你也有例如你的列表中也有字典,那么你必须添加另一个if子句。

答案 1 :(得分:2)

由于numpy只能使用规则形状的数组,因此它会检查嵌套iterable的所有元素是否与给定维度的长度相同。如果不是,它仍会创建一个数组,但类型为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总是需要两个输入。

答案 2 :(得分:1)

循环和添加可能更好,因为您希望保留原始结构。另外,您提到的错误表明您需要展平numpy数组,然后添加到每个元素。虽然numpy操作往往比列表操作更快,但转换,展平和恢复很麻烦,可能会抵消任何收益。

答案 3 :(得分:1)

我们将您的列表转换为数组,我们得到一个二维数组对象

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)

所以在对象数组上进行数学运算是命中注意力。有些操作会传播到元素,但即使这些操作也依赖于元素的行为方式。

答案 4 :(得分: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]]]

希望这有助于某人。