当数据帧预排序时 pandas.groupby.nsmallest 会丢弃多索引

2024-05-07

我正在使用 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,但我宁愿不这样做。


有趣的是,我认为您在带有排序数据帧的 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)

Output:

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_未排序)

      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
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

当数据帧预排序时 pandas.groupby.nsmallest 会丢弃多索引 的相关文章

随机推荐