如果我们仅将矩阵A的一部分分配给另一个变量(view1),则此变量将仅显示该矩阵的各个组成部分的视图。但是,如果使用高级索引将矩阵A的一部分分配给另一个变量(view2),则将创建矩阵中与之相对应的组件的副本。为什么要进行高级索引编制(当我们有一个切片参数列表而不是元组序列对象时)?我已经阅读了文档,但是仍然不明白。
A = np.arange(15)
A = A.reshape((3,5))
print(A, "\n")
view1 = A[:, [3]]
print("'view1' before the matrix A change =", view1)
view2 = A[:, 3]
print("'view2' before the matrix A change =", view2, "\n")
# Change in the 4th column od the matrix A
A[:, 3] = 5
print(A, "\n")
print("'view1' after the matrix A change =", view1)
print("'view2' after the matrix A change =", view2)
答案 0 :(得分:0)
首先,我将确切解释您的代码的作用。它创建一个二维3x5矩阵A
,然后从中创建一个二维3x1列矩阵view1
和一个一维矢量view2
。 view1
和view2
中的信息相同-矩阵A
的第四列。然后,您的代码将更改矩阵A
,并且view2
也将收到这些更改,但是view1
不会更改。结论是view2
实际上是A
的 view -它不是单独的数据,而只是看矩阵A
的一部分。但是,view1
是创建A
时view1
中信息的副本-它是与A
一致的单独数据开始,但可能会有所不同。
向量view2
是A
的视图,因为其定义A[:, 3]
是A
的基本切片,即第四列。列矩阵view1
是一个副本,因为其定义A[:, [3]]
是“高级索引”或“花式索引”。 3
周围的括号阻止了它成为基本切片。定义上的微小变化说明了原因:view3 = A[:, [3,0,1]]
创建了一个3x3矩阵,该矩阵按顺序包含A
的第四,第一和第二列。
您的问题是一个好问题:切片和高级索引的结果看起来很相似,那么为什么第一个是视图,第二个是副本?
简短的答案是:为了方便和快速,就像计算机科学中的许多其他决定一样。
这里是一个图形,该图形显示了如何在内存中设置一个numpy数组,而不是Python的标准列表。 (此图片摘自 Python数据科学手册,第2章,第1节。)
我们看到numpy的数组不是很灵活。对于A
,数组的尺寸是-3x5,对于view1
是3x1,对于view2
是3,对于view3
是3x3。然后是 strides ,基本上是沿特定维度从一个单元格到下一个单元格的偏移量。这就是数组的问题,而不是项目值本身。这样可以保持较低的内存使用率,但可以防止Python的list-in-lists可以做一些花哨的事情。 (我在这里简化一些细节。)
在您的示例中,A
的数据为
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14
第一个维度是行,要从一个单元格转到下一行中的相应单元格,numpy必须跳转5个值。例如。第一行和第四列的值为3
。移至下一行,我们再移动5个位置以查看值8
,而获得下一行则再移动5个位置以获取值13
。第二个维度是列,其跨度为1:3
仅更改列之后的值为4
。因此,矩阵A
的步幅为5
和1
。
view2
的切片很简单,因此数据与矩阵A
的数据相同。无需复制数据。那么view2
与A
有何不同? 0
的数据指针不像A
的数据指针那样指向值view2
,而是指向同一数据中的3
。它的尺寸块表示只有一个具有3个值的尺寸。它只有一个跨度,但跨度为5
。换句话说,view2
中的第一个值是3
,要找到下一个值,numpy会跳过5
(跨步)位置以找到8
,然后再次找到13
。数组view2
使用的数据与A
相同,但是维度和跨度不同,因此看起来不同。换句话说,它是A
的视图。
view3
的高级索引是不同的。在同一行中,值从A
的第四列跳到第一列,再跳到第二列。没有任何步伐可能会告诉numpy以这种方式访问值。因此,numpy必须从A
复制一些数据,以创建一个新的简单数组来处理view3
。复制是因为无法进行视图。为了澄清,这是view3
:
[[ 3 0 1]
[ 8 5 6]
[13 10 11]]
现在您的问题是:view1
呢?对于numpy,在特定情况下建立视图将是可能。但是,view1
是通过高级索引创建的,我们已经看到,通常无法为它们创建视图。因此,numpy采取了方便,快速且一致的方法,并声明即使在有可能使用视图的情况下,所有高级索引编制场合都可以创建副本。这很方便,因为numpy不需要尝试找出是否可以使用视图。它非常快,并且请记住,numpy的主要优点之一是它非常快,因此省去了在可能的情况下加快视图创建速度的尝试。这是一致的,因为高级索引的所有使用都会创建副本,而不是某些创建视图和某些副本。