NumPy数组的行优先和列优先

41

我很难理解 numpy 如何存储数据。考虑以下内容:

>>> import numpy as np
>>> a = np.ndarray(shape=(2,3), order='F')
>>> for i in xrange(6): a.itemset(i, i+1)
... 
>>> a
array([[ 1.,  2.,  3.],
       [ 4.,  5.,  6.]])
>>> a.flags
  C_CONTIGUOUS : False
  F_CONTIGUOUS : True
  OWNDATA : True
  WRITEABLE : True
  ALIGNED : True
  UPDATEIFCOPY : False

这意味着a是按列主序(F_CONTIGUOUS)排列的,因此在内部,a应该如下所示:

[1, 4, 2, 5, 3, 6]

这正是在词汇表中所述的内容。让我感到困惑的是,如果我尝试以线性方式访问a的数据,我会得到:

>>> for i in xrange(6): print a.item(i)
... 
1.0
2.0
3.0
4.0
5.0
6.0

目前我不确定 F_CONTIGUOUS 标志告诉我们什么,因为它没有遵守顺序。显然在Python中一切都是行主序的,当我们想要以线性方式迭代时,我们可以使用迭代器flat

问题如下:假设我们有一个数字列表: 1, 2, 3, 4, 5, 6 ,如何创建一个列主序的形状为(2, 3)的numpy数组?也就是说,我怎样才能得到一个看起来像这样的矩阵:

array([[ 1.,  3.,  5.],
       [ 2.,  4.,  6.]])

我希望能够线性迭代列表并将它们放入新创建的ndarray中。之所以这样做,是因为我将读取按列主序设置的多维数组文件。


你能否重新调整一下吗? - dvreed77
1
@dvreed77 reshape 只改变形状,不改变顺序。 - Kill Console
我已经查看了reshape的文档,它说:“order:{'C','F','A'},可选项。决定数组数据是被视为按行主序(C顺序)、列主序(FORTRAN顺序)还是C/FORTRAN顺序应该被保留。”所以我猜它只是改变了视图,而不是存储顺序。 - Kill Console
6个回答

51

NumPy以行优先顺序存储数据。

>>> a = np.array([[1,2,3,4], [5,6,7,8]])
>>> a.shape
(2, 4)
>>> a.shape = 4,2
>>> a
array([[1, 2],
       [3, 4],
       [5, 6],
       [7, 8]])

如果您更改形状,数据的顺序不会改变。

如果您添加一个 'F',您就可以得到想要的结果。

>>> b
array([1, 2, 3, 4, 5, 6])
>>> c = b.reshape(2,3,order='F')
>>> c
array([[1, 3, 5],
       [2, 4, 6]])

4
顺序是需要改变的。我想别无选择。我必须接受Matlab按列主导,而Python按行主导的事实。难道没有比创建一个(3, 2)数组然后使用transpose更好的方法吗? - jmlopez
@jmlopez 在创建数组时可以提供一个关键字参数 order。如果已经创建了数组,则可以使用 .reshape(order=...) 方法。详细信息请参见 https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.html - Mark
@jmlopez 只需使用该方法将Matlab数据转换为numpy格式。您应该保持标准的numpy顺序,否则在应用numpy的函数时可能会复制数据。 - Mark
1
F代表Fortran。它们有一种列式的数组类型。 - Stupid Loser

32

您的问题已经得到解答,但我想补充一下,解释一下您对"在这一点上,我不确定F_CONTIGUOUS标志告诉我们什么,因为它并没有遵循排序。"所做的观察。


item方法并没有像您想的那样直接访问数据。要实现这个目的,您应该访问data属性,这会给您一个字节字符串。

例如:

c = np.array([[1,2,3],
              [4,6,7]], order='C')

f = np.array([[1,2,3],
              [4,6,7]], order='F')

观察

print c.flags.c_contiguous, f.flags.f_contiguous
# True, True

print c.nbytes == len(c.data)
# True

现在让我们打印两者的连续数据:

nelements = np.prod(c.shape)
bsize = c.dtype.itemsize # should be 8 bytes for 'int64'
for i in range(nelements):
    bnum = c.data[i*bsize : (i+1)*bsize] # The element as a byte string.
    print np.fromstring(bnum, dtype=c.dtype)[0], # Convert to number.

这将打印:

1 2 3 4 6 7

这是我们预期的结果,因为c的顺序为'C',即它的数据按行连续存储。

另一方面,

nelements = np.prod(f.shape)
bsize = f.dtype.itemsize # should be 8 bytes for 'int64'
for i in range(nelements):
    bnum = f.data[i*bsize : (i+1)*bsize] # The element as a byte string.
    print np.fromstring(bnum, dtype=f.dtype)[0], # Convert to number.

打印

1 4 2 6 3 7

再次强调,这正是我们所期望看到的,因为f的数据是按列顺序连续存储的。


1
我喜欢这个答案,但是在Python3中np.fromstring不再像以前那样工作,导致出现TypeError: fromstring() argument 1 must be read-only bytes-like object, not memoryview。相反,我尝试使用frombuffer,如print(np.frombuffer(bnum, dtype=f.dtype)),它适用于行顺序数据,但对于列顺序数据失败,出现BufferError: memoryview: underlying buffer is not C-contiguous。因此,我还不确定如何确认底层数据顺序,如此处所述。 - taranaki
不是说这对你的例子的有效性有什么影响,但是你为什么把5漏掉了呢? - Daniel S.

9

希望能在评论中添加这条信息,但我的声誉值太低了:

Kill Console的答案给出了OP所需的解决方案,但需要注意的是,正如numpy.reshape()文档中所述(https://docs.scipy.org/doc/numpy/reference/generated/numpy.reshape.html):

请注意,返回的数组不存在内存布局(C-或Fortran连续)的保证。

因此,即使视图是按列排列的,数据本身也可能不是,这可能导致使用按列存储的数据进行计算时效率低下。也许应该这样表达:

a = np.array(np.array([1, 2, 3, 4, 5, 6]).reshape(2,3,order='F'), order='F')

提供更多的保证数据是按列存储的(请参见order参数说明)。


4

通常,numpy使用顺序来描述内存布局,但无论内存布局如何,数组的Python行为应保持一致。我认为您可以使用视图获得所需的行为。视图是与另一个数组共享内存的数组。例如:

import numpy as np

a = np.arange(1, 6 + 1)
b = a.reshape(3, 2).T

a[1] = 99
print b
# [[ 1  3  5]
#  [99  4  6]]

希望这有所帮助。

0

这里有一种简单的方法可以按照内存顺序打印数据,使用ravel()函数:

>>> import numpy as np
>>> a = np.ndarray(shape=(2,3), order='F')
>>> for i in range(6): a.itemset(i, i+1)

>>> print(a.ravel(order='K'))
[ 1.  4.  2.  5.  3.  6.]

这证实了数组以Fortran顺序存储。


0

非常古老的问题,但我觉得答案还没有提到。

只是提一下还没有被提到的几个函数:

a = np.ascontiguousarray(in_arr)
b = np.asfortranarray(in_arr)

然而,它们无法解决您的问题。以下内容可以帮助您:

a = np.ndarray(shape=(2,3), order='F')
def memory_index(*args, x):
    idx = (np.array(x.strides) / a.itemsize).dot(np.array(args))
    return int(idx)

flat_view = a.ravel(order='A')   # or order='F' to be explicit
for i, value in enumerate([1, 2, 3, 4, 5, 6]):
    flat_view[i] = value

print(a)

数组([[1.,3.,5.], [2.,4.,6.]])

显然,从memory_index中分解出重复的任务,使用简单的算术运算代替dot函数,这样可能会快得值得一试。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接