我正在使用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
这种行为是预期的,还是熊猫中的错误?任何人都可以推荐错误的解决方案吗?现在,在使用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
但这似乎没有必要和混乱。非常感谢任何想法或见解!
编辑06/18/18:
在查看了@gyoza建议的链接之后,我了解问题不在于nsmallest
或nlargest
,而是在groupby对象上的apply
操作的结果。如果apply
操作返回的Series与原始groupby组具有相同的索引,则pandas将返回原始索引而不是multiIndex。
@ gyoza的解决方案在应用操作中使用新索引创建一个Series,以确保返回multiIndex。但是,在我的实际代码中,后面的步骤(标记每个组中最小的用于查看)取决于通过应用操作保留的原始索引。我可以将该步骤重写为分组列上的合并,而不是使用.loc
进行索引,但我不想这样做。
答案 0 :(得分:0)
有趣的“ 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