NumPy列表推导式语法

30

我希望能够使用列表推导式语法轻松地处理NumPy数组。

例如,我希望类似下面这个明显错误的代码能够重复生成相同的数组。

>>> X = np.random.randn(8,4)
>>> [[X[i,j] for i in X] for j in X[i]]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: arrays used as indices must be of integer (or boolean) type

有什么简便方法可以避免使用range(len(X)吗?


2
在NumPy中,X[i,j]是X[i][j]的语法糖。 - Andrew Latham
10
不要这样做!这会破坏使用NumPy的全部目的。 - user2357112
而且(即使你修复了它),这也不会重现数组,而是会产生一个列表的列表。 - abarnert
6
X[i,j] 不是语法糖。对于 x[i,j],值被直接检索。x[i][j] 获取第 i 行,然后获取该行中的第 j 个元素。因此它需要更多时间。 - M4rtini
你是想对这些条目进行某些操作吗,比如把它们平方?如果是这样的话,那么问题可能会更有意义。 - Walter Nissen
4个回答

34

首先,您不应该将NumPy数组用作列表嵌套的方式。

其次,让我们忘记NumPy;您的列表推导在第一位就没有意义,即使对于列表嵌套。

在内部推导中,for i in X 将遍历X中的行。这些行不是数字,它们是列表(或者,在NumPy中,是1D数组),因此 X[i] 毫无意义。您可能想要使用 i[j] 来代替。

在外部推导中,for j in X[i] 有同样的问题,但更严重的问题是:根本没有 i 值。您有一个推导循环遍历每个 i 在这个推导内部。

如果您对推导感到困惑,请像在List Comprehensions的教程部分中所解释的那样,将其写成显式的 for 语句:

tmp = []
for j in X[i]:
    tmp.append([X[i,j] for i in X])

...它扩展为:

tmp = []
for j in X[i]:
    tmp2 = []
    for i in X:
        tmp2.append(X[i,j])
    tmp.append(tmp2)

...这应该能够清楚地表明问题所在。


我认为你想要的是:

[[cell for cell in row] for row in X]

再次将其转换为明确的for语句:

tmp = []
for row in X;
    tmp2 = []
    for cell in row:
        tmp2.append(cell)
    tmp.append(tmp2)

那显然是正确的。

或者,如果你真的想使用索引(但你不需要):

[[X[i][j] for j in range(len(X[i]))] for i in range(len(X))]

那么,回到NumPy。用NumPy的术语,最后一版是:

[[X[i,j] for j in range(X.shape[1])] for i in range(X.shape[0])]

如果你想按列主序而不是行主序进行操作,你可以这样做(与列表嵌套的方式不同):

[[X[i,j] for i in range(X.shape[0])] for j in range(X.shape[1])]

...但这当然会转置数组,而这并不是你想要做的。

唯一不能做的一件事是在同一表达式中混合使用列优先和行优先顺序,因为你最终会得到无意义的结果。


当然,制作数组的副本的正确方法是使用copy方法:

X.copy()

正如转换数组的正确方法是:

X.T

16

简单的方法是不要这样做。相反,使用Numpy的隐式向量化。例如,如果您有以下数组A和B:

A = numpy.array([[1, 3, 5],
                 [2, 4, 6],
                 [9, 8, 7]])
B = numpy.array([[5, 3, 5],
                 [3, 5, 3],
                 [5, 3, 5]])

然后使用列表推导式的以下代码:

C = numpy.array([[A[i, j] * B[i, j] for j in xrange(A.shape[1])]
                 for i in xrange(A.shape[0])])

可以更轻松地编写为

C = A * B

它也将运行得更快。通常情况下,如果您不使用numpy的列表推导式,您将会产生更快、更清晰的代码。

如果您真的想使用列表推导式,那么标准的Python列表推导式编写技巧适用。遍历元素而不是索引:

C = numpy.array([[a*b for a, b in zip(a_row, b_row)]
                 for a_row, b_row in zip(A, B)]

因此,您的示例代码将变为

numpy.array([[elem for elem in x_row] for x_row in X])

1
注意:为了提高效率,如果您不再使用列表,可以使用np.asarray()代替np.array()。它是具有copy=False的相同函数。 - Ricardo Magalhães Cruz
2
@RicardoCruz:无论如何都会进行复制。如果输入已经是一个数组,asarray才能避免复制。 - user2357112
@user237112,哦,谢谢你指出来。我不知道那个。 - Ricardo Magalhães Cruz

4

2
您的意思是以下内容吗?
>>> [[X[i,j] for j in range(X.shape[1])] for i in range(X.shape[0])]
[[0.62757350000000001, -0.64486080999999995, -0.18372566000000001, 0.78470704000000002],
 [1.78209799, -1.336448459999999 9, -1.3851422200000001, -0.49668994],
 [-0.84148266000000005, 0.18864597999999999, -1.1135151299999999, -0.40225053999999 999],
 [0.93852824999999995, 0.24652238000000001, 1.1481637499999999, -0.70346624999999996],
 [0.83842508000000004, 1.0058 697599999999, -0.91267403000000002, 0.97991269000000003],
 [-1.4265273000000001, -0.73465904999999998, 0.6684284999999999 8, -0.21551155],
 [-1.1115614599999999, -1.0035033200000001, -0.11558254, -0.4339924],
 [1.8771354, -1.0189299199999999, - 0.84754008000000003, -0.35387946999999997]]

使用numpy.ndarray.copy

>>> X.copy()
array([[ 0.6275735 , -0.64486081, -0.18372566,  0.78470704],
       [ 1.78209799, -1.33644846, -1.38514222, -0.49668994],
       [-0.84148266,  0.18864598, -1.11351513, -0.40225054],
       [ 0.93852825,  0.24652238,  1.14816375, -0.70346625],
       [ 0.83842508,  1.00586976, -0.91267403,  0.97991269],
       [-1.4265273 , -0.73465905,  0.6684285 , -0.21551155],
       [-1.11156146, -1.00350332, -0.11558254, -0.4339924 ],
       [ 1.8771354 , -1.01892992, -0.84754008, -0.35387947]])

他说他正在尝试制作与他开始时完全相同的形状,所以这甚至不是转置,而只是复制 - abarnert
@abarnert,感谢您的评论。我已经更新了答案。 - falsetru

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接