1. NumPy中形状的含义
你这样写道:“我知道它实际上是一个数字列表和一个包含仅数字的列表的列表”,但这种方式并不是很有帮助。
最好的方法是将NumPy数组看作由两个部分组成:一个数据缓冲区,它只是一块原始元素的块,以及一个视图,描述了如何解释数据缓冲区。
例如,如果我们创建了一个包含12个整数的数组:
>>> a = numpy.arange(12)
>>> a
array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
然后a
由一个数据缓冲区组成,排列方式类似于:
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │ 11 │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘
并且有一个视图,描述了如何解释这些数据:
>>> a.flags
C_CONTIGUOUS : True
F_CONTIGUOUS : True
OWNDATA : True
WRITEABLE : True
ALIGNED : True
UPDATEIFCOPY : False
>>> a.dtype
dtype('int64')
>>> a.itemsize
8
>>> a.strides
(8,)
>>> a.shape
(12,)
在这里,形状 (12,)
表示该数组由单个索引进行索引,该索引从0到11运行。就概念上而言,如果我们将这个单一的索引标记为i
,那么数组a
看起来像这样:
i= 0 1 2 3 4 5 6 7 8 9 10 11
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │ 11 │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘
如果我们重塑一个数组,这并不会改变数据缓冲区。相反,它会创建一个新的视图来描述不同的数据解释方式。因此,在进行以下操作后:
>>> b = a.reshape((3, 4))
数组b
和a
拥有相同的数据缓冲区,但现在它由两个索引索引,分别从0到2和0到3。如果我们标记这两个索引为i
和j
,那么数组b
看起来像这样:
i= 0 0 0 0 1 1 1 1 2 2 2 2
j= 0 1 2 3 0 1 2 3 0 1 2 3
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │ 11 │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘
这意味着:
>>> b[2,1]
9
您可以看到第二个索引的变化速度很快,而第一个索引的变化速度较慢。如果您希望反过来,可以指定order
参数:
>>> c = a.reshape((3, 4), order='F')
这将导致一个像这样索引的数组:
i= 0 1 2 0 1 2 0 1 2 0 1 2
j= 0 0 0 1 1 1 2 2 2 3 3 3
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │ 11 │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘
这意味着:
>>> c[2,1]
5
现在应该很清楚,一个数组具有一个或多个尺寸为1的维度的形状是什么意思了。之后:
>>> d = a.reshape((12, 1))
数组d
由两个索引引用,第一个索引从0到11,第二个索引始终为0:
i= 0 1 2 3 4 5 6 7 8 9 10 11
j= 0 0 0 0 0 0 0 0 0 0 0 0
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │ 11 │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘
因此:
>>> d[10,0]
10
长度为1的维度在某种程度上是“自由”的,因此没有任何阻止你进行操作:
>>> e = a.reshape((1, 2, 1, 6, 1))
给出一个像这样索引的数组:
i= 0 0 0 0 0 0 0 0 0 0 0 0
j= 0 0 0 0 0 0 1 1 1 1 1 1
k= 0 0 0 0 0 0 0 0 0 0 0 0
l= 0 1 2 3 4 5 0 1 2 3 4 5
m= 0 0 0 0 0 0 0 0 0 0 0 0
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │ 11 │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘
因此:
>>> e[0,1,0,0,0]
6
查看NumPy内部文档了解有关如何实现数组的更多细节。
2. 怎么做?
由于numpy.reshape
只是创建一个新视图,所以在必要时使用它不必担心。当您想以不同的方式对数组进行索引时,这是正确的工具。
然而,在长时间计算中,通常可以安排在第一次构造具有“正确”形状的数组,从而最小化重塑和转置的数量。但是,如果没有看到导致需要重塑的实际上下文,则很难说应该更改什么。
您问题中的示例为:
numpy.dot(M[:,0], numpy.ones((1, R)))
但这不是现实。首先,这个表达式:
M[:,0].sum()
计算结果更简单。其次,第0列是否真的有特殊之处?也许您实际需要的是:
M.sum(axis=0)
x=4,
分配一个元组,x=(4)
分配一个整数,这常常会引起混淆。形状n,
表示具有 n 个项目的一维数组的形状,而n,1
表示具有 n 行 x 1 列的数组的形状。(R,)
和(R,1)
只是添加了(无用的)括号,但仍然分别表示 1D 和 2D 数组的形状。在元组周围加上括号可以强制求值顺序并防止其被读取为值列表(例如在函数调用中)。记住这种元组的奇特性,事情就变得更清晰了,NumPy 返回有意义的形状。 - mins