1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/it-ebooks-pyda-2e-zh

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
В этом репозитории не указан файл с открытой лицензией (LICENSE). При использовании обратитесь к конкретному описанию проекта и его зависимостям в коде.
Клонировать/Скачать
10.md 32 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
gitlife-traslator Отправлено 28.11.2024 16:49 0ce2ae9

Группировка и агрегация данных

Группировка данных и применение функций к каждой группе — важный этап в анализе данных. После того как данные загружены, объединены и подготовлены, обычно выполняются расчёты статистических показателей для групп или создаются сводные таблицы.

Pandas предоставляет гибкий и эффективный инструмент groupby, который позволяет легко разрезать, нарезать и суммировать данные. Реляционные базы данных и SQL (Structured Query Language) популярны отчасти благодаря их способности удобно объединять, фильтровать, преобразовывать и группировать данные. Однако SQL-запросы могут выполнять ограниченный набор групповых операций. В этой главе вы увидите, что благодаря мощным возможностям выражения Python и pandas мы можем выполнять гораздо более сложные групповые операции, используя любую функцию, которая принимает pandas-объект или массив NumPy. В этой главе вы узнаете:

  • Как разделить pandas-объекты на группы с помощью одного или нескольких ключей (которые могут быть функциями, массивами или именами столбцов DataFrame).
  • Как вычислять статистические показатели для групп, такие как количество, среднее значение или стандартное отклонение, а также пользовательские функции.
  • Как применять преобразования или другие операции внутри групп, например стандартизацию, линейную регрессию, ранжирование или выбор подмножеств.
  • Как создавать сводные или кросс-таблицы.
  • Как выполнять квантильный анализ и другие статистические групповые анализы.

Примечание: Группировка временных рядов данных (одна из специальных функций groupby) также называется повторной выборкой (resampling), и она будет рассмотрена отдельно в главе 11.

10.1 Механизм GroupBy

Hadley Wickham (автор многих популярных пакетов R) придумал термин «split-apply-combine» для обозначения групповых операций. На первом этапе объекты pandas (Series, DataFrame или другие) разбиваются на несколько групп в соответствии с предоставленными ключами. Разделение выполняется по определённой оси объекта. Например, DataFrame можно разделить по строкам (axis=0) или столбцам (axis=1). Затем функция применяется к каждой группе, создавая новое значение. Наконец, результаты выполнения функции объединяются в конечный результат. Форма результата обычно зависит от выполняемой операции над данными. Рисунок 10-1 показывает простой процесс группировки и агрегирования.

Рисунок 10–1: Иллюстрация простого процесса группировки и агрегирования

Ключи для разделения могут иметь различные формы и типы:

  • Список или массив, длина которого совпадает с осью разделения.
  • Имя столбца DataFrame.
  • Словарь или Series, предоставляющий соответствие между значениями на оси разделения и именами групп.
  • Функция, обрабатывающая индексы оси или метки на них.

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

In [10]: df = pd.DataFrame({'key1' : ['a', 'a', 'b', 'b', 'a'],
   ....:                    'key2' : ['one', 'two', 'one', 'two', 'one'],
   ....:                    'data1' : np.random.randn(5),
   ....:                    'data2' : np.random.randn(5)})

In [11]: df
Out[11]: 
      data1     data2 key1 key2
0 -0.204708  1.393406    a  one
1  0.478943  0.092908    a  two
2 -0.519439  0.281746    b  one
3 -0.555730  0.769023    b  two
4  1.965781  1.246435    a  one

Предположим, вы хотите сгруппировать данные по key1 и вычислить среднее значение data1. Существует несколько способов сделать это, но мы будем использовать: доступ к data1 и вызов groupby с ключом df['key1']:

In [12]: grouped = df['data1'].groupby(df['key1'])

In [13]: grouped
Out[13]: <pandas.core.groupby.SeriesGroupBy object at 0x7faa31537390>

Переменная grouped является объектом GroupBy. Фактически, он ещё не выполнил никаких вычислений, а просто содержит промежуточную информацию о ключах разделения df['key1']. Другими словами, этот объект уже имеет всё необходимое для выполнения операций над каждой группой. Например, мы можем вызвать метод mean объекта GroupBy для вычисления среднего значения по группам:

In [14]: grouped.mean()
Out[14]: 
key1
a    0.746672
b   -0.537585
Name: data1, dtype: float64

Позже я подробно объясню процесс вызова .mean(). Здесь важно то, что данные (Series) были сгруппированы по ключу и создали новый Series с индексом, состоящим из уникальных значений key1. Имя индекса «key1» происходит от исходного столбца DataFrame с таким же именем.

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

In [15]: means = df['data1'].groupby([df['key1'], df['key2']]).mean()

In [16]: means
Out[16]: 
key1  key2
a     one     0.880536
      two     0.478943
b     one    -0.519439
      two    -0.555730
Name: data1, dtype: float64

Здесь мы сгруппировали данные по двум ключам, получив Series с многоуровневым индексом (состоящим из уникальных пар ключ-значение):

In [17]: means.unstack()
Out[17]: 
key2       one       two
key1                    
a     0.880536  0.478943
b    -0.519439 -0.555730

В этом примере ключи группировки являются Series. На самом деле, ключи могут быть любыми подходящими массивами:

In [18]: states = np.array(['Ohio', 'California', 'California', 'Ohio', 'Ohio'])

In [19]: years = np.array([2005, 2005, 2006, 2005, 2006])

In [20]: df['data1'].groupby([states, years]).mean()
Out[20]: 
California  2005    0.478943
            2006   -0.519439
Ohio        2005   -0.380219
            2006    1.965781
Name: data1, dtype: float64

Обычно информация о группировке находится в том же DataFrame, который нужно обработать. Здесь вы также можете использовать имена столбцов (строки, числа или другие объекты Python) в качестве ключей группировки:

In [21]: df.groupby('key1').mean()
Out[21]: 
         data1     data2
key1
a     0.746672  0.910916
b    -0.537585  0.525384

In [22]: df.groupby(['key1', 'key2']).mean()
Out[22]: 
              data1     data2
key1 key2                    
a    one   0.880536  1.319920
     two   0.478943  0.092908
b    one  -0.519439  0.281746
     two  -0.555730  0.769023

Возможно, вы заметили, что в первом примере при выполнении df.groupby('key1').mean(), результат не содержит столбец key2. Это потому, что df['key2'] не является числовыми данными («мешающий столбец»), поэтому он исключается из результата. По умолчанию все числовые столбцы объединяются, хотя иногда они могут быть отфильтрованы до подмножества, как мы увидим позже.

Независимо от того, что вы планируете делать с groupby, полезно знать метод size, который возвращает Series с количеством элементов в каждой группе:

In [23]: df.groupby(['key1', 'key2']).size()
Out[23]: 
key1  key2
a     one     2
      two     1
b     one     1
      two     1
dtype: int64

Обратите внимание, что любые пропущенные значения в ключевом столбце будут исключены из результата. Стив 2 3 Уэс 1 2 Джим 2 3 Тревис 2 3


## Группировка с использованием функций

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

Например, если взять индекс DataFrame из предыдущего раздела, то в качестве примера можно использовать имена людей. Можно вычислить массив длины строки, но проще передать функцию len:

```python
In [44]: people.groupby(len).sum()
Out[44]: 
          a         b         c         d         e
3  0.591569 -0.993608  0.798764 -0.791374  2.119639
5  0.886429 -2.001637 -0.371843  1.669025 -0.438570
6 -0.713544 -0.831154 -2.370232 -1.860761 -0.860757

Смешивание функций с массивами, списками, словарями и сериями также не является проблемой, поскольку всё внутри преобразуется в массив:

In [45]: key_list = ['one', 'one', 'one', 'two', 'two']

In [46]: people.groupby([len, key_list]).min()
Out[46]: 
              a         b         c         d         e
3 one -0.539741 -1.296221  0.274992 -1.021228 -0.577087
  two  0.124121  0.302614  0.523772  0.000940  1.343810
5 one  0.886429 -2.001637 -0.371843  1.669025 -0.438570
6 two -0.713544 -0.831154 -2.370232 -1.860761 -0.860757

Группировка по уровням индекса

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

In [47]: columns = pd.MultiIndex.from_arrays([['US', 'US', 'US', 'JP', 'JP'],
   ....:                                     [1, 3, 5, 1, 3]],
   ....:                                     names=['cty', 'tenor'])

In [48]: hier_df = pd.DataFrame(np.random.randn(4, 5), columns=columns)

In [49]: hier_df
Out[49]: 
cty          US                            JP          
tenor         1         3         5         1         3
0      0.560145 -1.265934  0.119827 -1.063512  0.332883
1     -2.359419 -0.199543 -1.541996 -0.970736 -1.307030
2      0.286350  0.377984 -0.753887  0.331286  1.349742
3      0.069877  0.246674 -0.011862  1.004812  1.327195

Для группировки по уровню используйте ключевое слово level для передачи номера или имени уровня:

In [50]: hier_df.groupby(level='cty', axis=1).count()
Out[50]: 
cty  JP  US
0     2   3
1     2   3
2     2   3
3     2   3
``` **Перевод текста на русский язык:**

Вы уже видели, что агрегатные операции над столбцами Series или DataFrame фактически представляют собой использование aggregate (с использованием пользовательской функции) или вызов таких методов, как mean и std. Однако вы можете захотеть применить разные агрегатные функции к разным столбцам или использовать несколько функций одновременно. Это тоже легко сделать, и я объясню это на нескольких примерах. Сначала я группирую данные tips по дням и статусу курильщика:

```python
In [60]: grouped = tips.groupby(['day', 'smoker'])

Обратите внимание, что для описательной статистики в таблице 10-1 можно передать имена функций в виде строк:

In [61]: grouped_pct = grouped['tip_pct']

In [62]: grouped_pct.agg('mean')
Out[62]: 
day   smoker
Fri   No        0.151650
      Yes       0.174783
Sat   No        0.158048
      Yes       0.147906
Sun   No        0.160113
      Yes       0.187250
Thur  No        0.160298
      Yes       0.163863
Name: tip_pct, dtype: float64

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

In [63]: grouped_pct.agg(['mean', 'std', peak_to_peak])
Out[63]: 
                 mean       std  peak_to_peak
day  smoker                                  
Fri  No      0.151650  0.028123      0.067349
     Yes     0.174783  0.051293      0.159925
Sat  No      0.158048  0.039767      0.235193
     Yes     0.147906  0.061375      0.290095
Sun  No      0.160113  0.042347      0.193226
     Yes     0.187250  0.154134      0.644685
Thur No      0.160298  0.038774      0.193350
     Yes     0.163863  0.039389      0.151240

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

Вам не обязательно принимать имена столбцов, автоматически предоставляемые GroupBy, особенно для лямбда-функций, их имя — '', что делает их идентификацию очень низкой (вы можете увидеть это, посмотрев на атрибут name функции). Поэтому, если вы передаёте список кортежей (имя, функция), то первый элемент каждого кортежа будет использоваться в качестве имени столбца DataFrame (этот список бинарных кортежей можно рассматривать как упорядоченное отображение):

In [64]: grouped_pct.agg([('foo', 'mean'), ('bar', np.std)])
Out[64]: 
                  foo       bar
day  smoker                    
Fri  No      0.151650  0.028123
     Yes     0.174783  0.051293
Sat  No      0.158048  0.039767
     Yes     0.147906  0.061375
Sun  No      0.160113  0.042347
     Yes     0.187250  0.154134
Thur No      0.160298  0.038774
     Yes     0.163863  0.039389

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

In [65]: functions = ['count', 'mean', 'max']

In [66]: result = grouped['tip_pct', 'total_bill'].agg(functions)

In [67]: result
Out[67]: 
            tip_pct                     total_bill                  
              count      mean       max      count       mean    max
day  smoker                                                         
Fri  No           4  0.151650  0.187735          4  18.420000  22.75
     Yes         15  0.174783  0.263480         15  16.813333  40.17
Sat  No          45  0.158048  0.291990         45  19.661778  48.33
     Yes         42  0.147906  0.325733         42  21.276667  50.81
Sun  No          57  0.160113  0.252672         57  20.506667  48.17
     Yes         19  0.187250  0.710345         19  24.120000  45.35
Thur No          45  0.160298  0.266312         45  17.113111  41.19
     Yes         17  0.163863  0.241255         17  19.190588  43.11

Как видите, результирующий DataFrame имеет иерархические столбцы, которые эквивалентны отдельным агрегациям для каждого столбца, а затем использованию concat для объединения результатов вместе с использованием имён столбцов в качестве ключей:

In [68]: result['tip_pct']
Out[68]: 
             count      mean       max
day  smoker                           
Fri  No          4  0.151650  0.187735
     Yes        15  0.174783  0.263480
Sat  No         45  0.158048  0.291990
     Yes        42  0.147906  0.325733
Sun  No         57  0.160113  0.252672
     Yes        19  0.187250  0.710345
Thur No         45  0.160298  0.266312
     Yes        17  0.163863  0.241255

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

In [69]: ftuples = [('Durchschnitt', 'mean'),('Abweichung', np.var)]

In [70]: grouped['tip_pct', 'total_bill'].agg(ftuples)
Out[70]: 
                 tip_pct              total_bill            
            Durchschnitt Abweichung
``` **Теперь предположим, что вы хотите применить к одному или нескольким столбцам различные функции.**

Конкретный способ заключается в передаче словаря, который отображает имена столбцов на функции, в agg:
```python
In [71]: grouped.agg({'tip' : np.max, 'size' : 'sum'})
Out[71]: 
               tip  size
day  smoker             
Fri  No       3.50     9
     Yes      4.73    31
Sat  No       9.00   115
     Yes     10.00   104
Sun  No       6.00   167
     Yes      6.50    49
Thur No       6.70   112
     Yes      5.00    40

Только когда несколько функций применяются хотя бы к одному столбцу, DataFrame будет иметь иерархические столбцы.

Возврат агрегированных данных в виде «без индексных строк»

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

In [73]: tips.groupby(['day', 'smoker'], as_index=False).mean()
Out[73]: 
    day smoker  total_bill       tip      size   tip_pct
0   Fri     No   18.420000  2.812500  2.250000  0.151650
1   Fri    Yes   16.813333  2.714000  2.066667  0.174783
2   Sat     No   19.661778  3.102889  2.555556  0.158048
3   Sat    Yes   21.276667  2.875476  2.476190  0.147906
4   Sun     No   20.506667  3.167895  2.929825  0.160113
5   Sun    Yes   24.120000  3.516842  2.578947  0.187250
6  Thur     No   17.113111  2.673778  2.488889  0.160298
7  Thur    Yes   19.190588  3.030000  2.352941  0.163863

Конечно, вызов reset_index для результата также может дать такой результат. Использование as_index = False позволяет избежать некоторых ненужных вычислений.

10.3 apply: общий метод «разделить-применить-объединить»

Наиболее общий метод GroupBy — это apply, и в оставшейся части этого раздела основное внимание уделяется его объяснению. Как показано на рисунке 10-2, apply разбивает обрабатываемый объект на несколько фрагментов, затем вызывает переданную функцию для каждого фрагмента и, наконец, пытается объединить фрагменты вместе.

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

In [74]: def top(df, n=5, column='tip_pct'):
   ....:     return df.sort_values(by=column)[-n:]

In [75]: top(tips, n=6)
Out[75]: 
     total_bill   tip smoker  day    time  size   tip_pct
109       14.31  4.00    Yes  Sat  Dinner     2  0.279525
183       23.17  6.50    Yes  Sun  Dinner     4  0.280535
232       11.61  3.39     No  Sat  Dinner     2  0.291990
67         3.07  1.00    Yes  Sat  Dinner     1  0.325733
178        9.60  4.00    Yes  Sun  Dinner     2  0.416667
172        7.25  5.15    Yes  Sun  Dinner     2  0.710345

Теперь, если вы примените эту функцию к группам, сгруппированным по курильщикам, используя apply, вы получите:

In [76]: tips.groupby('smoker').apply(top)
Out[76]: 
            total_bill   tip smoker   day    time  size   tip_pct
smoker                                                           
No     88        24.71  5.85     No  Thur   Lunch     2  0.236746
       185       20.69  5.00     No   Sun  Dinner     5  0.241663
       51        10.29  2.60     No   Sun  Dinner     2  0.252672
       149        7.51  2.00     No  Thur   Lunch     2  0.266312
       232       11.61  3.39     No   Sat  Dinner     2  0.291990
Yes    109       14.31  4.00    Yes   Sat  Dinner     2  0.279525
       183       23.17  6.50    Yes   Sun  Dinner     4  0.280535
       67         3.07  1.00    Yes   Sat  Dinner     1  0.325733
       178        9.60  4.00    Yes
``` **Перевод текста на русский язык:**

**Пример: выборка из колоды карт**

deck[:13]

AH 1 2H 2 3H 3 4H 4 5H 5 6H 6 7H 7 8H 8 9H 9 10H 10 JH 10 KH 10 QH 10 dtype: int64


Теперь, согласно тому, что я сказал выше, выберем 5 карт из полной колоды. Код следующий:

```python
In [109]: def draw(deck, n=5):
   .....:     return deck.sample(n)

In [110]: draw(deck)
AD     1
8C     8
5H     5
KC    10
2C     2
dtype: int64

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

In [111]: get_suit = lambda card: card[-1] # last letter is suit

In [112]: deck.groupby(get_suit).apply(draw, n=2)
C  2C     2
   3C     3
D  KD    10
   8D     8
H  KH    10
   3H     3
S  2S     2
   4S     4
dtype: int64

Или можно написать так:

In [113]: deck.groupby(get_suit, group_keys=False).apply(draw, n=2)
KC    10
JC    10
AD     1
5D     5
5H     5
6H     6
7S     7
KS    10
dtype: int64

Пример: группировка, взвешенное среднее и коэффициент корреляции

Согласно шаблону «разделить-применить-объединить» метода groupby, можно выполнять операции между столбцами DataFrame или двумя Series (например, групповое взвешенное среднее). Рассмотрим следующий набор данных, который содержит ключ группы, значение и некоторые весовые значения:

In [114]: df = pd.DataFrame({'category': ['a', 'a', 'a', 'a',
   .....:                                 'b', 'b', 'b', 'b'],
   .....:                    'data': np.random.randn(8),
   .....:                    'weights': np.random.rand(8)})

In [115]: df

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

In [116]: grouped = df.groupby('category')

In [117]: get_wavg = lambda g: np.average(g['data'], weights=g['weights'])

In [118]: grouped.apply(get_wavg)

Другой пример: рассмотрим набор данных с несколькими акциями и индексом Standard & Poor's 500 (символ SPX):

In [119]: close_px = pd.read_csv('examples/stock_px_2.csv', parse_dates=True,
   .....:                        index_col=0)

In [120]: close_px.info()

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

In [122]: spx_corr = lambda x: x.corrwith(x['SPX'])

Наконец, мы используем pct_change для расчёта процентного изменения close_px:

In [123]: rets = close_px.pct_change().dropna()

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

In [126]: by_year = rets.groupby(get_year)

In [127]: by_year.apply(spx_corr)
``` **Перевод текста на русский язык:**

0.207893  0.000000
       3    Нет      0.000000  0.154661  0.152663  0.000000 
         Да     0.000000  0.144995  0.152660  0.000000
       4    Нет      0.000000  0.150096  0.148143  0.000000
         Да     0.117750  0.124515  0.193370  0.000000
       5    Нет      0.000000  0.000000  0.206928  0.000000
Да     0.000000  0.106572  0.065660  0.000000
...                      ...       ...       ...       ...
Обед  1    Нет      0.000000  0.000000  0.000000  0.181728
            Да     0.223776  0.000000  0.000000  0.000000
       2    Нет      0.000000  0.000000  0.000000  0.166005
            Да     0.181969  0.000000  0.000000  0.158843
       3    Нет      0.187735  0.000000  0.000000  0.084246
            Да     0.000000  0.000000  0.000000  0.204952
       4    Нет      0.000000  0.000000  0.000000  0.138919
            Да     0.000000  0.000000  0.000000  0.155410
       5    Нет      0.000000  0.000000  0.000000  0.121389
       6    Нет      0.000000  0.000000  0.000000  0.173706
[21 строка x 4 столбца]

**Параметры pivot_table см. в таблице 10-2.**

## Перекрёстная таблица: crosstab

Перекрёстная таблица (cross-tabulation, сокращённо  crosstab)  это особый вид сводной таблицы, который используется для расчёта частоты групп. Рассмотрим следующий пример:
```python
In [138]: data
Out[138]:
   Sample Nationality    Handedness
0       1         USA  Right-handed
1       2       Japan   Left-handed
2       3         USA  Right-handed
3       4       Japan  Right-handed
4       5       Japan   Left-handed
5       6       Japan  Right-handed
6       7         USA  Right-handed
7       8         USA   Left-handed
8       9       Japan  Right-handed
9      10         USA  Right-handed

В рамках исследовательского анализа мы можем захотеть провести статистический анализ этих данных с учётом национальности и привычки использования рук. Хотя можно использовать pivot_table для реализации этой функции, функция pandas.crosstab будет более удобной:

In [139]: pd.crosstab(data.Nationality, data.Handedness, margins=True)
Out[139]: 
Handedness   Left-handed  Right-handed  All
Nationality
Japan                  2             3    5
USA                    1             4    5
All                    3             7   10

Два первых параметра crosstab могут быть массивом или Series, или списком массивов. Например, как в случае с данными о чаевых:

In [140]: pd.crosstab([tips.time, tips.day], tips.smoker, margins=True)
Out[140]: 
smoker        No  Yes  All
time   day                
Dinner Fri     3    9   12
       Sat    45   42   87
       Sun    57   19   76
       Thur    1    0    1
Lunch  Fri     1    6    7
       Thur   44   17   61
All          151   93  244

10.5 Резюме

Владение инструментами группировки данных в pandas помогает не только в очистке данных, но и в моделировании или статистическом анализе. В главе 14 мы рассмотрим несколько примеров использования groupby на реальных данных.

В следующей главе мы сосредоточимся на данных временных рядов.

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/it-ebooks-pyda-2e-zh.git
git@api.gitlife.ru:oschina-mirror/it-ebooks-pyda-2e-zh.git
oschina-mirror
it-ebooks-pyda-2e-zh
it-ebooks-pyda-2e-zh
master