python - pandas.groupby.nsmallest 在预排序数据帧时删除 multiindex

标签 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

这种行为是预期的,还是 pandas 中的错误?任何人都可以推荐错误的解决方案吗?现在,在使用 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

但这似乎是不必要的和困惑的。非常感谢任何想法或见解!

编辑 2018 年 6 月 18 日: 查看@gyoza 建议的链接后,我了解到问题不在于nsmallestnlargest,而是在于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/

相关文章:

python - 创建关系时从 2 个不同的目录读取到 pandas 数据帧中

python - Pandas DataFrame 括号访问器 [ ] 更喜欢列还是行?

python - 自动将数据帧的列标题传递到一列中,并将相应的值作为新列

python - 如何根据多列的值拆分数据框

当条件满足时,Python break 语句不会终止程序

python - 在这种情况下如何使用泛函?

python - 添加具有不同列名的两个 Pandas 系列的值

python - Pandas 数据框列减法,处理 NaN

python - 为什么 __init__.py 将文件夹中的所有文件作为模块导入?

python - PyTorch 反向传播的数值等效性