Python 为什么熊猫小组把轴的顺序搞砸了?

Python 为什么熊猫小组把轴的顺序搞砸了?,python,pandas,Python,Pandas,在一个熊猫面板上,轴的顺序看起来真的很混乱。为什么是这样 我的意思是: In [120]: import pandas as pd In [121]: import numpy as np In [122]: pnl = pd.Panel(np.random.randn(33, 55, 77)) In [123]: pnl.shape Out[123]: (33, 55, 77) In [124]: pnl[0].shape Out[124]: (55, 77) In [125]: p

在一个熊猫面板上,轴的顺序看起来真的很混乱。为什么是这样

我的意思是:

In [120]: import pandas as pd

In [121]: import numpy as np

In [122]: pnl = pd.Panel(np.random.randn(33, 55, 77))

In [123]: pnl.shape
Out[123]: (33, 55, 77)

In [124]: pnl[0].shape
Out[124]: (55, 77)

In [125]: pnl[0][0].shape
Out[125]: (55,)
因此,它分别从轴0、1、2的形状(33、55、77)开始。伟大的如果我使用
pnl[0]
删除索引,它将删除第一个轴(长度33),并保留形状(55,77)。还是很好。但是当我用
pnl[0][0]
取下另一个索引时,它并没有像我合理预期的那样取下前两个轴(长度33,55)并留下形状(77,)。不。它决定,这一次,它将去掉最后一个轴,而不是第一个轴,并给我留下形状(55,)。呵呵?!?!为什么会这样混乱?有人能给我解释一下这背后的设计逻辑吗

另外,我真的很想使用面板,但现在我不使用它,因为这个轴的问题。它有时会使代码变得不必要的混乱

更新:

F先生在下面给出了一个答案,基本上建议一致使用
pnl.ix[…]
,而不是使用
pnl[…]
。所以,我试了一下。然而,我仍然遇到了非常奇怪/令人困惑的行为

下面是一个示例,使用与上面定义的相同的
pnl
对象:

In [220]: pnl.shape
Out[220]: (33, 55, 77)

In [221]: pnl.ix[:, 0, 0].shape
Out[221]: (33,)

In [222]: pnl.ix[0, :, 0].shape
Out[222]: (55,)

In [223]: pnl.ix[0, 0, :].shape
Out[223]: (77,)

In [224]: pnl.ix[:, :, 0].shape
Out[224]: (55, 33)

In [225]: pnl.ix[:, 0, :].shape
Out[225]: (77, 33)

In [226]: pnl.ix[0, :, :].shape
Out[226]: (55, 77)

当我取下2个轴,只留下1个轴(上面的命令221-223)时,一切看起来都很棒。但是,当我取下一个轴,留下两个轴(上面的命令224-226)时,得到的形状再次变得毫无意义。很难理解和习惯结果形状如何神奇地交换轴顺序,但只是有时!(具体来说,命令226的结果形状(55,77)与我的预期相符。但是,在命令224中,我预期结果形状(33,55)不是(55,33);在命令225中,我预期结果形状(33,77)不是(77,33)。

问题在于项获取语法(使用方括号获取维度
[]
)这不是你想要的那种东西。您要做的是确保按照指定的维度对数据进行子索引

为此,您可以使用
ix

 pnl.ix[0, 0].shape
 (77,)
通过查看您尝试过的每种方法的
类型
,您可以了解到这一点:

In [71]: type(pnl.ix[0, 0])
Out[71]: pandas.core.series.Series

In [72]: type(pnl.ix[0])
Out[72]: pandas.core.frame.DataFrame

In [73]: type(pnl[0])
Out[73]: pandas.core.frame.DataFrame

特别是最后两个正在查看相同的子数据表,但是考虑到:

之间的区别。
(pnl[0])[0]
# Or, (pnl.ix[0])[0]

在第一种情况下,您说的是“嘿,继续并完全执行操作”
pnl[0]
,然后返回任何内容,然后继续并再次获取第0个元素”

由于
pnl[0]
是一个数据帧,因此额外的
[0]
项get操作将与任何旧数据帧的
df[0]
操作相同,如果该列存在,它将尝试提取该列。列维度将是结果数据帧的第一个维度,这就是为什么它的长度为55,而不是行长度为77

主要的一点是,在Python中,说
foo[x]
仅仅意味着“调用
foo
的特殊
\uuuu getitem\uuuu
方法,并将
x
作为参数”,仅此而已。如果与DataFrame一样,它有一个特殊的约定(例如引用列),与您在数学表示法中可能期望的不同(在这种情况下,它将沿着第一个轴引用一个项目,而不管其形状或结构如何),那么这只是一个实现细节

例如,对于纯NumPy数组,重复项获取会实现您所期望的:

In [90]: pnl.values[0][0].shape
Out[90]: (77,)
这并不意味着这是一种“正确”的方式。这只是一种恰好符合数学线性代数某些惯例的方法。由于DataFrame寻求表示关系数据模型,而不是纯粹的多维数组,因此没有理由期望Pandas必须在这种行为中模仿NumPy

添加了两个以上的维度

对于2维以上的数据,这些切片操作与原始三维面板中的布局方式相比,代表了数据的隐式换位。因此,Pandas必须做一些事情来解析子选定数据的布局,而在这样做时,Pandas似乎没有以保证轴从左到右顺序得以保留的方式实现切片方法

因此,当数据以块的形式排列时,它似乎独立于它从父面板数据存储的内容来确定新的长轴(索引)轴

例如,我创建了一个具有相同形状的随机数据集,我看到:

In [22]: pnl.ix[:, 0, :]._data
Out[22]: 
BlockManager
Items: Int64Index([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16,
            17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32],
           dtype='int64')
Axis 1: Int64Index([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16,
            17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33,
            34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
            51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67,
            68, 69, 70, 71, 72, 73, 74, 75, 76],
           dtype='int64')
FloatBlock: slice(0, 33, 1), 33 x 77, dtype: float64
特别注意最后一行,它说它确实知道它是一个33 x 77块。然而,当我们看到该块的
DataFrame
表示时:

In [23]: pnl.ix[:, 0, :].shape
Out[23]: (77, 33)

所以你说的很对,熊猫重新确定轴顺序的任意和未记录的过程是有问题的。这个例子应该正确地作为一个bug归档,要么是因为没有保留轴顺序,要么是因为没有记录用于确定将生成哪个顺序的任何条件。熊猫队应该提供一个或另一个。

我刚刚想出了一个有效的心理模型!好消息是我想我现在可以使用Panel了,因为这个心智模型实际上非常简单

我使用的是
pnl[…]
还是
pnl.ix[…]
,这也无关紧要。这个简单的心理模型在所有情况下都能正确地解释行为

这是模型:对于3维(如面板),只需想象轴是(第一、第三、第二)。对于像DataFrame这样的二维,只需想象轴是(第二个,第一个)。这种轴的顺序既适用于形状元组,也适用于使用多个索引时的索引顺序

现在,我将演示我在原始问题中键入的所有命令(这些命令似乎给出了无意义的结果)在心智模型中的意义(我将在每个问题的末尾添加注释)
In [23]: pnl.ix[:, 0, :].shape
Out[23]: (77, 33)
In [122]: pnl = pd.Panel(np.random.randn(33, 55, 77))  # mental: (first@33, second@77, third@55)

In [123]: pnl.shape
Out[123]: (33, 55, 77)  # mental: (first@33, second@77, third@55)

In [124]: pnl[0].shape  # mental: pnl[first=0]
Out[124]: (55, 77)  # mental: (first@77, second@55) was previously second@77,third@55

In [125]: pnl[0][0].shape  # mental: pnl[first=0][second=0]
Out[125]: (55,)  # mental: (first@55,) was previously third@55

...

In [220]: pnl.shape
Out[220]: (33, 55, 77)  # mental: (first@33, second@77, third@55)

In [221]: pnl.ix[:, 0, 0].shape  # mental: pnl.ix[:, third=0, second=0]
Out[221]: (33,)  # mental: (first@33,) was previously first@33

In [222]: pnl.ix[0, :, 0].shape  # mental: pnl.ix[first=0, :, second=0]
Out[222]: (55,)  # mental: (first@55,) was previously third@55

In [223]: pnl.ix[0, 0, :].shape  # mental: pnl.ix[first=0, third=0, :]
Out[223]: (77,)  # mental: (first@77,) was previously second@77

In [224]: pnl.ix[:, :, 0].shape  # mental: pnl.ix[:, :, second=0]
Out[224]: (55, 33)  # mental: (first@33, second@55) was previously first@33,third@55

In [225]: pnl.ix[:, 0, :].shape  # mental: pnl.ix[:, third=0, :]
Out[225]: (77, 33)  # mental: (first@33, second@77) was previously first@33,second@77

In [226]: pnl.ix[0, :, :].shape  # mental: pnl.ix[first=0, :, :]
Out[226]: (55, 77)  # mental: (first@77, second@55) was previously second@77,third@55