【问题标题】:Are there rules for the interaction between numpy reshape() and transpose()?numpy reshape() 和 transpose() 之间的交互是否有规则?
【发布时间】:2019-09-11 22:47:26
【问题描述】:

我已经把这个问题放在了相当多的上下文中,希望它更容易理解,但请随意跳到实际问题。


上下文

我正在做的工作引发了这个问题:

我正在使用 API 来访问一些表格数据,这实际上是一个带标签的 N 维数组。数据以列表(实际数据值的)扁平列表的形式返回,加上不同轴及其标签的列表,例如:

raw_data = [
    ['nrm', 'nrf'],
    ['ngm', 'ngf'],
    ['nbm', 'nbf'],
    ['srm', 'srf'],
    ['sgm', 'sgf'],
    ['sbm', 'sbf'],
    ['erm', 'erf'],
    ['egm', 'egf'],
    ['ebm', 'ebf'],
    ['wrm', 'wrf'],
    ['wgm', 'wgf'],
    ['wbm', 'wbf'],
]

axes = [
    ('Gender', ['Male', 'Female']),
    ('Color', ['Red', 'Green', 'Blue']),
    ('Location', ['North', 'South', 'East', 'West']),
]

数据通常是数字,但我在这里使用了字符串,因此您可以轻松查看它如何与标签匹配,例如nrmNorth, Red, Male 的值。

当您穿过(在)一个列表时,数据通过轴 0 循环,然后当您沿着列表向下时,数据通过轴 1 和轴 2 循环,轴 1(在“内部”)变化最快,然后是 2 (对于继续“向外”工作的高维数据),即:

       axis 0 ->
a a [ # # # # # # ]
x x [ # # # # # # ]
i i [ # # # # # # ]
s s [ #  R A W  # ]
    [ # D A T A # ]
2 1 [ # # # # # # ]
↓ ↓ [ # # # # # # ]
    [ # # # # # # ]

我想重塑这些数据并将其与其标签匹配,我使用以下方法将其输出到 Pandas(多索引)DataFrame:

import numpy as np
import pandas as pd

names = [name for (name, _) in axes]
labels = [labels for (_, labels) in axes]

sizes = tuple(len(L) for L in labels)  # (2, 3, 4)
data_as_array = np.array(raw_data)  # shape = (12, 2) = (3*4, 2)
A = len(sizes)  # number of axes
new_shape = (*sizes[1:],sizes[0])  # (3, 4, 2)

data = data_as_array.reshape(new_shape, order="F").transpose(A - 1, *range(A - 1))
# With my numbers: data_as_array.reshape((3, 4, 2), order="F").transpose(2, 0, 1)

df = pd.DataFrame(
    data.ravel(),
    index=pd.MultiIndex.from_product(labels, names=names),
    columns=["Value"],
)

(我已经用 cmets 记录了我的示例中的一些特定值,但代码旨在推广到任何 N 维数据。)

这给出了:

                      Value
Gender Color Location      
Male   Red   North      nrm
             South      srm
             East       erm
             West       wrm
       Green North      ngm
             South      sgm
             East       egm
             West       wgm
       Blue  North      nbm
             South      sbm
             East       ebm
             West       wbm
Female Red   North      nrf
             South      srf
             East       erf
             West       wrf
       Green North      ngf
             South      sgf
             East       egf
             West       wgf
       Blue  North      nbf
             South      sbf
             East       ebf
             West       wbf

这一切都符合预期,您可以看到这些值最终位于正确的位置,即附加到它们的匹配标签上。


问题

我的实际问题涉及这一行:

data = data_as_array.reshape(new_shape, order="F").transpose(A - 1, *range(A - 1))

在我的示例中,具体数字是:

data = data_as_array.reshape((3, 4, 2), order="F").transpose(2, 0, 1)

经过一番实验,我发现以下三个都是等价的(第一个是原版):

data1 = data_as_array.reshape(new_shape, order="F").transpose(D - 1, *range(D - 1))
data2 = data_as_array.T.reshape(*reversed(new_shape)).T.transpose(D - 1, *range(D - 1))
data3 = data_as_array.reshape(*reversed(sizes)).T

但这让我开始思考(这终于是我的问题了!):

是否有任何规则可以用来操作表达式,例如从data1data3?

特别是,transpose()reshape() 似乎密切相关,并且可能有一种方法可以将转置的动作“吸收”到reshape() 中,以便您可以删除它或至少删除它将其转换为更整洁的.T(根据data3)。


我的尝试

我设法建立了以下规则:

a.reshape(shape, order="F") == a.T.reshape(*reversed(shape)).T

您可以将.T 应用到两边,或将a.T 替换为a 以获得它的这些变体:

a.reshape(shape) == a.T.reshape(*reversed(shape), order="F").T
a.reshape(shape).T == a.T.reshape(*reversed(shape), order="F")
a.T.reshape(shape) == a.reshape(*reversed(shape), order="F").T

a.reshape(shape, order="F") == a.T.reshape(*reversed(shape)).T
a.reshape(shape, order="F").T == a.T.reshape(*reversed(shape))
a.T.reshape(shape, order="F") == a.reshape(*reversed(shape)).T

我认为这实际上是定义行优先和列优先排序之间的差异,以及它们之间的关系。

但我没有设法做的是展示你可以如何去:

data = data_as_array.reshape((3, 4, 2), order="F").transpose(2, 0, 1)

到:

data = data_as_array.reshape((4, 3, 2))

所以以某种方式将换位放入重塑中。

但我什至不确定这是否普遍正确,或者特定于我的数据或例如3 个维度。

编辑: 澄清一下,我对直截了当的.T 转置的工作方式感到相当满意,并且上面的规则涵盖了这一点。 (请注意,.T 等效于 3 个轴的 .tranpose(2, 1, 0),或 .tranpose(n-1, n-2, ... 2, 1, 0) 等效于 n 轴的一般情况。)

这是使用 .transpose() 的情况,你正在做一个我很好奇的“部分”转置,例如.tranpose(1, 0, 2) - 你正在做的不仅仅是颠倒轴的顺序。


一些参考资料:

  • 这涵盖了主要行和主要列的差异:How do you unroll a Numpy array of (mxn) dimentions into a single vector(我可以很容易地看到我的数据是如何发生的)
  • 这个 SO 答案对解释转置非常有帮助,并且基本上也涵盖了重塑:https://stackoverflow.com/a/32034565/9219425(查看 fantastic 图表!),包括转置如何影响形状和步幅。我编写了一个算法来模拟这个过程,看看这是否会使事情变得更清晰(例如,转置可能对应于交换算法中 for 循环的顺序),但它并没有真正帮助。

【问题讨论】:

  • 按照那个 SO 链接,我建议在各种操作之后查看 x.stridesx.shape。首先查看 strides 在转置时如何变化。
  • 呵呵我确实研究过这些……相当广泛! ??????请参阅对我的问题的编辑和/或我对您的答案的评论。明天我会尝试整理算法以将其添加到我的问题中。

标签: python arrays numpy multidimensional-array


【解决方案1】:

我不打算介绍您的所有案例(目前),但这里有一个关于重塑、转置和排序如何相互作用的说明:

In [176]: x = np.arange(12)                                                                                  
In [177]: x.strides, x.shape                                                                                 
Out[177]: ((8,), (12,))
In [178]: y = x.reshape(3,4)                                                                                 
In [179]: y.strides, y.shape                                                                                 
Out[179]: ((32, 8), (3, 4))        # (32=4*8)
In [180]: z = y.T                                                                                            
In [181]: z.strides, z.shape                                                                                 
Out[181]: ((8, 32), (4, 3))         # strides has been switched
In [182]: w = x.reshape(4,3, order='F')                                                                      
In [183]: w.strides, w.shape                                                                                 
Out[183]: ((8, 32), (4, 3))
In [184]: z                                                                                                  
Out[184]: 
array([[ 0,  4,  8],
       [ 1,  5,  9],
       [ 2,  6, 10],
       [ 3,  7, 11]])
In [185]: w                                                                                                  
Out[185]: 
array([[ 0,  4,  8],
       [ 1,  5,  9],
       [ 2,  6, 10],
       [ 3,  7, 11]])

带有“F”的reshape 产生的效果与转置相同。

ravel,本质上是reshape(-1)(到1d)

In [186]: w.ravel()     # order C                                                                                          
Out[186]: array([ 0,  4,  8,  1,  5,  9,  2,  6, 10,  3,  7, 11])
In [187]: w.ravel(order='F')                                                                                 
Out[187]: array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11])

请注意,w(和z)是viewx

In [190]: w.base                                                                                             
Out[190]: array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11])
In [191]: x.__array_interface__                                                                              
Out[191]: 
{'data': (139649452400704, False),
 'strides': None,
 'descr': [('', '<i8')],
 'typestr': '<i8',
 'shape': (12,),
 'version': 3}
In [192]: w.__array_interface__                                                                              
Out[192]: 
{'data': (139649452400704, False),   # same data buffer address
 'strides': (8, 32),
 'descr': [('', '<i8')],
 'typestr': '<i8',
 'shape': (4, 3),
 'version': 3}

对于部分转置:

In [194]: x = np.arange(24)                                                                                  
In [195]: y = x.reshape(2,3,4)                                                                               
In [196]: y.strides                                                                                          
Out[196]: (96, 32, 8)
In [197]: z = y.transpose(1,0,2)                                                                             
In [198]: z                                                                                                  
Out[198]: 
array([[[ 0,  1,  2,  3],
        [12, 13, 14, 15]],

       [[ 4,  5,  6,  7],
        [16, 17, 18, 19]],

       [[ 8,  9, 10, 11],
        [20, 21, 22, 23]]])
In [199]: z.shape                                                                                            
Out[199]: (3, 2, 4)
In [200]: z.strides                                                                                          
Out[200]: (32, 96, 8)

部分转置具有置换的形状和步幅。结果既不是 F 阶,也不是 C 阶。

基数中的元素顺序:

In [201]: z.ravel(order='K')                                                                                 
Out[201]: 
array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16,
       17, 18, 19, 20, 21, 22, 23])

按行排序:

In [202]: z.ravel(order='C')                                                                                 
Out[202]: 
array([ 0,  1,  2,  3, 12, 13, 14, 15,  4,  5,  6,  7, 16, 17, 18, 19,  8,
        9, 10, 11, 20, 21, 22, 23])

按列排序:

In [203]: z.ravel(order='F')                                                                                 
Out[203]: 
array([ 0,  4,  8, 12, 16, 20,  1,  5,  9, 13, 17, 21,  2,  6, 10, 14, 18,
       22,  3,  7, 11, 15, 19, 23])

【讨论】:

  • 谢谢!实际上,我对形状和步幅进行了很多实验-但这隐藏在我对编写算法以模仿该 SO 问题中描述的内容的间接引用中。我遇到的困难是“部分”换位,即当你不只是在做 .T 而是在做 .tranpose() 并传入参数时,它不仅仅是你的轴以相反的顺序。我认为如果你不做完整的.T,我认为必须完全重新计算步幅,此时我的算法比它似乎有机会澄清的要复杂。
  • 查看部分转置的步幅,我看不到 reshape with order 可以做任何类似的事情。坦率地说,转置对我来说已经足够清楚了;我对订单的工作不多。
猜你喜欢
  • 2011-09-07
  • 2013-04-15
  • 1970-01-01
  • 1970-01-01
  • 2017-11-04
  • 1970-01-01
  • 2012-09-06
  • 2016-11-11
  • 2016-05-15
相关资源
最近更新 更多