在保持对原始引用的同时更改已过滤的2d数组中的元素而没有列表理解

时间:2017-11-09 03:23:10

标签: python arrays numpy pass-by-reference

我不确定如何说出这个问题,但这就是我要做的事情。

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_first的前三个元素过滤arr_second,从而产生......

[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])]

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

[[ 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 list comprehension语法:

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更改来向我提供有关如何解决此问题的一些指导,而不是必须创建另一个列表并附加一个循环{{ 1}}到它。

2 个答案:

答案 0 :(得分:1)

您可以将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(),但命令实际上只是说:“按列0分组,然后是第1列,然后是第2列,然后是第3列”。这有效地按arr_first中每行的全部值进行分组。这似乎有效地模仿了您使用arr_first中的值匹配arr_second行的方法。

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

  • 然后,使用所选索引进行添加更新步骤。

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

我倾向于在熊猫中思考,但可能有一个Numpy等同于这些步骤。

答案 1 :(得分:1)

以下是如何使您的方法有效。

首先,为什么列表comp就地工作,而in1d却没有?列表comp对arr_first的各行进行操作,每个这样的行都是“视图”,即对arr_first的引用。相比之下,in1d soln会创建一个掩码,然后将其应用于阵列。使用掩码是“花式”或“高级”索引的一种形式。由于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]]