pandas.groupby.nsmallest удаляет мультииндекс, когда предварительно отсортирован фрейм данных - PullRequest
0 голосов
/ 09 июня 2018

Я использую pandas (0.22.0, python version 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]})

Я хочу три наименьших значения в столбце 'c' для каждой пары 'a' / 'b'.Выражение, которое я использую для получения n-наименьших значений для каждой группы в столбце 'c', выглядит следующим образом:

>>> (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

Итак, что-то в отсортированном фрейме данных вызвало выпадение MultiIndex из операции groupby.То же самое происходит, если мы сортируем от наибольшего к наименьшему и вызываем 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

Но это кажется ненужным и грязным.Будем благодарны за любые идеи или идеи!

Редактировать 18.06.18: Просматривая ссылки, предложенные @gyoza, я понимаю, что проблема не в nsmallest или nlargest скорее с результатами операции apply над объектом groupby.Если ряд, возвращаемый операцией apply, имеет тот же индекс, что и исходная группа groupby, pandas возвращает исходный индекс, а не multiIndex.

Решение @ gyoza создает серию в операции apply с новым индексомчтобы гарантировать, что multiIndex возвращается.В моем реальном коде, однако, более поздний шаг (который помечает наименьшее в каждой группе для просмотра) зависит от исходного индекса, сохраняемого с помощью операции apply.Я мог бы переписать этот шаг как объединение столбцов группировки вместо индексации с .loc, но я бы предпочел этого не делать.

1 Ответ

0 голосов
/ 20 августа 2019

Интересно, «ошибка», я думаю, вы нашли здесь в 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...