在不包含列表理解的情况下更改过滤后的二维数组中的元素,同时保持对原始元素的引用

Changing elements in a filtered 2d array without list comprehension while keeping reference to original

我不确定如何表达这个问题,但这是我正在尝试做的事情。

arr_first = np.array([[0,0,0,0],[0,0,0,0],[1,1,1,0],[1,1,1,0],[1,1,1,0],[1,1,2,0],[1,1,2,0],[2,2,2,0]])
arr_second = np.array([[0,0,0],[1,1,1],[1,1,2],[2,2,2]])

我正在尝试通过 arr_second 的前三个元素过滤 arr_first,导致...

[array([0, 0, 0, 0]), array([0, 0, 0, 0])]
[array([1, 1, 1, 0]), array([1, 1, 1, 0]), array([1, 1, 1, 0])]
[array([1, 1, 2, 0]), array([1, 1, 2, 0])]
[array([2, 2, 2, 0])]

然后,使用过滤后的二维数组,将 32 添加到每个二维数组中的一个数组的第四个元素,如下所示:

[[ 0  0  0  0]
 [ 0  0  0 32]
 [ 1  1  1  0]
 [ 1  1  1  0]
 [ 1  1  1 32]
 [ 1  1  2  0]
 [ 1  1  2 32]
 [ 2  2  2 32]]

并将该数据保存到原始 arr_first

我目前使用的方法是使用 python 列表理解语法:

for i in range(len(arr_second)):
    filtered = [row for row in arr_first if
                        arr_second[i][0] == row[0] and arr_second[i][1] == row[1] and arr_second[i][2] == row[2]]
    choosen_block = random.choice(filtered)
    choosen_block[3] += 32
print(arr_first)

这可行,但在大型数据集中可能会非常慢。因此,我尝试使用 numpy 的 in1d:

进行过滤
for i in range(len(arr_second)):
    filtered = arr_first[np.in1d(arr_first[:, 0], arr_second[i][0]) &
    np.in1d(arr_first[:, 1], arr_second[i][1]) &
    np.in1d(arr_first[:, 2], arr_second[i][2])]

    choosen_block = random.choice(filtered)
    choosen_block[3] += 32

但此方法的问题是更改不再保存在 arr_first 中,这与列表理解方法不同,因为 arr_first 不再通过引用传递到 filtered.

我想知道是否有人可以通过使 filtered 中的更改也出现在 arr_first 中而不是必须制作另一个列表并附加循环来解决这个问题filtered到它。

您可以使用 Pandas 到 groupbysample,并更新 arr_first

import pandas as pd

df = pd.DataFrame(arr_first)
inner_len = len(arr_first[0,:])
update_amt = 32
update_ix = 3

df.iloc[(df.groupby(list(range(inner_len)))
           .apply(lambda x: x.sample().index.values[0]).values), 
        update_ix] += update_amt

arr_first
[[ 0  0  0  0]
 [ 0  0  0 32]
 [ 1  1  1  0]
 [ 1  1  1 32]
 [ 1  1  1  0]
 [ 1  1  2 32]
 [ 1  1  2  0]
 [ 2  2  2 32]]

解释

  • Pandas 让我们可以根据唯一的行值集对 arr_first 进行分组,例如[1,1,1,0]。我将 groupby 过程缩写为 range(),但命令实际上只是说:"Group by column 0, then column 1, then column 2, then column 3"。这有效地按 arr_first 中每一行的完整值集进行了分组。这似乎有效地模仿了您通过 arr_second 中的值匹配 arr_first 行的方法。

  • 一旦我们得到了组中的行,我们就可以 sample 每组中的其中一行,并获取它的索引。

  • 然后,将选定的索引用于添加更新步骤。

  • 即使我们正在更新 dfarr_first 也会更新,因为它在 df 的创建过程中(有点)通过引用传递.

我倾向于在 Pandas 中思考,但可能有与这些步骤等效的 Numpy。

以下是使您的方法奏效的方法。

首先,为什么 list comp 可以就地工作,而 in1d 不能?列表 comp 对 arr_first 的各个行进行操作,每个这样的行都是一个 "view",即对 arr_first 的引用。相比之下,in1d soln 创建一个掩码,然后将其应用于阵列。使用掩码是 "fancy" 或 "advanced" 索引的一种形式。由于 orig 数组花式索引所指的子集通常不能用偏移量和步幅表示,因此这会强制复制,之后无论您做什么都不会影响 orig 数组。

一个简单的解决方法是不敷面膜。而是将其转换为行索引向量并直接在此向量上使用 random.choice:

import numpy as np
import random

arr_first = np.array([[0,0,0,0],[0,0,0,0],[1,1,1,0],[1,1,1,0],[1,1,1,0],[1,1,2,0],[1,1,2,0],[2,2,2,0]])
arr_second = np.array([[0,0,0],[1,1,1],[1,1,2],[2,2,2]])

for i in range(len(arr_second)):
    filtered_idx = np.where(np.in1d(arr_first[:, 0], arr_second[i][0]) &
                            np.in1d(arr_first[:, 1], arr_second[i][1]) &
                            np.in1d(arr_first[:, 2], arr_second[i][2]))[0]

    choosen_block = random.choice(filtered_idx)
    arr_first[choosen_block, 3] += 32

print(arr_first)

示例输出:

[[ 0  0  0  0]
 [ 0  0  0 32]
 [ 1  1  1 32]
 [ 1  1  1  0]
 [ 1  1  1  0]
 [ 1  1  2  0]
 [ 1  1  2 32]
 [ 2  2  2 32]]