1.形状在NumPy中的意义
你写道,"我知道这是一个数字列表,所有列表只包含一个数字的列表",但这是一种没有帮助的思考方式.
考虑NumPy数组的最佳方式是,它们由两部分组成,一部分是data buffer,它只是一个原始元素块,另一部分是view,它描述了如何解释数据缓冲区.
例如,如果我们创建一个由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,)
在这里,shape (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
相同的数据缓冲区,但现在它由two个索引索引,这些索引分别运行在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 internals documentation.
2.怎么办?
因为numpy.reshape
只是创建了一个新的视图,所以你不应该害怕在必要的时候使用它.当您想要以不同的方式索引数组时,它是正确的工具.
然而,在长时间的计算中,通常可以首先安排构造具有"正确"形状的数组,从而最小化reshape 和转置的数量.但如果没有看到导致需要reshape 的实际背景,就很难说应该改变什么.
你问题中的例子是:
numpy.dot(M[:,0], numpy.ones((1, R)))
但这并不现实.首先,这个表达:
M[:,0].sum()
计算结果更加简单.第二,第0栏真的有什么特别之处吗?也许你真正需要的是:
M.sum(axis=0)