我正在使用 pandas (0.22.0,python 版本 3.6.4).groupby
与.nsmallest
方法查找数据帧每组中的最小项目。这是一个示例数据框:
>>> import pandas as pd
>>> df = pd.DataFrame({'a': ['foo', 'foo', 'foo', 'foo',
'bar', 'bar', 'bar', 'bar', 'bar',
'qux', 'qux', 'qux'],
'b': ['baz', 'baz', 'baz', 'bat',
'baz', 'baz', 'bat', 'bat', 'bat',
'baz', 'bat', 'bat'],
'c': [1, 3, 2, 5,
6, 4, 9, 12, 7,
10, 8, 11]})
我想要每个“a”/“b”对的“c”列中的三个最小值。我用来获取“c”列中每个组的 n 个最小值的表达式如下:
>>> (df.groupby(['a', 'b'])['c'].apply(lambda x: x.nsmallest(3))
.reset_index(level=['a', 'b']))
正如预期的那样,这将返回以下数据帧:
a b c
8 bar bat 7
6 bar bat 9
7 bar bat 12
5 bar baz 4
4 bar baz 6
3 foo bat 5
0 foo baz 1
2 foo baz 2
1 foo baz 3
10 qux bat 8
11 qux bat 11
9 qux baz 10
但是,如果数据框首先在“c”列上从最小到最大排序,就会发生奇怪的事情:
>>> df2 = df.sort_values('c', ascending=True)
>>> (df2.groupby(['a', 'b'])['c'].apply(lambda x: x.nsmallest(3))
.reset_index(level=['a', 'b']))
这将返回:
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
<ipython-input-10-2afabcab898a> in <module>()
1 (df2.groupby(['a', 'b'])['c'].apply(lambda x: x.nsmallest(3))
----> 2 .reset_index(level=['a', 'b']))
3
~\AppData\Local\Continuum\anaconda3\lib\site-packages\pandas\core\series.py in reset_index(self, level, drop, name, inplace)
1048 else:
1049 df = self.to_frame(name)
-> 1050 return df.reset_index(level=level, drop=drop)
1051
1052 def __unicode__(self):
~\AppData\Local\Continuum\anaconda3\lib\site-packages\pandas\core\frame.py in reset_index(self, level, drop, inplace, col_level, col_fill)
3339 if not isinstance(level, (tuple, list)):
3340 level = [level]
-> 3341 level = [self.index._get_level_number(lev) for lev in level]
3342 if isinstance(self.index, MultiIndex):
3343 if len(level) < self.index.nlevels:
~\AppData\Local\Continuum\anaconda3\lib\site-packages\pandas\core\frame.py in <listcomp>(.0)
3339 if not isinstance(level, (tuple, list)):
3340 level = [level]
-> 3341 level = [self.index._get_level_number(lev) for lev in level]
3342 if isinstance(self.index, MultiIndex):
3343 if len(level) < self.index.nlevels:
~\AppData\Local\Continuum\anaconda3\lib\site-packages\pandas\core\indexes\base.py in _get_level_number(self, level)
1618
1619 def _get_level_number(self, level):
-> 1620 self._validate_index_level(level)
1621 return 0
1622
~\AppData\Local\Continuum\anaconda3\lib\site-packages\pandas\core\indexes\base.py in _validate_index_level(self, level)
1615 elif level != self.name:
1616 raise KeyError('Level %s must be same as name (%s)' %
-> 1617 (level, self.name))
1618
1619 def _get_level_number(self, level):
KeyError: 'Level a must be same as name (None)'
显然,.reset_index
是问题所在,所以我们将删除它:
>>> df2.groupby(['a', 'b'])['c'].apply(lambda x: x.nsmallest(3))
我们回到这个系列:
0 1
2 2
1 3
5 4
3 5
4 6
8 7
10 8
6 9
9 10
11 11
7 12
Name: c, dtype: int64
去除reset_index
第一个示例显示了 MultiIndex:
>>> df.groupby(['a', 'b'])['c'].apply(lambda x: x.nsmallest(3))
a b
bar bat 8 7
6 9
7 12
baz 5 4
4 6
foo bat 3 5
baz 0 1
2 2
1 3
qux bat 10 8
11 11
baz 9 10
Name: c, dtype: int64
因此,有关正在排序的数据帧的某些内容导致了 MultiIndexgroupby
操作以退出。如果我们从最大到最小排序并调用,也会发生同样的情况nlargest
:
>>> df3 = df.sort_values('c', ascending=False)
>>> df3.groupby(['a', 'b'])['c'].apply(lambda x: x.nlargest(3))
7 12
11 11
9 10
6 9
10 8
8 7
4 6
3 5
5 4
1 3
2 2
0 1
Name: c, dtype: int64
如果我们尝试巧妙地使用负号,也会发生同样的事情:
>>> df3.groupby(['a', 'b'])['c'].apply(lambda x: (-x).nsmallest(3))
7 -12
11 -11
9 -10
6 -9
10 -8
8 -7
4 -6
3 -5
5 -4
1 -3
2 -2
0 -1
Name: c, dtype: int64
但如果我们使用的话就不会了nlargest
带负号:
>>> df3.groupby(['a', 'b'])['c'].apply(lambda x: (-x).nlargest(3))
a b
bar bat 8 -7
6 -9
7 -12
baz 5 -4
4 -6
foo bat 3 -5
baz 0 -1
2 -2
1 -3
qux bat 10 -8
11 -11
baz 9 -10
Name: c, dtype: int64
我已经玩过很多次了,而且我很困惑。您可能会问“如果您知道数据框会导致此错误,为什么还要对数据框进行排序?”,但这种情况会发生在nsmallest
如果其中一组碰巧按升序排序,并且nlargest
如果一个组按降序排序。这是一个简单的例子:
>>> df4 = pd.DataFrame({'a': ['foo', 'foo', 'foo', 'bar', 'bar'],
'b': ['baz', 'baz', 'bat', 'baz', 'bat'],
'c': [1, 2, 10, 4, 7]})
a b c
0 foo baz 1
1 foo baz 2
2 foo bat 10
3 bar baz 4
4 bar bat 7
>>> df4.groupby(['a', 'b'])['c'].apply(lambda x: x.nsmallest(3))
0 1
1 2
2 10
3 4
4 7
Name: c, dtype: int64
这是预期的行为,还是 pandas 中的错误?任何人都可以推荐该错误的解决方案吗?现在我只是在使用之前以相反的方向防御性地对数据框进行排序groupby
and nsmallest
:
>>> df5 = df4.sort_values('c', ascending=False)
>>> (df5.groupby(['a', 'b'])['c'].apply(lambda x: x.nsmallest(3))
.reset_index(level=['a', 'b']))
a b c
4 bar bat 7
3 bar baz 4
2 foo bat 10
0 foo baz 1
1 foo baz 2
但这似乎没有必要而且很混乱。任何想法或见解将不胜感激!
编辑 2018 年 6 月 18 日:看了 @gyoza 建议的链接后,我明白问题不在于nsmallest
or nlargest
,而不是结果apply
对 groupby 对象进行操作。如果系列返回apply
操作与原始 groupby 组具有相同的索引,pandas 返回原始索引而不是 multiIndex。
@gyoza 的解决方案在 apply 操作中使用新索引创建一个 Series,以确保返回 multiIndex。然而,在我的实际代码中,后面的步骤(标记每组中最小的以供审查)取决于通过应用操作保留的原始索引。我可以将该步骤重写为分组列上的合并,而不是使用索引.loc
,但我宁愿不这样做。