Крис Моффитт, редактор сайта об автоматизации бизнес-задач на Python, разработал модуль sidetable.
Со слов автора новый модуль расширяет возможности value_counts()
и использует API pandas
для регистрации собственных методов.
Давайте разбираться, как он работает.
Для начала установим модуль:
#!pip3 install sidetable
Рассмотрим пример с грантами для школ США, если кратко: Конгресс еще при Обаме выделил 4 миллиарда у.е. для реформы образования, для получения гранта школе надо выбрать одну из моделей реформирования (Model Selected
).
Начинаем, как обычно, с импорта модулей:
import pandas as pd
import sidetable
df = pd.read_csv('https://github.com/chris1610/pbpython/blob/master/data/school_transform.csv?raw=True', index_col=0)
df.head()
School Name | City | State | District Name | Model Selected | Award_Amount | Region | |
---|---|---|---|---|---|---|---|
0 | HOGARTH KINGEEKUK MEMORIAL SCHOOL | SAVOONGA | AK | BERING STRAIT SCHOOL DISTRICT | Transformation | 471014 | West |
1 | AKIACHAK SCHOOL | AKIACHAK | AK | YUPIIT SCHOOL DISTRICT | Transformation | 520579 | West |
2 | GAMBELL SCHOOL | GAMBELL | AK | BERING STRAIT SCHOOL DISTRICT | Transformation | 449592 | West |
3 | BURCHELL HIGH SCHOOL | WASILLA | AK | MATANUSKA-SUSITNA BOROUGH SCHOOL DISTRICT | Transformation | 641184 | West |
4 | AKIAK SCHOOL | AKIAK | AK | YUPIIT SCHOOL DISTRICT | Transformation | 399686 | West |
В результате импорта модуля sidetable
у DataFrame
появился новый метод stb
.
Вызов stb.freq()
позволяет построить сводную таблицу частот по штатам:
df.stb.freq(['State']).head()
State | count | percent | cumulative_count | cumulative_percent | |
---|---|---|---|---|---|
0 | CA | 92 | 12.153236 | 92 | 12.153236 |
1 | FL | 71 | 9.379128 | 163 | 21.532365 |
2 | PA | 58 | 7.661823 | 221 | 29.194188 |
3 | OH | 35 | 4.623514 | 256 | 33.817701 |
4 | MO | 32 | 4.227213 | 288 | 38.044914 |
Этот пример показывает, что CA
(California) встречается 92 раза и составляет 12,15%
от общего количества школ. Если включить в подсчеты FL
(Florida), то будет 163 школы, что составляет 21,5%
от общего числа школ, участвующих в грантах.
Можно сравнить этот результат с выводом стандартного метода value_counts()
.
При установке normalize
в True
возвращаемый объект будет содержать относительные частоты уникальных значений:
df['State'].value_counts(normalize=True)[:10]
CA 0.121532 FL 0.093791 PA 0.076618 OH 0.046235 MO 0.042272 MI 0.036988 GA 0.034346 NY 0.033025 NC 0.030383 AZ 0.025099 Name: State, dtype: float64
Хм... разница заметна, даже невооруженным глазом.
Можно составить список штатов, которые составляют около 50%
от общего числа с помощью аргумента thresh
(рус. «молотить») и сгруппировать все остальные штаты в категорию Others
:
df.stb.freq(['State'], thresh=50)
State | count | percent | cumulative_count | cumulative_percent | |
---|---|---|---|---|---|
0 | CA | 92 | 12.153236 | 92 | 12.153236 |
1 | FL | 71 | 9.379128 | 163 | 21.532365 |
2 | PA | 58 | 7.661823 | 221 | 29.194188 |
3 | OH | 35 | 4.623514 | 256 | 33.817701 |
4 | MO | 32 | 4.227213 | 288 | 38.044914 |
5 | MI | 28 | 3.698811 | 316 | 41.743725 |
6 | GA | 26 | 3.434610 | 342 | 45.178336 |
7 | NY | 25 | 3.302510 | 367 | 48.480845 |
8 | others | 390 | 51.519155 | 757 | 100.000000 |
Теперь видим, что 8 штатов составляют практически 50%
от общего количества.
Можем для симпатичности переименовать категорию Others
, используя ключевой аргумент other_label
:
df.stb.freq(['State'], thresh=50, other_label='Остальные штаты')
State | count | percent | cumulative_count | cumulative_percent | |
---|---|---|---|---|---|
0 | CA | 92 | 12.153236 | 92 | 12.153236 |
1 | FL | 71 | 9.379128 | 163 | 21.532365 |
2 | PA | 58 | 7.661823 | 221 | 29.194188 |
3 | OH | 35 | 4.623514 | 256 | 33.817701 |
4 | MO | 32 | 4.227213 | 288 | 38.044914 |
5 | MI | 28 | 3.698811 | 316 | 41.743725 |
6 | GA | 26 | 3.434610 | 342 | 45.178336 |
7 | NY | 25 | 3.302510 | 367 | 48.480845 |
8 | Остальные штаты | 390 | 51.519155 | 757 | 100.000000 |
sidetable
позволяет группировать столбцы для лучшего понимания распределения.
Посмотрим, как различные Модели трансформации (Model Selected
) применяются в разных регионах?
df.stb.freq(['Region', 'Model Selected'])
Region | Model Selected | count | percent | cumulative_count | cumulative_percent | |
---|---|---|---|---|---|---|
0 | South | Transformation | 185 | 24.765730 | 185 | 24.765730 |
1 | West | Transformation | 142 | 19.009371 | 327 | 43.775100 |
2 | Midwest | Transformation | 111 | 14.859438 | 438 | 58.634538 |
3 | Northeast | Transformation | 102 | 13.654618 | 540 | 72.289157 |
4 | West | Turnaround | 49 | 6.559572 | 589 | 78.848728 |
5 | South | Turnaround | 44 | 5.890228 | 633 | 84.738956 |
6 | Midwest | Turnaround | 43 | 5.756359 | 676 | 90.495315 |
7 | Northeast | Turnaround | 25 | 3.346720 | 701 | 93.842035 |
8 | South | Restart | 11 | 1.472557 | 712 | 95.314592 |
9 | Northeast | Restart | 9 | 1.204819 | 721 | 96.519411 |
10 | West | Restart | 7 | 0.937082 | 728 | 97.456493 |
11 | West | Closure | 6 | 0.803213 | 734 | 98.259705 |
12 | Midwest | Closure | 5 | 0.669344 | 739 | 98.929050 |
13 | South | Closure | 3 | 0.401606 | 742 | 99.330656 |
14 | Midwest | Restart | 3 | 0.401606 | 745 | 99.732262 |
15 | Northeast | Closure | 2 | 0.267738 | 747 | 100.000000 |
sidetable
позволяет передавать значение value
, по которому можно суммировать (вместо подсчета вхождений).
df.stb.freq(['Region'], value='Award_Amount')
Region | Award_Amount | percent | cumulative_Award_Amount | cumulative_percent | |
---|---|---|---|---|---|
0 | South | 117467481 | 37.314735 | 117467481 | 37.314735 |
1 | West | 74418552 | 23.639807 | 191886033 | 60.954542 |
2 | Midwest | 65736175 | 20.881762 | 257622208 | 81.836304 |
3 | Northeast | 57179654 | 18.163696 | 314801862 | 100.000000 |
Узнали, что Northeast
(Северо-Восток) затратил наименьшее количество средств на реформу, а 37%
от общих расходов было потрачено на школы в South
(Южном) регионе.
Посмотрим на типы выбранных моделей и определим разбиение 80/20
для выделенных средств:
df.stb.freq(['Region', 'Model Selected'],
value='Award_Amount',
thresh=82,
other_label='Remaining')
Region | Model Selected | Award_Amount | percent | cumulative_Award_Amount | cumulative_percent | |
---|---|---|---|---|---|---|
0 | South | Transformation | 88680032 | 28.170110 | 88680032 | 28.170110 |
1 | West | Transformation | 56207890 | 17.855006 | 144887922 | 46.025116 |
2 | Midwest | Transformation | 48702505 | 15.470844 | 193590427 | 61.495960 |
3 | Northeast | Transformation | 41263161 | 13.107661 | 234853588 | 74.603621 |
4 | South | Turnaround | 22531412 | 7.157331 | 257385000 | 81.760952 |
5 | Remaining | Remaining | 57416862 | 18.239048 | 314801862 | 100.000000 |
Можем сравнить с кросс-таблицей crosstab
в pandas:
pd.crosstab(df['Region'],
df['Model Selected'],
values=df['Award_Amount'],
aggfunc='sum')
Model Selected | Closure | Restart | Transformation | Turnaround |
---|---|---|---|---|
Region | ||||
Midwest | 86872 | 1397735 | 48702505 | 15549063 |
Northeast | 508773 | 5728010 | 41263161 | 9679710 |
South | 354323 | 5901714 | 88680032 | 22531412 |
West | 272520 | 2245146 | 56207890 | 15692996 |
Сравните с:
df.stb.freq(['Region', 'Model Selected'],
value='Award_Amount')
Region | Model Selected | Award_Amount | percent | cumulative_Award_Amount | cumulative_percent | |
---|---|---|---|---|---|---|
0 | South | Transformation | 88680032 | 28.170110 | 88680032 | 28.170110 |
1 | West | Transformation | 56207890 | 17.855006 | 144887922 | 46.025116 |
2 | Midwest | Transformation | 48702505 | 15.470844 | 193590427 | 61.495960 |
3 | Northeast | Transformation | 41263161 | 13.107661 | 234853588 | 74.603621 |
4 | South | Turnaround | 22531412 | 7.157331 | 257385000 | 81.760952 |
5 | West | Turnaround | 15692996 | 4.985039 | 273077996 | 86.745991 |
6 | Midwest | Turnaround | 15549063 | 4.939317 | 288627059 | 91.685309 |
7 | Northeast | Turnaround | 9679710 | 3.074858 | 298306769 | 94.760167 |
8 | South | Restart | 5901714 | 1.874739 | 304208483 | 96.634906 |
9 | Northeast | Restart | 5728010 | 1.819560 | 309936493 | 98.454466 |
10 | West | Restart | 2245146 | 0.713193 | 312181639 | 99.167660 |
11 | Midwest | Restart | 1397735 | 0.444005 | 313579374 | 99.611664 |
12 | Northeast | Closure | 508773 | 0.161617 | 314088147 | 99.773281 |
13 | South | Closure | 354323 | 0.112554 | 314442470 | 99.885835 |
14 | West | Closure | 272520 | 0.086569 | 314714990 | 99.972404 |
15 | Midwest | Closure | 86872 | 0.027596 | 314801862 | 100.000000 |
Можно улучшить читабельность данных в pandas за счет добавления форматирования столбцов Percentage
и Amount
.
Укажем для этого ключевой аргумент style=True
:
df.stb.freq(['Region'], value='Award_Amount', style=True)
Region | Award_Amount | percent | cumulative_Award_Amount | cumulative_percent | |
---|---|---|---|---|---|
0 | South | 117,467,481 | 37.31% | 117,467,481 | 37.31% |
1 | West | 74,418,552 | 23.64% | 191,886,033 | 60.95% |
2 | Midwest | 65,736,175 | 20.88% | 257,622,208 | 81.84% |
3 | Northeast | 57,179,654 | 18.16% | 314,801,862 | 100.00% |
Пример построения таблицы пропущенных значений:
df.stb.missing()
missing | total | percent | |
---|---|---|---|
Region | 10 | 757 | 1.321004 |
School Name | 0 | 757 | 0.000000 |
City | 0 | 757 | 0.000000 |
State | 0 | 757 | 0.000000 |
District Name | 0 | 757 | 0.000000 |
Model Selected | 0 | 757 | 0.000000 |
Award_Amount | 0 | 757 | 0.000000 |
Видим 10 пропущенных значений в столбце Region
, что составляет чуть менее 1,3%
от общего значения в этом столбце.
Похожий результат можно получить с помощью info()
:
df.info()
<class 'pandas.core.frame.DataFrame'> Int64Index: 757 entries, 0 to 830 Data columns (total 7 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 School Name 757 non-null object 1 City 757 non-null object 2 State 757 non-null object 3 District Name 757 non-null object 4 Model Selected 757 non-null object 5 Award_Amount 757 non-null int64 6 Region 747 non-null object dtypes: int64(1), object(6) memory usage: 47.3+ KB