在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)。)
答案 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[...]
之间没有区别。只需要将这个简单的心智模型提交到记忆中。