创建一个 numpy 数组的置换浅拷贝

问题描述

我希望有相同数据的两个不同视图,行的顺序不同,这样通过一个视图所做的更改将反映在另一个视图中。具体如下代码

# Create original array
A = numpy.array([[0,1,2],[3,4,5],[6,7,8]])
B = A.view()[[0,2,1],:] # Permute the rows
print("(before) B =\n",B)

# Change a value in A
A[1,2] = 143
print("(after) A =\n",A)
print("(after) B =\n",B)

具有以下输出

(before) B =
 [[0 1 2]
  [6 7 8]
  [3 4 5]]
(after) A =
 [[  0   1   2]
  [  3   4 143]
  [  6   7   8]]
(after) B =
 [[0 1 2]
  [6 7 8]
  [3 4 5]]

但我想要最后一点

(after) B =
 [[0   1   2]
  [6   7   8]
  [3   4 143]]

this question 的回答指出,在 特定索引 处获得视图是不可能的,尽管该问题的 OP 是询问数组的一个子集,而我想要一个视图整个数组。 (这里的主要区别似乎是切片与智能索引)

一个 different post 询问关于按 行然后列列然后行 进行切片的问题有一个公认的答案,指出 “所有重要的是无论您是按行切片还是按列切片...”。所以我尝试处理数组的扁平视图..

A = numpy.array([[0,8]])
B = A.view()
B.shape = (A.size,)

A[1,2] = 198
print("(After first) A =\n",A)
print("(After first) B =\n",B)

# Identity index map
all_idx = numpy.arange(A.size).reshape(A.shape)

# Swapped and flattened index map
new_row_idx = all_idx[[0,1]].flatten()

C = B[new_row_idx]

print("(Before second) C =\n",C)

# Manipulate through 'B'
B[7] = 666

print("(After second) B =\n",B)
print("(After second) C =\n",C)

给出以下输出

(After first) A =
 [[  0   1   2]
 [  3   4 198]
 [  6   7   8]]
(After first) B =
 [  0   1   2   3   4 198   6   7   8]
(Before second) C =
 [  0   1   2   6   7   8   3   4 198]
(After second) B =
 [  0   1   2   3   4 198   6 666   8]
(After second) C =
 [  0   1   2   6   7   8   3   4 198]

如您所见,C 的第 4 个条目未更改。 first post I mentioned 的建议解决方案是创建一个副本,进行更改,然后更新原始数组。我可以编写函数来包装它,但这并不能消除我制作副本的次数。它所做的只是对用户隐藏它。

在这里错过了什么?我应该使用这些数组的 data 属性吗?如果是这样,了解如何执行此操作的良好起点是什么?

解决方法

一个数组有一个 shapestridesdtype 和 1d data_buffer。 view 将拥有自己的 shapestridesdtype 和指向 base 的 data_buffer 中某个位置的指针。仅使用这些属性即可实现使用 slice 的索引。

但是不能通过这种方式实现使用诸如 [0,2,1] 之类的列表进行索引。所以 numpy 用它自己的 data_buffer 创建了一个新数组,一个 copy。 [0,1] 索引列表/数组不与副本一起存储。

In [43]: A = np.arange(9).reshape(3,3)
In [44]: B = A[[0,1],:]
In [45]: A
Out[45]: 
array([[0,1,2],[3,4,5],[6,7,8]])
In [46]: B
Out[46]: 
array([[0,8],5]])

ravel 显示数据库中元素的顺序:

In [47]: A.ravel()
Out[47]: array([0,3,5,6,8])

B 中元素的顺序不同。

In [48]: B.ravel()
Out[48]: array([0,8,5])

相反,考虑使用切片对行进行重新排序:

In [49]: C = A[::-1,:]
In [50]: C
Out[50]: 
array([[6,[0,2]])

In [52]: A.strides
Out[52]: (24,8)

这是通过简单地更改 strides 来实现的:

In [53]: C.strides
Out[53]: (-24,8)

转置也是一种视图,但步幅发生了变化:

In [54]: D = A.T
In [55]: D.strides
Out[55]: (8,24)

我本来打算展示 C.ravel(),但意识到 reshape 会生成一个副本(即使 C 是一个视图)。

基本点是,numpy 描述为 advanced indexing 的任何内容都会复制。对副本的更改不会出现在原始数组中。 https://numpy.org/doc/stable/reference/arrays.indexing.html#advanced-indexing