目前,我有一个锯齿状数组格式的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数组(稍后我需要将其转换回来),或者循环遍历数组并在这种情况下添加所需的更好的解决方案?
答案 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]]]
希望这有助于某人。