为什么Pandas Panel搞砸了轴订单?

时间:2015-08-15 20:22:34

标签: python pandas

在Pandas Panel中,轴顺序看起来真的搞砸了。为什么会这样?

这就是我的意思:

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,)正如我所预期的那样。不。它决定,这一次,它将取消 last 轴而不是第一个轴,并留下形状(55,)。咦?!?!为什么这么搞砸了?有人可以向我解释一下这背后的设计逻辑。

PS。我真的很想使用Panel,但是由于这个轴问题,我现在没有使用它。它有时使代码不必要地混淆。

更新

先生。 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)时,一切看起来都很棒。但是,当我取下1轴离开2轴(上面的命令224-226)时,产生的形状再次无意义。它很难理解并习惯了结果形状如何神奇地交换轴顺序,但有时只是! (具体来说,命令226的结果形状(55,77)与我的期望相符。但是,在命令224中,我希望结果形状(33,55)不是(55,33);在命令225中,我会期望结果形状(33,77)不是(77,33)。)

2 个答案:

答案 0 :(得分:2)

问题是item-getter语法(使用方括号[]获取维度)不是您想要的那种。您想要的是确保您沿着指定的维度子索引到数据中。

为此,您可以使用ix

 pnl.ix[0, 0].shape
 (77,)

通过查看您尝试过的每件事的type,您可以深入了解这一点:

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

特别是最后两个正在查看相同的子DataFrame,但请考虑以下区别:

(pnl[0])[0]
# Or, (pnl.ix[0])[0]

pnl.ix[0, 0] 
# Or, (pnl.ix[0]).ix[0]

在第一种情况下,你说“嘿,继续并完全执行操作'pnl[0]'然后返回任何内容,然后继续继续进行操作&item-再次获得第0个元素“。

由于pnl[0]是一个DataFrame,因此对于任何旧的DataFrame,额外的[0] item-get操作将与df[0]相同,如果存在,则会尝试提取该列。列维度将是结果DataFrame的第一个维度,这就是长度为55而不是行长度为77的原因。

主要的一点是,在Python中,foo[x]只是表示“使用__getitem__作为参数调用foo的特殊x方法”,仅此而已。如果像DataFrame那样,它有一个特殊的约定(例如引用),这个约定与你在数学符号中所期望的不同(在这种情况下它会引用中的项目)第一个轴,无论形状或结构如何),这只是一个实现细节。

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

In [90]: pnl.values[0][0].shape
Out[90]: (77,)

这并不能使这成为做任何事情的“正确”方式。它只是 a 方式恰好符合数学线性代数的某些约定。由于DataFrame试图表示关系数据模型而不是纯粹的多维数组,因此没有理由认为Pandas必须在此行为中模拟NumPy。

已添加超过2个维度

对于超过2个维度,这些切片操作表示与原始3-D面板中的布局方式相比,数据的隐式转置。因此,Pandas必须做一些事情来解决子选择数据的布局,并且似乎在这样做时,Pandas只是没有实现切片方法以保证从左到右的顺序轴被保留。

因此,当数据以块的形式排列时,它似乎决定了新的主(索引)轴,而与它从父Panel数据中存储的内容无关。

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

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)

所以你非常正确,Pandas重新确定轴排序的任意和无证的过程是有问题的。这个例子应该正确地归档为bug,因为轴命令没有被保留,或者因为用于确定将产生哪个排序的条件没有记录。熊猫队应该提供一个或另一个。

答案 1 :(得分:0)

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

我使用pnl[...]还是pnl.ix[...]并不重要。这个简单的心理模型在所有情况下都能正确解释行为!

这是模型:对于像Panel这样的3个维度,想象一下轴是(第一,第三,第二)。对于像DataFrame这样的2个维度,想象一下轴是(第二个,第一个)。轴的这种排序既适用于形状元组,也适用于使用多个索引时的索引顺序。

现在我将演示我在原始问题中输入的所有命令,它们似乎给出了无意义的结果,在心理模型中完全有意义(我将在每行的末尾添加注释):

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

总之,现在一切都有意义(以扭曲的方式)。 pnl[...]pnl.ix[...]之间没有区别。只需要将这个简单的心智模型提交到记忆中。