当数据帧被预先排序时,pandas.groupby.nsmallest会删除多索引

时间:2018-06-09 02:23:51

标签: python pandas

我正在使用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

这种行为是预期的,还是熊猫中的错误?任何人都可以推荐错误的解决方案吗?现在,在使用groupbynsmallest之前,我只是在相反方向上对数据框进行防御性排序:

>>> 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建议的链接之后,我了解问题不在于nsmallestnlargest,而是在groupby对象上的apply操作的结果。如果apply操作返回的Series与原始groupby组具有相同的索引,则pandas将返回原始索引而不是multiIndex。

@ gyoza的解决方案在应用操作中使用新索引创建一个Series,以确保返回multiIndex。但是,在我的实际代码中,后面的步骤(标记每个组中最小的用于查看)取决于通过应用操作保留的原始索引。我可以将该步骤重写为分组列上的合并,而不是使用.loc进行索引,但我不想这样做。

1 个答案:

答案 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