Spec-Zone.ru › pandas 0.19

Group By: split-apply-combine

По «group by» мы подразумеваем процесс, включающий один или несколько из следующих шагов

  • Разбиение данных на группы на основе определенных критериев
  • Применение функции к каждой группе независимо
  • Объединение результатов в структуру данных

Из этих шагов этап разбиения является наиболее простым. На самом деле, во многих ситуациях вы можете разбить набор данных на группы и сами что-то сделать с этими группами. На этапе применения мы можем захотеть выполнить одно из следующих действий:

  • Агрегирование: вычисление сводной статистики (или статистик) по каждой группе. Некоторые примеры:

    • Вычислить групповые суммы или средние значения
    • Вычислить размеры/количество элементов в группах
  • Преобразование: выполнение некоторых групповых вычислений и возврат данных с теми же индексами. Некоторые примеры:

    • Нормализация данных (z-оценка) внутри группы
    • Заполнение пропусков (NaN) в группах значением, полученным из каждой группы
  • Фильтрация: отбрасывание некоторых групп на основе группового вычисления, которое вычисляет Истина или Ложь. Некоторые примеры:

    • Отбрасывание данных, которые относятся к группам с небольшим количеством членов
    • Фильтрация данных на основе групповой суммы или среднего значения
  • Некоторое сочетание вышеперечисленного: GroupBy проверит результаты шага применения и попытается вернуть разумно объединённый результат, если он не подходит ни к одной из вышеупомянутых категорий

Поскольку набор методов экземпляров объектов в структурах данных pandas обычно богат и выразителен, мы часто просто хотим вызвать, например, функцию DataFrame для каждой группы. Название GroupBy должно быть достаточно знакомым тем, кто использовал инструмент на основе SQL (или itertools), в котором вы можете написать код, подобный:

SELECT Column1, Column2, mean(Column3), sum(Column4)
FROM SomeTable
GROUP BY Column1, Column2

Мы стремимся сделать такие операции естественными и лёгкими для выражения с помощью pandas. Мы рассмотрим каждый раздел функциональности GroupBy и затем предоставим некоторые нетривиальные примеры/случаи использования.

См. справочник для некоторых продвинутых стратегий

Разбиение объекта на группы

Объекты pandas можно разбить по любому из их осей. Абстрактное определение группировки заключается в предоставлении соответствия между метками и именами групп. Для создания объекта GroupBy (подробнее об объекте GroupBy позже) вы делаете следующее:

>>> grouped = obj.groupby(key)
>>> grouped = obj.groupby(key, axis=1)
>>> grouped = obj.groupby([key1, key2])

Сопоставление можно задать различными способами:

  • Функция Python, вызываемая для каждой метки оси
  • Список или массив NumPy той же длины, что и выбранная ось
  • Словарь или Series, предоставляющий label -> group name отображение
  • Для объектов DataFrame строка, указывающая столбец, который нужно использовать для группировки. Конечно df.groupby('A') это просто синтаксический сахар для df.groupby(df['A']), но это упрощает жизнь
  • Список любых из вышеперечисленных элементов

В совокупности объекты группировки называются ключами. Например, рассмотрим следующую таблицу DataFrame:

In [1]: df = pd.DataFrame({'A' : ['foo', 'bar', 'foo', 'bar',
   ...:                           'foo', 'bar', 'foo', 'foo'],
   ...:                    'B' : ['one', 'one', 'two', 'three',
   ...:                           'two', 'two', 'one', 'three'],
   ...:                    'C' : np.random.randn(8),
   ...:                    'D' : np.random.randn(8)})
   ...: 

In [2]: df
Out[2]: 
     A      B         C         D
0  foo    one  0.469112 -0.861849
1  bar    one -0.282863 -2.104569
2  foo    two -1.509059 -0.494929
3  bar  three -1.135632  1.071804
4  foo    two  1.212112  0.721555
5  bar    two -0.173215 -0.706771
6  foo    one  0.119209 -1.039575
7  foo  three -1.044236  0.271860

Мы могли бы естественным образом сгруппировать по столбцам A или B или по обоим:

In [3]: grouped = df.groupby('A')

In [4]: grouped = df.groupby(['A', 'B'])

Это разделит DataFrame по индексу (строкам). Мы также могли бы разделить по столбцам:

In [5]: def get_letter_type(letter):
   ...:     if letter.lower() in 'aeiou':
   ...:         return 'vowel'
   ...:     else:
   ...:         return 'consonant'
   ...: 

In [6]: grouped = df.groupby(get_letter_type, axis=1)

Начиная с версии 0.8, объекты Index pandas теперь поддерживают дублированные значения. Если в операции groupby используется не уникальный индекс в качестве ключа группы, все значения для одного значения индекса будут рассматриваться как принадлежащие одной группе, и поэтому в результате функций агрегирования будут содержаться только уникальные значения индекса:

In [7]: lst = [1, 2, 3, 1, 2, 3]

In [8]: s = pd.Series([1, 2, 3, 10, 20, 30], lst)

In [9]: grouped = s.groupby(level=0)

In [10]: grouped.first()
Out[10]: 
1    1
2    2
3    3
dtype: int64

In [11]: grouped.last()
Out[11]: 
1    10
2    20
3    30
dtype: int64

In [12]: grouped.sum()
Out[12]: 
1    11
2    22
3    33
dtype: int64

Обратите внимание, что разбиение не происходит до тех пор, пока это не потребуется. Создание объекта GroupBy только проверяет, что вы передали допустимое отображение.

Примечание

Многие виды сложных манипуляций с данными могут быть выражены в терминах операций GroupBy (хотя не гарантируется, что это будет наиболее эффективным способом). Вы можете проявить творческий подход к функциям сопоставления меток.

Сортировка GroupBy

По умолчанию ключи групп сортируются во время операции groupby. Однако вы можете передать sort=False для повышения потенциальной скорости:

In [13]: df2 = pd.DataFrame({'X' : ['B', 'B', 'A', 'A'], 'Y' : [1, 2, 3, 4]})

In [14]: df2.groupby(['X']).sum()
Out[14]: 
   Y
X   
A  7
B  3

In [15]: df2.groupby(['X'], sort=False).sum()
Out[15]: 
   Y
X   
B  3
A  7

Обратите внимание, что groupby сохранит порядок сортировки наблюдений внутри каждой группы. Например, группы, созданные с помощью groupby() ниже, находятся в том порядке, в котором они появились в исходном DataFrame.

In [16]: df3 = pd.DataFrame({'X' : ['A', 'B', 'A', 'B'], 'Y' : [1, 4, 3, 2]})

In [17]: df3.groupby(['X']).get_group('A')
Out[17]: 
   X  Y
0  A  1
2  A  3

In [18]: df3.groupby(['X']).get_group('B')
Out[18]: 
   X  Y
1  B  4
3  B  2

Атрибуты объекта GroupBy

Атрибут groups — это словарь, ключами которого являются вычисленные уникальные группы, а соответствующие значениями — метки осей, принадлежащие каждой группе. В приведенном выше примере у нас есть:

In [19]: df.groupby('A').groups
Out[19]: 
{'bar': Int64Index([1, 3, 5], dtype='int64'),
 'foo': Int64Index([0, 2, 4, 6, 7], dtype='int64')}

In [20]: df.groupby(get_letter_type, axis=1).groups
Out[20]: 
{'consonant': Index([u'B', u'C', u'D'], dtype='object'),
 'vowel': Index([u'A'], dtype='object')}

Вызов стандартной Python-функции len для объекта GroupBy просто возвращает длину словаря groups, поэтому это в основном просто удобство:

In [21]: grouped = df.groupby(['A', 'B'])

In [22]: grouped.groups
Out[22]: 
{('bar', 'one'): Int64Index([1], dtype='int64'),
 ('bar', 'three'): Int64Index([3], dtype='int64'),
 ('bar', 'two'): Int64Index([5], dtype='int64'),
 ('foo', 'one'): Int64Index([0, 6], dtype='int64'),
 ('foo', 'three'): Int64Index([7], dtype='int64'),
 ('foo', 'two'): Int64Index([2, 4], dtype='int64')}

In [23]: len(grouped)
Out[23]: 6

GroupBy позволит выполнить автодополнение имён столбцов (и других атрибутов)

In [24]: df
Out[24]: 
            gender     height      weight
2000-01-01    male  42.849980  157.500553
2000-01-02    male  49.607315  177.340407
2000-01-03    male  56.293531  171.524640
2000-01-04  female  48.421077  144.251986
2000-01-05    male  46.556882  152.526206
2000-01-06  female  68.448851  168.272968
2000-01-07    male  70.757698  136.431469
2000-01-08  female  58.909500  176.499753
2000-01-09  female  76.435631  174.094104
2000-01-10    male  45.306120  177.540920

In [25]: gb = df.groupby('gender')
In [26]: gb.<TAB>
gb.agg        gb.boxplot    gb.cummin     gb.describe   gb.filter     gb.get_group  gb.height     gb.last       gb.median     gb.ngroups    gb.plot       gb.rank       gb.std        gb.transform
gb.aggregate  gb.count      gb.cumprod    gb.dtype      gb.first      gb.groups     gb.hist       gb.max        gb.min        gb.nth        gb.prod       gb.resample   gb.sum        gb.var
gb.apply      gb.cummax     gb.cumsum     gb.fillna     gb.gender     gb.head       gb.indices    gb.mean       gb.name       gb.ohlc       gb.quantile   gb.size       gb.tail       gb.weight

GroupBy с MultiIndex

С данными с иерархически индексированными данными вполне естественно сгруппировать данные по одному из уровней иерархии.

Давайте создадим Series с двухуровневым MultiIndex.

In [27]: arrays = [['bar', 'bar', 'baz', 'baz', 'foo', 'foo', 'qux', 'qux'],
   ....:           ['one', 'two', 'one', 'two', 'one', 'two', 'one', 'two']]
   ....: 

In [28]: index = pd.MultiIndex.from_arrays(arrays, names=['first', 'second'])

In [29]: s = pd.Series(np.random.randn(8), index=index)

In [30]: s
Out[30]: 
first  second
bar    one      -0.575247
       two       0.254161
baz    one      -1.143704
       two       0.215897
foo    one       1.193555
       two      -0.077118
qux    one      -0.408530
       two      -0.862495
dtype: float64

Затем мы можем сгруппировать по одному из уровней в s.

In [31]: grouped = s.groupby(level=0)

In [32]: grouped.sum()
Out[32]: 
first
bar   -0.321085
baz   -0.927807
foo    1.116437
qux   -1.271025
dtype: float64

Если в MultiIndex указаны имена, их можно передать вместо номера уровня:

In [33]: s.groupby(level='second').sum()
Out[33]: 
second
one   -0.933926
two   -0.469555
dtype: float64

Функции агрегирования, такие как sum , примут параметр уровня напрямую. Кроме того, имя результирующего индекса будет задано в соответствии с выбранным уровнем:

In [34]: s.sum(level='second')
Out[34]: 
second
one   -0.933926
two   -0.469555
dtype: float64

Также начиная с версии 0.6, поддерживается группировка по нескольким уровням.

In [35]: s
Out[35]: 
first  second  third
bar    doo     one      1.346061
               two      1.511763
baz    bee     one      1.627081
               two     -0.990582
foo    bop     one     -0.441652
               two      1.211526
qux    bop     one      0.268520
               two      0.024580
dtype: float64

In [36]: s.groupby(level=['first', 'second']).sum()
Out[36]: 
first  second
bar    doo       2.857824
baz    bee       0.636499
foo    bop       0.769873
qux    bop       0.293100
dtype: float64

Подробнее о функции sum и агрегировании позже.

Выбор столбцов DataFrame в GroupBy

После создания объекта GroupBy из DataFrame, например, вы можете захотеть сделать что-то другое для каждого из столбцов. Таким образом, используя [] аналогично получению столбца из DataFrame, вы можете сделать:

In [37]: grouped = df.groupby(['A'])

In [38]: grouped_C = grouped['C']

In [39]: grouped_D = grouped['D']

Это в основном синтаксический сахар для альтернативного и гораздо более подробного варианта:

In [40]: df['C'].groupby(df['A'])
Out[40]: <pandas.core.groupby.SeriesGroupBy object at 0x7ff26f58b810>

Кроме того, этот метод позволяет избежать повторного вычисления внутренней информации о группировке, полученной из переданного ключа.

Итерация по группам

С объектом GroupBy в руках итерация по сгруппированным данным очень естественна и работает аналогично itertools.groupby:

In [41]: grouped = df.groupby('A')

In [42]: for name, group in grouped:
   ....:        print(name)
   ....:        print(group)
   ....: 
bar
     A      B         C         D
1  bar    one -0.042379 -0.089329
3  bar  three -0.009920 -0.945867
5  bar    two  0.495767  1.956030
foo
     A      B         C         D
0  foo    one -0.919854 -1.131345
2  foo    two  1.247642  0.337863
4  foo    two  0.290213 -0.932132
6  foo    one  0.362949  0.017587
7  foo  three  1.548106 -0.016692

В случае группировки по нескольким ключам имя группы будет кортежем:

In [43]: for name, group in df.groupby(['A', 'B']):
   ....:        print(name)
   ....:        print(group)
   ....: 
('bar', 'one')
     A    B         C         D
1  bar  one -0.042379 -0.089329
('bar', 'three')
     A      B        C         D
3  bar  three -0.00992 -0.945867
('bar', 'two')
     A    B         C        D
5  bar  two  0.495767  1.95603
('foo', 'one')
     A    B         C         D
0  foo  one -0.919854 -1.131345
6  foo  one  0.362949  0.017587
('foo', 'three')
     A      B         C         D
7  foo  three  1.548106 -0.016692
('foo', 'two')
     A    B         C         D
2  foo  two  1.247642  0.337863
4  foo  two  0.290213 -0.932132

Это стандартный Python-приём, но помните, что вы можете распаковать кортеж в операторе цикла for, если хотите: for (k1, k2), group in grouped:.

Выбор группы

Отдельную группу можно выбрать с помощью GroupBy.get_group():

In [44]: grouped.get_group('bar')
Out[44]: 
     A      B         C         D
1  bar    one -0.042379 -0.089329
3  bar  three -0.009920 -0.945867
5  bar    two  0.495767  1.956030

Или для объекта, сгруппированного по нескольким столбцам:

In [45]: df.groupby(['A', 'B']).get_group(('bar', 'one'))
Out[45]: 
     A    B         C         D
1  bar  one -0.042379 -0.089329

Агрегирование

После создания объекта GroupBy доступно несколько методов для выполнения вычислений на сгруппированных данных.

Очевидным является агрегирование с помощью метода aggregate или, что эквивалентно, agg:

In [46]: grouped = df.groupby('A')

In [47]: grouped.aggregate(np.sum)
Out[47]: 
            C         D
A                      
bar  0.443469  0.920834
foo  2.529056 -1.724719

In [48]: grouped = df.groupby(['A', 'B'])

In [49]: grouped.aggregate(np.sum)
Out[49]: 
                  C         D
A   B                        
bar one   -0.042379 -0.089329
    three -0.009920 -0.945867
    two    0.495767  1.956030
foo one   -0.556905 -1.113758
    three  1.548106 -0.016692
    two    1.537855 -0.594269

Как видите, результатом агрегирования будет именование групп в качестве нового индекса вдоль сгруппированной оси. В случае нескольких ключей результат по умолчанию представляет собой MultiIndex, хотя это можно изменить, используя параметр as_index:

In [50]: grouped = df.groupby(['A', 'B'], as_index=False)

In [51]: grouped.aggregate(np.sum)
Out[51]: 
     A      B         C         D
0  bar    one -0.042379 -0.089329
1  bar  three -0.009920 -0.945867
2  bar    two  0.495767  1.956030
3  foo    one -0.556905 -1.113758
4  foo  three  1.548106 -0.016692
5  foo    two  1.537855 -0.594269

In [52]: df.groupby('A', as_index=False).sum()
Out[52]: 
     A         C         D
0  bar  0.443469  0.920834
1  foo  2.529056 -1.724719

Обратите внимание, что вы можете использовать функцию reset_index DataFrame, чтобы добиться того же результата, поскольку имена столбцов хранятся в результирующем MultiIndex:

In [53]: df.groupby(['A', 'B']).sum().reset_index()
Out[53]: 
     A      B         C         D
0  bar    one -0.042379 -0.089329
1  bar  three -0.009920 -0.945867
2  bar    two  0.495767  1.956030
3  foo    one -0.556905 -1.113758
4  foo  three  1.548106 -0.016692
5  foo    two  1.537855 -0.594269

Ещё один простой пример агрегирования — вычисление размера каждой группы. Это включено в GroupBy в качестве метода size . Он возвращает Series, индексом которого являются имена групп, а значениями — размеры каждой группы.

In [54]: grouped.size()
Out[54]: 
A    B    
bar  one      1
     three    1
     two      1
foo  one      2
     three    1
     two      2
dtype: int64
In [55]: grouped.describe()
Out[55]: 
                C         D
0 count  1.000000  1.000000
  mean  -0.042379 -0.089329
  std         NaN       NaN
  min   -0.042379 -0.089329
  25%   -0.042379 -0.089329
  50%   -0.042379 -0.089329
  75%   -0.042379 -0.089329
...           ...       ...
5 mean   0.768928 -0.297134
  std    0.677005  0.898022
  min    0.290213 -0.932132
  25%    0.529570 -0.614633
  50%    0.768928 -0.297134
  75%    1.008285  0.020364
  max    1.247642  0.337863

[48 rows x 2 columns]

Примечание

Функции агрегирования не будут возвращать группы, по которым вы агрегируете, если они являются именованными столбцами, когда as_index=True, по умолчанию. Сгруппированные столбцы будут индексами возвращаемого объекта.

Передача as_index=False будет возвращать группы, по которым вы агрегируете, если они являются именованными столбцами.

Функции агрегирования — это функции, которые уменьшают размер возвращаемых объектов, например: mean, sum, size, count, std, var, sem, describe, first, last, nth, min, max. Это происходит, когда вы делаете, например, DataFrame.sum() и получаете Series.

nth может выполнять функцию редуктора или фильтра, см. здесь

Применение нескольких функций одновременно

С сгруппированными Series вы также можете передать список или словарь функций для агрегирования, выведя DataFrame:

In [56]: grouped = df.groupby('A')

In [57]: grouped['C'].agg([np.sum, np.mean, np.std])
Out[57]: 
          sum      mean       std
A                                
bar  0.443469  0.147823  0.301765
foo  2.529056  0.505811  0.966450

Если передается словарь, ключи будут использоваться для именования столбцов. В противном случае будет использоваться имя функции (сохранённое в объекте функции).

In [58]: grouped['D'].agg({'result1' : np.sum,
   ....:                   'result2' : np.mean})
   ....: 
Out[58]: 
      result2   result1
A                      
bar  0.306945  0.920834
foo -0.344944 -1.724719

Для сгруппированного DataFrame вы можете передать список функций для применения к каждому столбцу, что даёт агрегированный результат с иерархическим индексом:

In [59]: grouped.agg([np.sum, np.mean, np.std])
Out[59]: 
            C                             D                    
          sum      mean       std       sum      mean       std
A                                                              
bar  0.443469  0.147823  0.301765  0.920834  0.306945  1.490982
foo  2.529056  0.505811  0.966450 -1.724719 -0.344944  0.645875

Передача словаря функций имеет другое поведение по умолчанию, см. следующий раздел.

Применение различных функций к столбцам DataFrame

Передавая словарь в aggregate , вы можете применить различное агрегирование к столбцам DataFrame:

In [60]: grouped.agg({'C' : np.sum,
   ....:              'D' : lambda x: np.std(x, ddof=1)})
   ....: 
Out[60]: 
            C         D
A                      
bar  0.443469  1.490982
foo  2.529056  0.645875

Имена функций также могут быть строками. Для того, чтобы строка была валидной, она должна быть реализована в GroupBy или доступна через диспетчеризацию:

In [61]: grouped.agg({'C' : 'sum', 'D' : 'std'})
Out[61]: 
            C         D
A                      
bar  0.443469  1.490982
foo  2.529056  0.645875

Примечание

Если вы передаёте словарь в aggregate, порядок выходных столбцов не определён. Если вы хотите быть уверены, что выходные столбцы будут в определённом порядке, вы можете использовать OrderedDict. Сравните вывод следующих двух команд:

In [62]: grouped.agg({'D': 'std', 'C': 'mean'})
Out[62]: 
            C         D
A                      
bar  0.147823  1.490982
foo  0.505811  0.645875

In [63]: grouped.agg(OrderedDict([('D', 'std'), ('C', 'mean')]))
Out[63]: 
            D         C
A                      
bar  1.490982  0.147823
foo  0.645875  0.505811

Cython-оптимизированные функции агрегирования

Некоторые распространенные агрегации, в настоящее время только sum, mean, std, и sem, имеют оптимизированные реализации на Cython:

In [64]: df.groupby('A').sum()
Out[64]: 
            C         D
A                      
bar  0.443469  0.920834
foo  2.529056 -1.724719

In [65]: df.groupby(['A', 'B']).mean()
Out[65]: 
                  C         D
A   B                        
bar one   -0.042379 -0.089329
    three -0.009920 -0.945867
    two    0.495767  1.956030
foo one   -0.278452 -0.556879
    three  1.548106 -0.016692
    two    0.768928 -0.297134

Конечно, sum и mean реализованы для объектов pandas, поэтому приведенный выше код будет работать даже без специальных версий через диспетчеризацию (см. ниже).

Преобразование

Метод transform возвращает объект, индексированный так же (того же размера), как и группируемый. Таким образом, переданная функция преобразования должна возвращать результат того же размера, что и фрагмент группы. Например, предположим, что мы хотели стандартизировать данные внутри каждой группы:

In [66]: index = pd.date_range('10/1/1999', periods=1100)

In [67]: ts = pd.Series(np.random.normal(0.5, 2, 1100), index)

In [68]: ts = ts.rolling(window=100,min_periods=100).mean().dropna()

In [69]: ts.head()
Out[69]: 
2000-01-08    0.779333
2000-01-09    0.778852
2000-01-10    0.786476
2000-01-11    0.782797
2000-01-12    0.798110
Freq: D, dtype: float64

In [70]: ts.tail()
Out[70]: 
2002-09-30    0.660294
2002-10-01    0.631095
2002-10-02    0.673601
2002-10-03    0.709213
2002-10-04    0.719369
Freq: D, dtype: float64

In [71]: key = lambda x: x.year

In [72]: zscore = lambda x: (x - x.mean()) / x.std()

In [73]: transformed = ts.groupby(key).transform(zscore)

Мы ожидали, что результат теперь будет иметь среднее значение 0 и стандартное отклонение 1 в каждой группе, что мы можем легко проверить:

# Original Data
In [74]: grouped = ts.groupby(key)

In [75]: grouped.mean()
Out[75]: 
2000    0.442441
2001    0.526246
2002    0.459365
dtype: float64

In [76]: grouped.std()
Out[76]: 
2000    0.131752
2001    0.210945
2002    0.128753
dtype: float64

# Transformed Data
In [77]: grouped_trans = transformed.groupby(key)

In [78]: grouped_trans.mean()
Out[78]: 
2000    1.168208e-15
2001    1.454544e-15
2002    1.726657e-15
dtype: float64

In [79]: grouped_trans.std()
Out[79]: 
2000    1.0
2001    1.0
2002    1.0
dtype: float64

Мы также можем визуально сравнить исходные и преобразованные наборы данных.

In [80]: compare = pd.DataFrame({'Original': ts, 'Transformed': transformed})

In [81]: compare.plot()
Out[81]: <matplotlib.axes._subplots.AxesSubplot at 0x7ff26ffe62d0>
_images/groupby_transform_plot.png

Другим распространённым преобразованием данных является замена пропущенных данных средним значением группы.

In [82]: data_df
Out[82]: 
            A         B         C
0    1.539708 -1.166480  0.533026
1    1.302092 -0.505754       NaN
2   -0.371983  1.104803 -0.651520
3   -1.309622  1.118697 -1.161657
4   -1.924296  0.396437  0.812436
5    0.815643  0.367816 -0.469478
6   -0.030651  1.376106 -0.645129
..        ...       ...       ...
993  0.012359  0.554602 -1.976159
994  0.042312 -1.628835  1.013822
995 -0.093110  0.683847 -0.774753
996 -0.185043  1.438572       NaN
997 -0.394469 -0.642343  0.011374
998 -1.174126  1.857148       NaN
999  0.234564  0.517098  0.393534

[1000 rows x 3 columns]

In [83]: countries = np.array(['US', 'UK', 'GR', 'JP'])

In [84]: key = countries[np.random.randint(0, 4, 1000)]

In [85]: grouped = data_df.groupby(key)

# Non-NA count in each group
In [86]: grouped.count()
Out[86]: 
      A    B    C
GR  209  217  189
JP  240  255  217
UK  216  231  193
US  239  250  217

In [87]: f = lambda x: x.fillna(x.mean())

In [88]: transformed = grouped.transform(f)

Мы можем проверить, что средние значения групп не изменились в преобразованных данных и что преобразованные данные не содержат значений NaN.

In [89]: grouped_trans = transformed.groupby(key)

In [90]: grouped.mean() # original group means
Out[90]: 
           A         B         C
GR -0.098371 -0.015420  0.068053
JP  0.069025  0.023100 -0.077324
UK  0.034069 -0.052580 -0.116525
US  0.058664 -0.020399  0.028603

In [91]: grouped_trans.mean() # transformation did not change group means
Out[91]: 
           A         B         C
GR -0.098371 -0.015420  0.068053
JP  0.069025  0.023100 -0.077324
UK  0.034069 -0.052580 -0.116525
US  0.058664 -0.020399  0.028603

In [92]: grouped.count() # original has some missing data points
Out[92]: 
      A    B    C
GR  209  217  189
JP  240  255  217
UK  216  231  193
US  239  250  217

In [93]: grouped_trans.count() # counts after transformation
Out[93]: 
      A    B    C
GR  228  228  228
JP  267  267  267
UK  247  247  247
US  258  258  258

In [94]: grouped_trans.size() # Verify non-NA count equals group size
Out[94]: 
GR    228
JP    267
UK    247
US    258
dtype: int64

Примечание

Некоторые функции, когда применяются к объекту groupby, автоматически преобразуют входные данные, возвращая объект той же формы, что и исходный. Передача as_index=False не повлияет на эти методы преобразования.

Например: fillna, ffill, bfill, shift.

In [95]: grouped.ffill()
Out[95]: 
            A         B         C
0    1.539708 -1.166480  0.533026
1    1.302092 -0.505754  0.533026
2   -0.371983  1.104803 -0.651520
3   -1.309622  1.118697 -1.161657
4   -1.924296  0.396437  0.812436
5    0.815643  0.367816 -0.469478
6   -0.030651  1.376106 -0.645129
..        ...       ...       ...
993  0.012359  0.554602 -1.976159
994  0.042312 -1.628835  1.013822
995 -0.093110  0.683847 -0.774753
996 -0.185043  1.438572 -0.774753
997 -0.394469 -0.642343  0.011374
998 -1.174126  1.857148 -0.774753
999  0.234564  0.517098  0.393534

[1000 rows x 3 columns]

Новый синтаксис для операций window и resample

Новое в версии 0.18.1.

Работа с операциями resample, expanding или rolling на уровне groupby раньше требовала применения вспомогательных функций. Однако теперь можно использовать resample(), expanding() и rolling() в качестве методов для groupby.

В примере ниже будет применена метод rolling() к образцам столбца B, сгруппированным по столбцу A.

In [96]: df_re = pd.DataFrame({'A': [1] * 10 + [5] * 10,
   ....:                       'B': np.arange(20)})
   ....: 

In [97]: df_re
Out[97]: 
    A   B
0   1   0
1   1   1
2   1   2
3   1   3
4   1   4
5   1   5
6   1   6
.. ..  ..
13  5  13
14  5  14
15  5  15
16  5  16
17  5  17
18  5  18
19  5  19

[20 rows x 2 columns]

In [98]: df_re.groupby('A').rolling(4).B.mean()
Out[98]: 
A    
1  0      NaN
   1      NaN
   2      NaN
   3      1.5
   4      2.5
   5      3.5
   6      4.5
         ... 
5  13    11.5
   14    12.5
   15    13.5
   16    14.5
   17    15.5
   18    16.5
   19    17.5
Name: B, dtype: float64

Метод expanding() будет накапливать заданную операцию (sum() в примере) для всех членов каждой конкретной группы.

In [99]: df_re.groupby('A').expanding().sum()
Out[99]: 
         A      B
A                
1 0    1.0    0.0
  1    2.0    1.0
  2    3.0    3.0
  3    4.0    6.0
  4    5.0   10.0
  5    6.0   15.0
  6    7.0   21.0
...    ...    ...
5 13  20.0   46.0
  14  25.0   60.0
  15  30.0   75.0
  16  35.0   91.0
  17  40.0  108.0
  18  45.0  126.0
  19  50.0  145.0

[20 rows x 2 columns]

Предположим, что вы хотите использовать метод resample() для получения ежедневной частоты в каждой группе вашей таблицы данных и хотите заполнить пропущенные значения методом ffill().

In [100]: df_re = pd.DataFrame({'date': pd.date_range(start='2016-01-01',
   .....:                               periods=4,
   .....:                       freq='W'),
   .....:                      'group': [1, 1, 2, 2],
   .....:                      'val': [5, 6, 7, 8]}).set_index('date')
   .....: 

In [101]: df_re
Out[101]: 
            group  val
date                  
2016-01-03      1    5
2016-01-10      1    6
2016-01-17      2    7
2016-01-24      2    8

In [102]: df_re.groupby('group').resample('1D').ffill()
Out[102]: 
                  group  val
group date                  
1     2016-01-03      1    5
      2016-01-04      1    5
      2016-01-05      1    5
      2016-01-06      1    5
      2016-01-07      1    5
      2016-01-08      1    5
      2016-01-09      1    5
...                 ...  ...
2     2016-01-18      2    7
      2016-01-19      2    7
      2016-01-20      2    7
      2016-01-21      2    7
      2016-01-22      2    7
      2016-01-23      2    7
      2016-01-24      2    8

[16 rows x 2 columns]

Фильтрация

Новое в версии 0.12.

Метод filter возвращает подмножество исходного объекта. Предположим, мы хотим взять только элементы, принадлежащие группам с суммой группы больше 2.

In [103]: sf = pd.Series([1, 1, 2, 3, 3, 3])

In [104]: sf.groupby(sf).filter(lambda x: x.sum() > 2)
Out[104]: 
3    3
4    3
5    3
dtype: int64

Аргумент filter должен быть функцией, которая, применённая к группе целиком, возвращает True или False.

Другой полезной операцией является фильтрация элементов, принадлежащих группам с небольшим количеством членов.

In [105]: dff = pd.DataFrame({'A': np.arange(8), 'B': list('aabbbbcc')})

In [106]: dff.groupby('B').filter(lambda x: len(x) > 2)
Out[106]: 
   A  B
2  2  b
3  3  b
4  4  b
5  5  b

Вместо удаления нарушающих групп, можно вернуть объекты с аналогичной индексацией, где группы, не прошедшие фильтрацию, заполнены значениями NaN.

In [107]: dff.groupby('B').filter(lambda x: len(x) > 2, dropna=False)
Out[107]: 
     A    B
0  NaN  NaN
1  NaN  NaN
2  2.0    b
3  3.0    b
4  4.0    b
5  5.0    b
6  NaN  NaN
7  NaN  NaN

Для таблиц данных с несколькими столбцами фильтры должны явно указывать столбец как критерий фильтрации.

In [108]: dff['C'] = np.arange(8)

In [109]: dff.groupby('B').filter(lambda x: len(x['C']) > 2)
Out[109]: 
   A  B  C
2  2  b  2
3  3  b  3
4  4  b  4
5  5  b  5

Примечание

Некоторые функции, когда применяются к объекту groupby, действуют как фильтр на входных данных, возвращая уменьшенную форму исходного (и потенциально удаляя группы), но с неизменным индексом. Передача as_index=False не повлияет на эти методы преобразования.

Например: head, tail.

In [110]: dff.groupby('B').head(2)
Out[110]: 
   A  B  C
0  0  a  0
1  1  a  1
2  2  b  2
3  3  b  3
6  6  c  6
7  7  c  7

Диспетчеризация к методам экземпляра

При выполнении агрегации или преобразования вы можете просто вызвать метод экземпляра для каждой группы данных. Это довольно легко сделать, передав лямбда-функции:

In [111]: grouped = df.groupby('A')

In [112]: grouped.agg(lambda x: x.std())
Out[112]: 
            C         D
A                      
bar  0.301765  1.490982
foo  0.966450  0.645875

Но это довольно громоздко и может быть неудобно, если вам нужно передать дополнительные аргументы. Используя немного хитрости метапрограммирования, GroupBy теперь имеет возможность «диспетчеризировать» вызовы методов к группам:

In [113]: grouped.std()
Out[113]: 
            C         D
A                      
bar  0.301765  1.490982
foo  0.966450  0.645875

На самом деле здесь генерируется функция-обертка. При вызове она принимает все переданные аргументы и вызывает функцию со всеми аргументами в каждой группе (в приведенном выше примере функция std). Результаты затем объединяются в стиле agg и transform (на самом деле она использует apply для определения склеивания, документация которого приведена далее). Это позволяет выполнять некоторые операции довольно лаконично:

In [114]: tsdf = pd.DataFrame(np.random.randn(1000, 3),
   .....:                     index=pd.date_range('1/1/2000', periods=1000),
   .....:                     columns=['A', 'B', 'C'])
   .....: 

In [115]: tsdf.ix[::2] = np.nan

In [116]: grouped = tsdf.groupby(lambda x: x.year)

In [117]: grouped.fillna(method='pad')
Out[117]: 
                   A         B         C
2000-01-01       NaN       NaN       NaN
2000-01-02 -0.353501 -0.080957 -0.876864
2000-01-03 -0.353501 -0.080957 -0.876864
2000-01-04  0.050976  0.044273 -0.559849
2000-01-05  0.050976  0.044273 -0.559849
2000-01-06  0.030091  0.186460 -0.680149
2000-01-07  0.030091  0.186460 -0.680149
...              ...       ...       ...
2002-09-20  2.310215  0.157482 -0.064476
2002-09-21  2.310215  0.157482 -0.064476
2002-09-22  0.005011  0.053897 -1.026922
2002-09-23  0.005011  0.053897 -1.026922
2002-09-24 -0.456542 -1.849051  1.559856
2002-09-25 -0.456542 -1.849051  1.559856
2002-09-26  1.123162  0.354660  1.128135

[1000 rows x 3 columns]

В этом примере коллекция временных рядов разбивалась на годовые сегменты, а затем независимо вызывался метод fillna для групп.

Новое в версии 0.14.1.

Методы nlargest и nsmallest работают с группировками типа Series:

In [118]: s = pd.Series([9, 8, 7, 5, 19, 1, 4.2, 3.3])

In [119]: g = pd.Series(list('abababab'))

In [120]: gb = s.groupby(g)

In [121]: gb.nlargest(3)
Out[121]: 
a  4    19.0
   0     9.0
   2     7.0
b  1     8.0
   3     5.0
   7     3.3
dtype: float64

In [122]: gb.nsmallest(3)
Out[122]: 
a  6    4.2
   2    7.0
   0    9.0
b  5    1.0
   7    3.3
   3    5.0
dtype: float64

Гибкое apply

Некоторые операции с сгруппированными данными могут не вписываться ни в категорию агрегирования, ни в категорию преобразования. Или вы просто хотите, чтобы GroupBy определил, как комбинировать результаты. Для этого используйте функцию apply, которая может быть заменена как aggregate, так и transform во многих стандартных случаях использования. Однако apply может обрабатывать некоторые исключительные случаи использования, например:

In [123]: df
Out[123]: 
     A      B         C         D
0  foo    one -0.919854 -1.131345
1  bar    one -0.042379 -0.089329
2  foo    two  1.247642  0.337863
3  bar  three -0.009920 -0.945867
4  foo    two  0.290213 -0.932132
5  bar    two  0.495767  1.956030
6  foo    one  0.362949  0.017587
7  foo  three  1.548106 -0.016692

In [124]: grouped = df.groupby('A')

# could also just call .describe()
In [125]: grouped['C'].apply(lambda x: x.describe())
Out[125]: 
A         
bar  count    3.000000
     mean     0.147823
     std      0.301765
     min     -0.042379
     25%     -0.026149
     50%     -0.009920
     75%      0.242924
                ...   
foo  mean     0.505811
     std      0.966450
     min     -0.919854
     25%      0.290213
     50%      0.362949
     75%      1.247642
     max      1.548106
Name: C, dtype: float64

Размер возвращаемого результата также может измениться:

In [126]: grouped = df.groupby('A')['C']

In [127]: def f(group):
   .....:     return pd.DataFrame({'original' : group,
   .....:                          'demeaned' : group - group.mean()})
   .....: 

In [128]: grouped.apply(f)
Out[128]: 
   demeaned  original
0 -1.425665 -0.919854
1 -0.190202 -0.042379
2  0.741831  1.247642
3 -0.157743 -0.009920
4 -0.215598  0.290213
5  0.347944  0.495767
6 -0.142862  0.362949
7  1.042295  1.548106

apply для Series может работать с возвращаемым значением из применяемой функции, которое само по себе является серией, и возможно повысить результат до DataFrames.

In [129]: def f(x):
   .....:   return pd.Series([ x, x**2 ], index = ['x', 'x^2'])
   .....: 

In [130]: s
Out[130]: 
0     9.0
1     8.0
2     7.0
3     5.0
4    19.0
5     1.0
6     4.2
7     3.3
dtype: float64

In [131]: s.apply(f)
Out[131]: 
      x     x^2
0   9.0   81.00
1   8.0   64.00
2   7.0   49.00
3   5.0   25.00
4  19.0  361.00
5   1.0    1.00
6   4.2   17.64
7   3.3   10.89

Примечание

apply может действовать как функция сокращения, преобразования или фильтрации, в зависимости от того, что именно передано ей. Таким образом, в зависимости от выбранного пути и того, что вы группируете. Поэтому сгруппированные столбцы (столбец) могут быть включены в вывод, а также задать индексы.

Предупреждение

В текущей реализации apply дважды вызывает func для первой группы, чтобы определить, может ли он использовать быстрый или медленный путь кода. Это может привести к неожиданному поведению, если func имеет побочные эффекты, так как они будут выполняться дважды для первой группы.

In [132]: d = pd.DataFrame({"a":["x", "y"], "b":[1,2]})

In [133]: def identity(df):
   .....:     print df
   .....:     return df
   .....: 

In [134]: d.groupby("a").apply(identity)
   a  b
0  x  1
   a  b
0  x  1
   a  b
1  y  2
Out[134]: 
   a  b
0  x  1
1  y  2

Другие полезные функции

Автоматическое исключение «мешающих» столбцов

Рассмотрим снова пример DataFrame, который мы рассматривали:

In [135]: df
Out[135]: 
     A      B         C         D
0  foo    one -0.919854 -1.131345
1  bar    one -0.042379 -0.089329
2  foo    two  1.247642  0.337863
3  bar  three -0.009920 -0.945867
4  foo    two  0.290213 -0.932132
5  bar    two  0.495767  1.956030
6  foo    one  0.362949  0.017587
7  foo  three  1.548106 -0.016692

Предположим, мы хотим вычислить стандартное отклонение, сгруппированное по столбцу A. Существует небольшая проблема, а именно, нас не интересуют данные в столбце B. Мы называем это «мешающим» столбцом. Если переданная функция агрегирования не может быть применена к некоторым столбцам, проблемные столбцы будут (молча) удалены. Таким образом, это не вызывает никаких проблем:

In [136]: df.groupby('A').std()
Out[136]: 
            C         D
A                      
bar  0.301765  1.490982
foo  0.966450  0.645875

Обработка групп NaN и NaT

Если в ключе группировки есть какие-либо значения NaN или NaT, они будут автоматически исключены. Таким образом, никогда не будет группы «NaN» или «NaT». В старых версиях pandas это было не так, но пользователи обычно отбрасывали группу NaN (и поддержка этой группы была проблемой реализации).

Группировка с упорядоченными факторами

Категориальные переменные, представленные экземплярами класса Categorical pandas, могут использоваться в качестве ключей группировки. В этом случае порядок уровней будет сохранён:

In [137]: data = pd.Series(np.random.randn(100))

In [138]: factor = pd.qcut(data, [0, .25, .5, .75, 1.])

In [139]: data.groupby(factor).mean()
Out[139]: 
[-2.617, -0.684]    -1.331461
(-0.684, -0.0232]   -0.272816
(-0.0232, 0.541]     0.263607
(0.541, 2.369]       1.166038
dtype: float64

Группировка со спецификацией Grouper

Вам может потребоваться указать больше данных для правильной группировки. Вы можете использовать pd.Grouper для предоставления этого локального управления.

In [140]: import datetime

In [141]: df = pd.DataFrame({
   .....:          'Branch' : 'A A A A A A A B'.split(),
   .....:          'Buyer': 'Carl Mark Carl Carl Joe Joe Joe Carl'.split(),
   .....:          'Quantity': [1,3,5,1,8,1,9,3],
   .....:          'Date' : [
   .....:              datetime.datetime(2013,1,1,13,0),
   .....:              datetime.datetime(2013,1,1,13,5),
   .....:              datetime.datetime(2013,10,1,20,0),
   .....:              datetime.datetime(2013,10,2,10,0),
   .....:              datetime.datetime(2013,10,1,20,0),
   .....:              datetime.datetime(2013,10,2,10,0),
   .....:              datetime.datetime(2013,12,2,12,0),
   .....:              datetime.datetime(2013,12,2,14,0),
   .....:              ]
   .....:          })
   .....: 

In [142]: df
Out[142]: 
  Branch Buyer                Date  Quantity
0      A  Carl 2013-01-01 13:00:00         1
1      A  Mark 2013-01-01 13:05:00         3
2      A  Carl 2013-10-01 20:00:00         5
3      A  Carl 2013-10-02 10:00:00         1
4      A   Joe 2013-10-01 20:00:00         8
5      A   Joe 2013-10-02 10:00:00         1
6      A   Joe 2013-12-02 12:00:00         9
7      B  Carl 2013-12-02 14:00:00         3

Группировка по определённому столбцу с требуемой частотой. Это похоже на ресемплирование.

In [143]: df.groupby([pd.Grouper(freq='1M',key='Date'),'Buyer']).sum()
Out[143]: 
                  Quantity
Date       Buyer          
2013-01-31 Carl          1
           Mark          3
2013-10-31 Carl          6
           Joe           9
2013-12-31 Carl          3
           Joe           9

У вас есть неоднозначная спецификация, так как у вас есть именованный индекс и столбец, которые могут быть потенциальными группировщиками.

In [144]: df = df.set_index('Date')

In [145]: df['Date'] = df.index + pd.offsets.MonthEnd(2)

In [146]: df.groupby([pd.Grouper(freq='6M',key='Date'),'Buyer']).sum()
Out[146]: 
                  Quantity
Date       Buyer          
2013-02-28 Carl          1
           Mark          3
2014-02-28 Carl          9
           Joe          18

In [147]: df.groupby([pd.Grouper(freq='6M',level='Date'),'Buyer']).sum()
Out[147]: 
                  Quantity
Date       Buyer          
2013-01-31 Carl          1
           Mark          3
2014-01-31 Carl          9
           Joe          18

Взятие первых строк каждой группы

Так же, как для DataFrame или Series, вы можете вызвать head и tail для groupby:

In [148]: df = pd.DataFrame([[1, 2], [1, 4], [5, 6]], columns=['A', 'B'])

In [149]: df
Out[149]: 
   A  B
0  1  2
1  1  4
2  5  6

In [150]: g = df.groupby('A')

In [151]: g.head(1)
Out[151]: 
   A  B
0  1  2
2  5  6

In [152]: g.tail(1)
Out[152]: 
   A  B
1  1  4
2  5  6

Это показывает первые или последние n строк из каждой группы.

Предупреждение

До версии 0.14.0 это реализовывалось с помощью apply со сквозным проходом, поэтому результат неправильно учитывал флаг as_index:

>>> g.head(1):  # was equivalent to g.apply(lambda x: x.head(1))
      A  B
 A
 1 0  1  2
 5 2  5  6

Взятие n-ой строки каждой группы

Для выбора из DataFrame или Series n-го элемента используйте метод nth. Это метод сокращения, и он вернёт одну строку (или не вернёт строку) на группу, если вы передадите целое число для n:

In [153]: df = pd.DataFrame([[1, np.nan], [1, 4], [5, 6]], columns=['A', 'B'])

In [154]: g = df.groupby('A')

In [155]: g.nth(0)
Out[155]: 
     B
A     
1  NaN
5  6.0

In [156]: g.nth(-1)
Out[156]: 
     B
A     
1  4.0
5  6.0

In [157]: g.nth(1)
Out[157]: 
     B
A     
1  4.0

Если вы хотите выбрать n-ый непустой элемент, используйте параметр dropna. Для DataFrame это должно быть либо 'any' или 'all', как вы бы передали для dropna, для Series это просто должно быть истинным значением.

# nth(0) is the same as g.first()
In [158]: g.nth(0, dropna='any')
Out[158]: 
     B
A     
1  4.0
5  6.0

In [159]: g.first()
Out[159]: 
     B
A     
1  4.0
5  6.0

# nth(-1) is the same as g.last()
In [160]: g.nth(-1, dropna='any')  # NaNs denote group exhausted when using dropna
Out[160]: 
     B
A     
1  4.0
5  6.0

In [161]: g.last()
Out[161]: 
     B
A     
1  4.0
5  6.0

In [162]: g.B.nth(0, dropna=True)
Out[162]: 
A
1    4.0
5    6.0
Name: B, dtype: float64

Как и в других методах, передача as_index=False, приведет к фильтрации, которая возвращает сгруппированную строку.

In [163]: df = pd.DataFrame([[1, np.nan], [1, 4], [5, 6]], columns=['A', 'B'])

In [164]: g = df.groupby('A',as_index=False)

In [165]: g.nth(0)
Out[165]: 
   A    B
0  1  NaN
2  5  6.0

In [166]: g.nth(-1)
Out[166]: 
   A    B
1  1  4.0
2  5  6.0

Вы также можете выбрать несколько строк из каждой группы, указав несколько значений nth в виде списка целых чисел.

In [167]: business_dates = pd.date_range(start='4/1/2014', end='6/30/2014', freq='B')

In [168]: df = pd.DataFrame(1, index=business_dates, columns=['a', 'b'])

# get the first, 4th, and last date index for each month
In [169]: df.groupby((df.index.year, df.index.month)).nth([0, 3, -1])
Out[169]: 
        a  b
2014 4  1  1
     4  1  1
     4  1  1
     5  1  1
     5  1  1
     5  1  1
     6  1  1
     6  1  1
     6  1  1

Перечисление элементов группы

Новое в версии 0.13.0.

Чтобы увидеть порядок, в котором каждая строка появляется в своей группе, используйте метод cumcount:

In [170]: df = pd.DataFrame(list('aaabba'), columns=['A'])

In [171]: df
Out[171]: 
   A
0  a
1  a
2  a
3  b
4  b
5  a

In [172]: df.groupby('A').cumcount()
Out[172]: 
0    0
1    1
2    2
3    0
4    1
5    3
dtype: int64

In [173]: df.groupby('A').cumcount(ascending=False)  # kwarg only
Out[173]: 
0    3
1    2
2    1
3    1
4    0
5    0
dtype: int64

Использование с графиками

GroupBy также работает с некоторыми методами построения графиков. Например, предположим, что мы подозреваем, что некоторые признаки в DataFrame могут отличаться по группам, в этом случае значения в столбце 1, где группа «B», в среднем на 3 больше.

In [174]: np.random.seed(1234)

In [175]: df = pd.DataFrame(np.random.randn(50, 2))

In [176]: df['g'] = np.random.choice(['A', 'B'], size=50)

In [177]: df.loc[df['g'] == 'B', 1] += 3

Мы можем легко визуализировать это с помощью boxplot:

In [178]: df.groupby('g').boxplot()
Out[178]: 
A         Axes(0.1,0.15;0.363636x0.75)
B    Axes(0.536364,0.15;0.363636x0.75)
dtype: object
_images/groupby_boxplot.png

Результат вызова boxplot — словарь, ключами которого являются значения нашего столбца группировки g («A» и «B»). Значения результирующего словаря можно контролировать с помощью ключевого слова return_type метода boxplot. См. документацию по визуализации для получения дополнительной информации.

Предупреждение

По историческим причинам, df.groupby("g").boxplot() не эквивалентно df.boxplot(by="g"). См. здесь для объяснения.

Примеры

Группировка по фактору

Группировка столбцов DataFrame по их сумме и суммирование агрегированных значений.

In [179]: df = pd.DataFrame({'a':[1,0,0], 'b':[0,1,0], 'c':[1,0,0], 'd':[2,3,4]})

In [180]: df
Out[180]: 
   a  b  c  d
0  1  0  1  2
1  0  1  0  3
2  0  0  0  4

In [181]: df.groupby(df.sum(), axis=1).sum()
Out[181]: 
   1  9
0  2  2
1  1  3
2  0  4

Группировка по индексатору для «перевыборки» данных

Перевыборка генерирует новые гипотетические выборки (перевыборки) из уже существующих наблюдаемых данных или из модели, которая генерирует данные. Эти новые выборки похожи на предварительно существующие выборки.

Для перевыборки по индексам, которые не являются datetimelike, можно использовать следующий метод.

В следующих примерах df.index // 5 возвращает двоичный массив, который используется для определения, что выбирается для операции groupby.

Примечание

В примере ниже показано, как можно уменьшить выборку путем объединения выборок в меньшее количество выборок. Используя df.index // 5, мы агрегируем выборки в интервалах. Применяя функцию std(), мы агрегируем информацию, содержащуюся во многих выборках, в небольшой подмножество значений, которые представляют собой их стандартное отклонение, тем самым уменьшая количество выборок.

In [182]: df = pd.DataFrame(np.random.randn(10,2))

In [183]: df
Out[183]: 
          0         1
0 -0.832423  0.114059
1  1.218203 -0.890593
2  0.165445 -1.127470
3 -1.192185  0.818644
4  0.237185 -0.336384
5  0.694727  0.750161
6  0.247055  0.645433
7 -1.366120  0.313160
8  0.205207  0.089987
9  0.186062  1.314182

In [184]: df.index // 5
Out[184]: Int64Index([0, 0, 0, 0, 0, 1, 1, 1, 1, 1], dtype='int64')

In [185]: df.groupby(df.index // 5).std()
Out[185]: 
          0         1
0  0.955154  0.783648
1  0.788428  0.467576

Возвращение Series для распространения имен

Группировка столбцов DataFrame, вычисление набора метрик и возвращение именованного Series. Имя Series используется в качестве имени для индекса столбца. Это особенно полезно в сочетании с операциями изменения формы, такими как stacking, в котором имя индекса столбца будет использоваться в качестве имени вставленного столбца:

In [186]: df = pd.DataFrame({
   .....:          'a':  [0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2],
   .....:          'b':  [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1],
   .....:          'c':  [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0],
   .....:          'd':  [0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1],
   .....:          })
   .....: 

In [187]: def compute_metrics(x):
   .....:     result = {'b_sum': x['b'].sum(), 'c_mean': x['c'].mean()}
   .....:     return pd.Series(result, name='metrics')
   .....: 

In [188]: result = df.groupby('a').apply(compute_metrics)

In [189]: result
Out[189]: 
metrics  b_sum  c_mean
a                     
0          2.0     0.5
1          2.0     0.5
2          2.0     0.5

In [190]: result.stack()
Out[190]: 
a  metrics
0  b_sum      2.0
   c_mean     0.5
1  b_sum      2.0
   c_mean     0.5
2  b_sum      2.0
   c_mean     0.5
dtype: float64

© 2008–2012, AQR Capital Management, LLC, Lambda Foundry, Inc. and PyData Development Team
Licensed under the 3-clause BSD License.
https://pandas.pydata.org/pandas-docs/version/0.19.2/groupby.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API