我正在使用 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
所以关于正在排序的数据帧的某些事情导致 groupby
操作中的 MultiIndex 丢失。如果我们从最大到最小排序并调用 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
和 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
或nlargest
,而是在于apply
操作的结果在 groupby 对象上。如果 apply
操作返回的 Series 与原始 groupby 组具有相同的索引,则 pandas 返回原始索引而不是 multiIndex。
@gyoza 的解决方案在应用操作中创建一个带有新索引的系列,以确保返回 multiIndex。然而,在我的实际代码中,后面的步骤(标记每个组中最小的以供审查)取决于通过应用操作保留的原始索引。我可以将该步骤重写为对分组列的合并,而不是使用 .loc
进行索引,但我不想这样做。
最佳答案
有趣的“bug”,我想你在 pandas.SeriesGroupBy 对象中发现了排序的数据帧。
我认为我们可以使用 pandas.DataFrameGroupBy 对象(但是,我相信你那里有一个错误)。
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]})
df2 = df.sort_values('c', ascending=True)
df_sorted = df2.groupby(['a','b']).apply(lambda x: x.nsmallest(n=3, columns='c')).reset_index(drop=True)
df_unsorted = df.groupby(['a','b']).apply(lambda x: x.nsmallest(n=3, columns='c')).reset_index(drop=True)
all(df_sorted.eqw(df_unsorted)
输出:
True
打印 df_sorted 和 df_unsorted:
print(df_sorted)
a b c
0 bar bat 7
1 bar bat 9
2 bar bat 12
3 bar baz 4
4 bar baz 6
5 foo bat 5
6 foo baz 1
7 foo baz 2
8 foo baz 3
9 qux bat 8
10 qux bat 11
11 qux baz 10
打印(df_unsorted)
a b c
0 bar bat 7
1 bar bat 9
2 bar bat 12
3 bar baz 4
4 bar baz 6
5 foo bat 5
6 foo baz 1
7 foo baz 2
8 foo baz 3
9 qux bat 8
10 qux bat 11
11 qux baz 10
关于python - pandas.groupby.nsmallest 在预排序数据帧时删除 multiindex,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50770370/