.groupby()
и .agg()
¶Часто случается, что данные необходимо сгруппировать по какому-то признаку – по значениям определенной переменной. На входе имеется таблица (датафрейм), а на выходе хочется получить несколько таблиц: отдельная таблица для каждого значения. Давайте рассмотрим такой пример. У нас есть база данных с результатами выборов, и нам нужно сгруппировать данные по регионам.
Для начала импортируем библиотеку pandas и загрузим файл с данными.
import pandas as pd
Для разнообразия загрузим файл по ссылке с Github (база большая, загрузится не моментально):
df = pd.read_csv("https://raw.githubusercontent.com/allatambov/py-dat18/master/26-12/47130-8314.csv")
В таблице сохранены результаты выборов президента России 2012 года.
df.head()
link | uik | kom1 | kom2 | kom3 | kom4 | kom5 | 1 | 2 | 3 | ... | 18 | 19 | 20 | 21 | 22 | 23 | а | б | в | г | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | http://www.adygei.vybory.izbirkom.ru/region/ad... | 1 | Республика Адыгея (Адыгея) | Адыгейская | УИК №1 | NaN | NaN | 2383.0 | 2147.0 | 0.0 | ... | 0.0 | 24.0 | 382.0 | 28.0 | 71.0 | 1066.0 | NaN | NaN | NaN | NaN |
1 | http://www.adygei.vybory.izbirkom.ru/region/ad... | 2 | Республика Адыгея (Адыгея) | Адыгейская | УИК №2 | NaN | NaN | 2865.0 | 2586.0 | 0.0 | ... | 0.0 | 51.0 | 453.0 | 49.0 | 104.0 | 1174.0 | NaN | NaN | NaN | NaN |
2 | http://www.adygei.vybory.izbirkom.ru/region/ad... | 3 | Республика Адыгея (Адыгея) | Адыгейская | УИК №3 | NaN | NaN | 2821.0 | 2558.0 | 0.0 | ... | 0.0 | 36.0 | 481.0 | 24.0 | 107.0 | 1025.0 | NaN | NaN | NaN | NaN |
3 | http://www.adygei.vybory.izbirkom.ru/region/ad... | 4 | Республика Адыгея (Адыгея) | Адыгейская | УИК №4 | NaN | NaN | 2069.0 | 1868.0 | 0.0 | ... | 0.0 | 0.0 | 414.0 | 0.0 | 48.0 | 784.0 | NaN | NaN | NaN | NaN |
4 | http://www.adygei.vybory.izbirkom.ru/region/ad... | 5 | Республика Адыгея (Адыгея) | Адыгейская | УИК №5 | NaN | NaN | 777.0 | 705.0 | 0.0 | ... | 0.0 | 19.0 | 138.0 | 4.0 | 7.0 | 286.0 | NaN | NaN | NaN | NaN |
5 rows × 34 columns
df.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 90003 entries, 0 to 90002 Data columns (total 34 columns): link 90003 non-null object uik 90003 non-null int64 kom1 90003 non-null object kom2 90003 non-null object kom3 89618 non-null object kom4 0 non-null float64 kom5 0 non-null float64 1 89994 non-null float64 2 89994 non-null float64 3 89994 non-null float64 4 89994 non-null float64 5 89994 non-null float64 6 89994 non-null float64 7 89994 non-null float64 8 89994 non-null float64 9 89994 non-null float64 10 89994 non-null float64 11 89994 non-null float64 12 89994 non-null float64 13 89994 non-null float64 14 89994 non-null float64 15 89994 non-null float64 16 89994 non-null float64 17 89994 non-null float64 18 89994 non-null float64 19 89994 non-null float64 20 89994 non-null float64 21 89994 non-null float64 22 89994 non-null float64 23 89994 non-null float64 а 0 non-null float64 б 0 non-null float64 в 0 non-null float64 г 0 non-null float64 dtypes: float64(29), int64(1), object(4) memory usage: 23.3+ MB
Таблица достаточно большая, поэтому давайте выберем те столбцы, которые понадобятся нам для работы. Какие именно? Столбцы в этой базе имеют порядковый номер строки в таблице на сайте Центральной избирательной комиссии.
Выберем столбцы, которые соответствуют уровням комиссий, а также следующим показателям: общее число зарегистрированных избирателей, число недействительных бюллетеней, число действительных бюллетеней, число голосов за Жириновского, Зюганова, Миронова, Прохорова и Путина.
d = df[["kom1", "kom2", "kom3", "1", "9", "10", "19", "20", "21", "22", "23"]]
d.head()
kom1 | kom2 | kom3 | 1 | 9 | 10 | 19 | 20 | 21 | 22 | 23 | |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | Республика Адыгея (Адыгея) | Адыгейская | УИК №1 | 2383.0 | 19.0 | 1571.0 | 24.0 | 382.0 | 28.0 | 71.0 | 1066.0 |
1 | Республика Адыгея (Адыгея) | Адыгейская | УИК №2 | 2865.0 | 29.0 | 1831.0 | 51.0 | 453.0 | 49.0 | 104.0 | 1174.0 |
2 | Республика Адыгея (Адыгея) | Адыгейская | УИК №3 | 2821.0 | 31.0 | 1673.0 | 36.0 | 481.0 | 24.0 | 107.0 | 1025.0 |
3 | Республика Адыгея (Адыгея) | Адыгейская | УИК №4 | 2069.0 | 0.0 | 1246.0 | 0.0 | 414.0 | 0.0 | 48.0 | 784.0 |
4 | Республика Адыгея (Адыгея) | Адыгейская | УИК №5 | 777.0 | 8.0 | 454.0 | 19.0 | 138.0 | 4.0 | 7.0 | 286.0 |
Теперь присвоим столбцам более информативные названия:
d.columns = ["region", "tik", "uik", "total", "invalid", "valid", "Zh", "Zu", "Mi", "Pr", "Pu"]
d.head() # опять посмотрим
region | tik | uik | total | invalid | valid | Zh | Zu | Mi | Pr | Pu | |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | Республика Адыгея (Адыгея) | Адыгейская | УИК №1 | 2383.0 | 19.0 | 1571.0 | 24.0 | 382.0 | 28.0 | 71.0 | 1066.0 |
1 | Республика Адыгея (Адыгея) | Адыгейская | УИК №2 | 2865.0 | 29.0 | 1831.0 | 51.0 | 453.0 | 49.0 | 104.0 | 1174.0 |
2 | Республика Адыгея (Адыгея) | Адыгейская | УИК №3 | 2821.0 | 31.0 | 1673.0 | 36.0 | 481.0 | 24.0 | 107.0 | 1025.0 |
3 | Республика Адыгея (Адыгея) | Адыгейская | УИК №4 | 2069.0 | 0.0 | 1246.0 | 0.0 | 414.0 | 0.0 | 48.0 | 784.0 |
4 | Республика Адыгея (Адыгея) | Адыгейская | УИК №5 | 777.0 | 8.0 | 454.0 | 19.0 | 138.0 | 4.0 | 7.0 | 286.0 |
Посмотрим теперь, какие регионы есть в базе. Выбрать столбец region в таком случае будет не совсем удачно, поскольку в нем будет много повторяющихся значений. Посмотрим только на уникальные:
d.region.unique() # метод unique - уникальные значения
array(['Республика Адыгея (Адыгея)', 'Республика Алтай', 'Республика Башкортостан', 'Республика Бурятия', 'Республика Дагестан', 'Ðåñïóáëèêà Äàãåñòàí', 'Республика Ингушетия', 'Кабардино-Балкарская Республика', 'Республика Калмыкия', 'Карачаево-Черкесская Республика', 'Республика Карелия', 'Республика Коми', 'Республика Марий Эл', 'Республика Мордовия', 'Республика Саха (Якутия)', 'Республика Северная Осетия - Алания', 'Республика Тыва', 'Удмуртская Республика', 'Республика Хакасия', 'Чувашская Республика - Чувашия', 'Алтайский край', 'Забайкальский край', 'Камчатский край', 'Краснодарский край', 'Красноярский край', 'Пермский край', 'Приморский край', 'Ставропольский край', 'Хабаровский край', 'Õàáàðîâñêèé êðàé', 'Амурская область', 'Архангельская область', 'Астраханская область', 'Белгородская область', 'Брянская область', 'Владимирская область', 'Волгоградская область', 'Вологодская область', 'Воронежская область', 'Ивановская область', 'Иркутская область', 'Калужская область', 'Кемеровская область', 'Кировская область', 'Костромская область', 'Курганская область', 'Курская область', 'Ленинградская область', 'Липецкая область', 'Магаданская область', 'Московская область', 'Мурманская область', 'Ìóðìàíñêàÿ îáëàñòü', 'Нижегородская область', 'Новгородская область', 'Новосибирская область', 'Омская область', 'Оренбургская область', 'Орловская область', 'Пензенская область', 'Псковская область', 'Ростовская область', 'Рязанская область', 'Самарская область', 'Саратовская область', 'Сахалинская область', 'Свердловская область', 'Смоленская область', 'Тамбовская область', 'Тверская область', 'Томская область', 'Тульская область', 'Тюменская область', 'Ульяновская область', 'Челябинская область', 'Город Москва', 'Город Санкт-Петербург', 'Ãîðîä Ñàíêò-Ïåòåðáóðã', 'Еврейская автономная область', 'Ненецкий автономный округ', 'Чукотский автономный округ', 'Ямало-Ненецкий автономный округ', 'Город Байконур (Республика Казахстан)', 'Территория за пределами РФ'], dtype=object)
Видно, что в этом массиве встречаются какие-то крокозябры (названия со странной кодировкой). Давайте уберем эти строки из базы.
# отфильтруем с помощью условий
d = d[(d.region != 'Ðåñïóáëèêà Äàãåñòàí') &
(d.region != 'Õàáàðîâñêèé êðàé') &
(d.region != 'Ìóðìàíñêàÿ îáëàñòü') & (d.region != 'Ãîðîä Ñàíêò-Ïåòåðáóðã')]
Сгруппируем данные по регионам и посчитаем для каждого региона явку в процентах и процент голосов за каждого кандидата. Группировка осуществляется с помощью метода .groupby()
.
d.groupby('region') # пока ничего не увидели
<pandas.core.groupby.groupby.DataFrameGroupBy object at 0x10ea5b390>
Что выдает метод .groupby()
? На самом деле он делает следующее: создает список, состоящий из кортежей. Каждый кортеж – это пара название группы-соответствующий ей фрагмент датафрейма.
# посмотрим на все сразу
for g in d.groupby('region'):
print(g)
В таком виде метод .groupby()
дает нам немного. Мы же хотим не просто получать отдельные таблицы, а агрегировать данные по регионам ‒ суммировать все показатели (число избирателей, бюллетеней, голосов) по каждому региону. Тут на помощь придет метод .agg()
, который выполняет агрегирование по группам.
d.groupby('region').agg('sum')
total | invalid | valid | Zh | Zu | Mi | Pr | Pu | |
---|---|---|---|---|---|---|---|---|
region | ||||||||
Алтайский край | 1961328.0 | 12004.0 | 1163426.0 | 97961.0 | 261665.0 | 45883.0 | 83778.0 | 674139.0 |
Амурская область | 662320.0 | 4708.0 | 394996.0 | 39717.0 | 67433.0 | 13594.0 | 23070.0 | 251182.0 |
Архангельская область | 988678.0 | 5522.0 | 569492.0 | 51169.0 | 91648.0 | 33223.0 | 60108.0 | 333344.0 |
Астраханская область | 769608.0 | 5107.0 | 427496.0 | 21918.0 | 67662.0 | 18595.0 | 21873.0 | 297448.0 |
Белгородская область | 1210590.0 | 10209.0 | 889764.0 | 59561.0 | 211079.0 | 35601.0 | 49807.0 | 533716.0 |
Брянская область | 1045083.0 | 6922.0 | 692926.0 | 42974.0 | 146340.0 | 23453.0 | 32141.0 | 448018.0 |
Владимирская область | 1202174.0 | 8484.0 | 629526.0 | 53615.0 | 132400.0 | 41895.0 | 60315.0 | 341301.0 |
Волгоградская область | 2003455.0 | 12696.0 | 1265720.0 | 87657.0 | 240998.0 | 55325.0 | 71142.0 | 810598.0 |
Вологодская область | 987574.0 | 6596.0 | 601999.0 | 49492.0 | 93417.0 | 40306.0 | 57064.0 | 361720.0 |
Воронежская область | 1918524.0 | 13073.0 | 1291271.0 | 81081.0 | 292379.0 | 47974.0 | 69813.0 | 800024.0 |
Город Байконур (Республика Казахстан) | 15116.0 | 185.0 | 10422.0 | 586.0 | 1288.0 | 317.0 | 722.0 | 7509.0 |
Город Москва | 7309869.0 | 87698.0 | 4159740.0 | 267418.0 | 814573.0 | 214703.0 | 868736.0 | 1994310.0 |
Город Санкт-Петербург | 3849426.0 | 33331.0 | 2355236.0 | 110979.0 | 311937.0 | 157768.0 | 370799.0 | 1403753.0 |
Еврейская автономная область | 135703.0 | 1208.0 | 78205.0 | 6632.0 | 14796.0 | 2763.0 | 5102.0 | 48912.0 |
Забайкальский край | 831712.0 | 5271.0 | 493136.0 | 49612.0 | 71636.0 | 15015.0 | 29466.0 | 327407.0 |
Ивановская область | 866474.0 | 5338.0 | 513901.0 | 37650.0 | 95005.0 | 23060.0 | 37016.0 | 321170.0 |
Иркутская область | 1915179.0 | 12186.0 | 1060537.0 | 88419.0 | 242097.0 | 41152.0 | 94008.0 | 594861.0 |
Кабардино-Балкарская Республика | 528147.0 | 418.0 | 385368.0 | 11888.0 | 53261.0 | 11753.0 | 8937.0 | 299529.0 |
Калужская область | 798196.0 | 6327.0 | 500606.0 | 37634.0 | 101459.0 | 21427.0 | 40911.0 | 299175.0 |
Камчатский край | 256522.0 | 1951.0 | 154696.0 | 16504.0 | 25009.0 | 5430.0 | 14015.0 | 93738.0 |
Карачаево-Черкесская Республика | 319473.0 | 631.0 | 290989.0 | 2851.0 | 16937.0 | 2162.0 | 2629.0 | 266410.0 |
Кемеровская область | 2076673.0 | 16002.0 | 1626578.0 | 112067.0 | 133705.0 | 37450.0 | 75519.0 | 1267837.0 |
Кировская область | 1125794.0 | 7864.0 | 682321.0 | 54531.0 | 127982.0 | 36005.0 | 63993.0 | 399810.0 |
Костромская область | 567472.0 | 3076.0 | 345513.0 | 28204.0 | 90714.0 | 16094.0 | 26517.0 | 183984.0 |
Краснодарский край | 3803307.0 | 32893.0 | 2659197.0 | 176119.0 | 496909.0 | 88976.0 | 181844.0 | 1715349.0 |
Красноярский край | 2192321.0 | 16279.0 | 1287567.0 | 112222.0 | 235058.0 | 46123.0 | 109827.0 | 784337.0 |
Курганская область | 751903.0 | 4314.0 | 478077.0 | 41340.0 | 83955.0 | 19280.0 | 27725.0 | 305777.0 |
Курская область | 947765.0 | 6350.0 | 600367.0 | 49744.0 | 122775.0 | 23101.0 | 38002.0 | 366745.0 |
Ленинградская область | 1281947.0 | 10664.0 | 800093.0 | 54857.0 | 114951.0 | 47518.0 | 80874.0 | 501893.0 |
Липецкая область | 954695.0 | 7751.0 | 618784.0 | 44697.0 | 132408.0 | 24722.0 | 34778.0 | 382179.0 |
... | ... | ... | ... | ... | ... | ... | ... | ... |
Республика Калмыкия | 214497.0 | 1242.0 | 131760.0 | 3374.0 | 23295.0 | 3562.0 | 8029.0 | 93500.0 |
Республика Карелия | 558774.0 | 3839.0 | 305600.0 | 26579.0 | 50957.0 | 18886.0 | 37798.0 | 171380.0 |
Республика Коми | 750661.0 | 6970.0 | 518810.0 | 40314.0 | 70135.0 | 22738.0 | 43759.0 | 341864.0 |
Республика Марий Эл | 537932.0 | 3984.0 | 377164.0 | 24895.0 | 84200.0 | 15175.0 | 24282.0 | 228612.0 |
Республика Мордовия | 649355.0 | 3796.0 | 577911.0 | 13635.0 | 42060.0 | 6448.0 | 9353.0 | 506415.0 |
Республика Саха (Якутия) | 614351.0 | 3978.0 | 453719.0 | 20010.0 | 65871.0 | 20193.0 | 29712.0 | 317933.0 |
Республика Северная Осетия - Алания | 512245.0 | 3995.0 | 409435.0 | 13063.0 | 87017.0 | 12864.0 | 6848.0 | 289643.0 |
Республика Тыва | 159341.0 | 860.0 | 146720.0 | 2574.0 | 6370.0 | 2023.0 | 2925.0 | 132828.0 |
Республика Хакасия | 382578.0 | 2819.0 | 244660.0 | 20991.0 | 50872.0 | 8878.0 | 19400.0 | 144519.0 |
Ростовская область | 3315673.0 | 21742.0 | 2091438.0 | 132418.0 | 423884.0 | 76633.0 | 134461.0 | 1324042.0 |
Рязанская область | 967998.0 | 6508.0 | 614459.0 | 47068.0 | 132981.0 | 25562.0 | 37903.0 | 370945.0 |
Самарская область | 2562916.0 | 20828.0 | 1536839.0 | 117828.0 | 320128.0 | 61361.0 | 125423.0 | 912099.0 |
Саратовская область | 1991376.0 | 12400.0 | 1310761.0 | 66985.0 | 206818.0 | 43267.0 | 59006.0 | 934685.0 |
Сахалинская область | 398893.0 | 2846.0 | 225504.0 | 20016.0 | 45730.0 | 8856.0 | 22337.0 | 128565.0 |
Свердловская область | 3527808.0 | 25560.0 | 2048423.0 | 107819.0 | 251690.0 | 113353.0 | 237780.0 | 1337781.0 |
Смоленская область | 816276.0 | 5843.0 | 476106.0 | 38246.0 | 111182.0 | 20930.0 | 32516.0 | 273232.0 |
Ставропольский край | 1983954.0 | 12448.0 | 1183292.0 | 83543.0 | 215600.0 | 37551.0 | 75724.0 | 770874.0 |
Тамбовская область | 884888.0 | 5570.0 | 614521.0 | 28179.0 | 107797.0 | 13973.0 | 19594.0 | 444978.0 |
Тверская область | 1137087.0 | 7076.0 | 660420.0 | 49384.0 | 131591.0 | 32835.0 | 59302.0 | 387308.0 |
Территория за пределами РФ | 459661.0 | 5838.0 | 436093.0 | 12006.0 | 31785.0 | 8674.0 | 59942.0 | 323686.0 |
Томская область | 787075.0 | 5194.0 | 453117.0 | 35139.0 | 86403.0 | 16966.0 | 53028.0 | 261581.0 |
Тульская область | 1249121.0 | 8862.0 | 858707.0 | 50218.0 | 147019.0 | 29601.0 | 43917.0 | 587952.0 |
Тюменская область | 1056505.0 | 6915.0 | 829264.0 | 59083.0 | 95398.0 | 20455.0 | 43047.0 | 611281.0 |
Удмуртская Республика | 1218251.0 | 9048.0 | 775357.0 | 49160.0 | 116277.0 | 26803.0 | 67362.0 | 515755.0 |
Ульяновская область | 1048667.0 | 6926.0 | 659233.0 | 46384.0 | 160089.0 | 27783.0 | 37437.0 | 387540.0 |
Хабаровский край | 1056125.0 | 8733.0 | 645264.0 | 68500.0 | 115436.0 | 31944.0 | 62145.0 | 367239.0 |
Челябинская область | 2757879.0 | 25366.0 | 1704033.0 | 97869.0 | 254542.0 | 88177.0 | 138907.0 | 1124538.0 |
Чувашская Республика - Чувашия | 954572.0 | 10465.0 | 692492.0 | 39707.0 | 144676.0 | 31201.0 | 38838.0 | 438070.0 |
Чукотский автономный округ | 35968.0 | 428.0 | 28909.0 | 2106.0 | 2651.0 | 633.0 | 2209.0 | 21310.0 |
Ямало-Ненецкий автономный округ | 358834.0 | 2669.0 | 332293.0 | 17456.0 | 18738.0 | 4979.0 | 7807.0 | 283313.0 |
80 rows × 8 columns
Сначала в .groupby()
мы указали переменную, по которой нужно выполнить группировку, затем в .agg()
мы указали функцию, которую нужно выполнить. В нашем случае это 'sum'
, поскольку нам нужно просто сложить все показатели в пределах одного региона. Применять можно и другие функции, например, считать среднее:
d.groupby('region').agg('mean').head() # mean - среднее
total | invalid | valid | Zh | Zu | Mi | Pr | Pu | |
---|---|---|---|---|---|---|---|---|
region | ||||||||
Алтайский край | 1053.344791 | 6.446831 | 624.825994 | 52.610634 | 140.529001 | 24.641783 | 44.993555 | 362.051020 |
Амурская область | 845.874840 | 6.012771 | 504.464879 | 50.724138 | 86.121328 | 17.361430 | 29.463602 | 320.794381 |
Архангельская область | 1004.754065 | 5.611789 | 578.752033 | 52.001016 | 93.138211 | 33.763211 | 61.085366 | 338.764228 |
Астраханская область | 1313.324232 | 8.715017 | 729.515358 | 37.402730 | 115.464164 | 31.732082 | 37.325939 | 507.590444 |
Белгородская область | 968.472000 | 8.167200 | 711.811200 | 47.648800 | 168.863200 | 28.480800 | 39.845600 | 426.972800 |
Или сразу несколько статистик. которые можно указать в .agg()
в виде списка.
d.groupby('region').agg(['mean', 'median']).head() # среднее и медиана
total | invalid | valid | Zh | Zu | Mi | Pr | Pu | |||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
mean | median | mean | median | mean | median | mean | median | mean | median | mean | median | mean | median | mean | median | |
region | ||||||||||||||||
Алтайский край | 1053.344791 | 823.0 | 6.446831 | 4.0 | 624.825994 | 495.0 | 52.610634 | 41.0 | 140.529001 | 109.5 | 24.641783 | 15.0 | 44.993555 | 22.0 | 362.051020 | 305.5 |
Амурская область | 845.874840 | 523.0 | 6.012771 | 4.0 | 504.464879 | 326.0 | 50.724138 | 31.0 | 86.121328 | 52.0 | 17.361430 | 9.0 | 29.463602 | 12.0 | 320.794381 | 224.0 |
Архангельская область | 1004.754065 | 581.5 | 5.611789 | 2.0 | 578.752033 | 332.5 | 52.001016 | 29.0 | 93.138211 | 44.0 | 33.763211 | 19.0 | 61.085366 | 20.5 | 338.764228 | 230.5 |
Астраханская область | 1313.324232 | 1283.5 | 8.715017 | 6.0 | 729.515358 | 692.5 | 37.402730 | 31.0 | 115.464164 | 100.5 | 31.732082 | 22.0 | 37.325939 | 22.0 | 507.590444 | 480.0 |
Белгородская область | 968.472000 | 802.0 | 8.167200 | 6.0 | 711.811200 | 633.0 | 47.648800 | 41.0 | 168.863200 | 140.5 | 28.480800 | 21.0 | 39.845600 | 22.0 | 426.972800 | 397.0 |
Кроме того, внутри .agg()
можно указывать свои функции. Например, нас интересует разница между максимальным и минимальным значением. Сначала напишем функцию my_diff
, которая будет определять такую разность:
def my_diff(x):
return max(x) - min(x)
Проверим, как она работает:
my_diff([4, 6, 8]) # все верно, 8 - 4 = 4
4
Теперь используем эту функцию внутри .agg()
:
d.groupby('region').agg(my_diff).head() # везде смотрим на первые 5 строк
total | invalid | valid | Zh | Zu | Mi | Pr | Pu | |
---|---|---|---|---|---|---|---|---|
region | ||||||||
Алтайский край | 3030.0 | 72.0 | 2389.0 | 379.0 | 573.0 | 131.0 | 351.0 | 1639.0 |
Амурская область | 2942.0 | 130.0 | 1773.0 | 267.0 | 404.0 | 92.0 | 197.0 | 1201.0 |
Архангельская область | 2953.0 | 76.0 | 1951.0 | 232.0 | 407.0 | 153.0 | 369.0 | 1205.0 |
Астраханская область | 2936.0 | 223.0 | 1862.0 | 209.0 | 411.0 | 157.0 | 234.0 | 1367.0 |
Белгородская область | 2998.0 | 71.0 | 2118.0 | 234.0 | 612.0 | 108.0 | 335.0 | 1268.0 |
Всё, что мы пока сделали, очень интересно, но есть проблема: все данные пока даны в абсолютных значениях, не в процентах. Это неудобно. Давайте сгруппируем данные по региону и добавим в базу с агрегированными данными новые столбцы: явка в процентах и проценты голосов за каждого кандидата.
Для этого необходимо вспомнить, как считается явка и проценты голосов. Явка считается так: суммируем число действительных и недействительных бюллетеней. Чтобы получить явку в процентах, делим явку на общее число зарегистрированных избирателей и домножаем на $100$, чтобы перевести долю в проценты. Проценты голосов за кандидатов считаем от явки, берем число голосов за кандидата, делим на явку и домножаем на $100$. Проделаем это поэтапно.
Сначала сохраним результат агрегирования в переменную regs
и добавим новый столбец для явки в абсолютных значениях (в голосах).
regs = d.groupby('region').agg('sum')
regs["turnout"] = regs.invalid + regs.valid # новый столбец - сумма двух старых
regs.head(3)
total | invalid | valid | Zh | Zu | Mi | Pr | Pu | turnout | |
---|---|---|---|---|---|---|---|---|---|
region | |||||||||
Алтайский край | 1961328.0 | 12004.0 | 1163426.0 | 97961.0 | 261665.0 | 45883.0 | 83778.0 | 674139.0 | 1175430.0 |
Амурская область | 662320.0 | 4708.0 | 394996.0 | 39717.0 | 67433.0 | 13594.0 | 23070.0 | 251182.0 | 399704.0 |
Архангельская область | 988678.0 | 5522.0 | 569492.0 | 51169.0 | 91648.0 | 33223.0 | 60108.0 | 333344.0 | 575014.0 |
Теперь добавим столбец с явкой в процентах:
regs["turnout_perc"] = regs.turnout / regs.total * 100
regs.head(3)
total | invalid | valid | Zh | Zu | Mi | Pr | Pu | turnout | turnout_perc | |
---|---|---|---|---|---|---|---|---|---|---|
region | ||||||||||
Алтайский край | 1961328.0 | 12004.0 | 1163426.0 | 97961.0 | 261665.0 | 45883.0 | 83778.0 | 674139.0 | 1175430.0 | 59.930313 |
Амурская область | 662320.0 | 4708.0 | 394996.0 | 39717.0 | 67433.0 | 13594.0 | 23070.0 | 251182.0 | 399704.0 | 60.349076 |
Архангельская область | 988678.0 | 5522.0 | 569492.0 | 51169.0 | 91648.0 | 33223.0 | 60108.0 | 333344.0 | 575014.0 | 58.159886 |
Осталось проделать аналогичные операции для голосов за разных кандидатов. Но повторять одно и то же пять раз не хочется (а что бы мы делали, если бы кандидатов было больше?). Давайте напишем функцию, которая будет принимать на вход столбец, делить все его значения на значения из столбца turnout и переводить все в проценты.
def to_perc(x):
return x / regs.turnout * 100
А теперь выберем из базы данных столбцы с голосами за кандидатов и применим к ним нашу функцию.
perc = regs[['Zh' ,'Zu', 'Mi', 'Pr', 'Pu']].apply(to_perc, axis = 0) # axis = 0 - по столбцам, не по строкам
perc.head(3)
Zh | Zu | Mi | Pr | Pu | |
---|---|---|---|---|---|
region | |||||
Алтайский край | 8.334056 | 22.261215 | 3.903508 | 7.127434 | 57.352543 |
Амурская область | 9.936603 | 16.870734 | 3.401017 | 5.771771 | 62.842003 |
Архангельская область | 8.898740 | 15.938395 | 5.777772 | 10.453311 | 57.971458 |
Нужно переименовать столбцы в базе perc
. Давайте сделаем это по-умному: возьмем названия столбцов в perc
и приклеим к ним часть с _perc
, чтобы названия столбцов с показателями в процентах отличались от показателей в абсолютных числах.
old_cols = list(perc.columns)
old_cols
['Zh', 'Zu', 'Mi', 'Pr', 'Pu']
new_cols = [x + "_perc" for x in old_cols]
new_cols
['Zh_perc', 'Zu_perc', 'Mi_perc', 'Pr_perc', 'Pu_perc']
perc.columns = new_cols
perc.head(3)
Zh_perc | Zu_perc | Mi_perc | Pr_perc | Pu_perc | |
---|---|---|---|---|---|
region | |||||
Алтайский край | 8.334056 | 22.261215 | 3.903508 | 7.127434 | 57.352543 |
Амурская область | 9.936603 | 16.870734 | 3.401017 | 5.771771 | 62.842003 |
Архангельская область | 8.898740 | 15.938395 | 5.777772 | 10.453311 | 57.971458 |
Ура! Последний аккорд: соединим нашу таблицу regs
с таблицей perc
, чтобы все показатели были в одном месте. Способов объединять датафреймы много, но давайте обсудим их в следующий раз. А пока просто склеим две таблицы по столбцам с помощью метода .concat()
.
final = pd.concat([regs, perc], axis = 1) # axis = 1 - по столбцам
final.head()
total | invalid | valid | Zh | Zu | Mi | Pr | Pu | turnout | turnout_perc | Zh_perc | Zu_perc | Mi_perc | Pr_perc | Pu_perc | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
region | |||||||||||||||
Алтайский край | 1961328.0 | 12004.0 | 1163426.0 | 97961.0 | 261665.0 | 45883.0 | 83778.0 | 674139.0 | 1175430.0 | 59.930313 | 8.334056 | 22.261215 | 3.903508 | 7.127434 | 57.352543 |
Амурская область | 662320.0 | 4708.0 | 394996.0 | 39717.0 | 67433.0 | 13594.0 | 23070.0 | 251182.0 | 399704.0 | 60.349076 | 9.936603 | 16.870734 | 3.401017 | 5.771771 | 62.842003 |
Архангельская область | 988678.0 | 5522.0 | 569492.0 | 51169.0 | 91648.0 | 33223.0 | 60108.0 | 333344.0 | 575014.0 | 58.159886 | 8.898740 | 15.938395 | 5.777772 | 10.453311 | 57.971458 |
Астраханская область | 769608.0 | 5107.0 | 427496.0 | 21918.0 | 67662.0 | 18595.0 | 21873.0 | 297448.0 | 432603.0 | 56.210824 | 5.066539 | 15.640668 | 4.298398 | 5.056137 | 68.757729 |
Белгородская область | 1210590.0 | 10209.0 | 889764.0 | 59561.0 | 211079.0 | 35601.0 | 49807.0 | 533716.0 | 899973.0 | 74.341685 | 6.618087 | 23.453926 | 3.955785 | 5.534277 | 59.303557 |
Приличную базу мы получили, можно перейти к чему-то более содержательному.
В прошлый раз мы познакомились с тем, как строить графики для переменных в базе данных. Мы уже обсудили два типа графиков для количественных данных: гистограмму и ящик с усами. Давайте посмотрим на диаграммы рассеяния – графики, которые позволяют увидеть совместное распределение пары количественных показателей.
import matplotlib # загружаем библиотеку для графиков
% matplotlib inline
А теперь сама диаграмма рассеяния (scatterplot) для показателей явка в процентах и процент за Зюганова:
final.plot.scatter('turnout_perc', 'Zu_perc')
<matplotlib.axes._subplots.AxesSubplot at 0x116c3eda0>
Можем привести график в порядок. Добавить заголовок и подписи к осям, плюс, изменить цвет точек. Для этого основной график сохраним в переменную ax
, а затем применим к ней методы, которые отвечают за добавление заголовка и названиям осей x и y.
ax = final.plot.scatter('turnout_perc', 'Zu_perc', color = "magenta") # цвет magenta
ax.set_title('Scatterplot') # заголовок для объекта ax
ax.set_xlabel('turnout rate (%)') # подпись для оси x
ax.set_ylabel('votes for Zuganov (%)') # подпись для оси y
Text(0,0.5,'votes for Zuganov (%)')
По графику видно, что, в целом, чем выше явка, тем ниже процент голосов за Зюганова. Углубляться в разные настройки графиков и в статистику не будем, но познакомимся с примером графика средствами библиотеки pandas. Построим матрицу диаграмм рассеяния (scatterplot matrix), сетку с диаграммами рассеяния для всех пар показателей.
Логично будет строить такой график для переменных в базе perc
, поскольку правильнее смотреть на связи между показателями в процентах.
from pandas.plotting import scatter_matrix # импортируем функцию
scatter_matrix(perc, diagonal='hist', figsize=(10, 10)) # строим график
array([[<matplotlib.axes._subplots.AxesSubplot object at 0x11494ceb8>, <matplotlib.axes._subplots.AxesSubplot object at 0x116d896a0>, <matplotlib.axes._subplots.AxesSubplot object at 0x116db1d30>, <matplotlib.axes._subplots.AxesSubplot object at 0x116de0400>, <matplotlib.axes._subplots.AxesSubplot object at 0x116e09a90>], [<matplotlib.axes._subplots.AxesSubplot object at 0x116e09ac8>, <matplotlib.axes._subplots.AxesSubplot object at 0x116e627f0>, <matplotlib.axes._subplots.AxesSubplot object at 0x116e8ae80>, <matplotlib.axes._subplots.AxesSubplot object at 0x116ebb550>, <matplotlib.axes._subplots.AxesSubplot object at 0x116ee5be0>], [<matplotlib.axes._subplots.AxesSubplot object at 0x116f162b0>, <matplotlib.axes._subplots.AxesSubplot object at 0x116f3f940>, <matplotlib.axes._subplots.AxesSubplot object at 0x116f68fd0>, <matplotlib.axes._subplots.AxesSubplot object at 0x116f976a0>, <matplotlib.axes._subplots.AxesSubplot object at 0x116fbed30>], [<matplotlib.axes._subplots.AxesSubplot object at 0x116ff2400>, <matplotlib.axes._subplots.AxesSubplot object at 0x117019a90>, <matplotlib.axes._subplots.AxesSubplot object at 0x11704e160>, <matplotlib.axes._subplots.AxesSubplot object at 0x1170757f0>, <matplotlib.axes._subplots.AxesSubplot object at 0x11709be80>], [<matplotlib.axes._subplots.AxesSubplot object at 0x1170cd550>, <matplotlib.axes._subplots.AxesSubplot object at 0x1170f5be0>, <matplotlib.axes._subplots.AxesSubplot object at 0x1171272b0>, <matplotlib.axes._subplots.AxesSubplot object at 0x11714f940>, <matplotlib.axes._subplots.AxesSubplot object at 0x117179fd0>]], dtype=object)
Аргумент diagonal
отвечает за тип графика, который будет находиться на диагонали (в нашем случае гистограмма – 'hist'
), а аргумент figsize
– за размер графика (по горизонтали и по вертикали). На диагоналях также можно построить сглаженные графики плотности распределения показателей:
scatter_matrix(perc, diagonal='kde', figsize=(10, 10)) # kde - от kernel density estimation
array([[<matplotlib.axes._subplots.AxesSubplot object at 0x1172185f8>, <matplotlib.axes._subplots.AxesSubplot object at 0x11761b160>, <matplotlib.axes._subplots.AxesSubplot object at 0x11779d550>, <matplotlib.axes._subplots.AxesSubplot object at 0x1177c0be0>, <matplotlib.axes._subplots.AxesSubplot object at 0x1177f42b0>], [<matplotlib.axes._subplots.AxesSubplot object at 0x1177f42e8>, <matplotlib.axes._subplots.AxesSubplot object at 0x117845fd0>, <matplotlib.axes._subplots.AxesSubplot object at 0x1178766a0>, <matplotlib.axes._subplots.AxesSubplot object at 0x11789ed30>, <matplotlib.axes._subplots.AxesSubplot object at 0x1178d0400>], [<matplotlib.axes._subplots.AxesSubplot object at 0x1178f6a90>, <matplotlib.axes._subplots.AxesSubplot object at 0x117929160>, <matplotlib.axes._subplots.AxesSubplot object at 0x1179517f0>, <matplotlib.axes._subplots.AxesSubplot object at 0x117979e80>, <matplotlib.axes._subplots.AxesSubplot object at 0x1179aa550>], [<matplotlib.axes._subplots.AxesSubplot object at 0x1179d4be0>, <matplotlib.axes._subplots.AxesSubplot object at 0x117a062b0>, <matplotlib.axes._subplots.AxesSubplot object at 0x117a2d940>, <matplotlib.axes._subplots.AxesSubplot object at 0x117a57fd0>, <matplotlib.axes._subplots.AxesSubplot object at 0x117a866a0>], [<matplotlib.axes._subplots.AxesSubplot object at 0x117aafd30>, <matplotlib.axes._subplots.AxesSubplot object at 0x117ae2400>, <matplotlib.axes._subplots.AxesSubplot object at 0x117b09a90>, <matplotlib.axes._subplots.AxesSubplot object at 0x117b3c160>, <matplotlib.axes._subplots.AxesSubplot object at 0x117b627f0>]], dtype=object)
Поиграем с цветами. Изменим цвет точек на всех диаграммах рассеяния:
scatter_matrix(perc, diagonal='hist', figsize=(10, 10), color='green')
array([[<matplotlib.axes._subplots.AxesSubplot object at 0x1a2a4f1e10>, <matplotlib.axes._subplots.AxesSubplot object at 0x1a2a71c748>, <matplotlib.axes._subplots.AxesSubplot object at 0x1a2a736ac8>, <matplotlib.axes._subplots.AxesSubplot object at 0x1a2a8c7198>, <matplotlib.axes._subplots.AxesSubplot object at 0x1a2a8ee828>], [<matplotlib.axes._subplots.AxesSubplot object at 0x1a2a8ee860>, <matplotlib.axes._subplots.AxesSubplot object at 0x1a2a948588>, <matplotlib.axes._subplots.AxesSubplot object at 0x1a2a971c18>, <matplotlib.axes._subplots.AxesSubplot object at 0x1a2a9a32e8>, <matplotlib.axes._subplots.AxesSubplot object at 0x1a2a9ca978>], [<matplotlib.axes._subplots.AxesSubplot object at 0x1a2a9fe048>, <matplotlib.axes._subplots.AxesSubplot object at 0x1a2aa246d8>, <matplotlib.axes._subplots.AxesSubplot object at 0x1a2aa4cd68>, <matplotlib.axes._subplots.AxesSubplot object at 0x1a2aa7f438>, <matplotlib.axes._subplots.AxesSubplot object at 0x1a2aaa8ac8>], [<matplotlib.axes._subplots.AxesSubplot object at 0x1a2aad7198>, <matplotlib.axes._subplots.AxesSubplot object at 0x1a2ab02828>, <matplotlib.axes._subplots.AxesSubplot object at 0x1a2ab29eb8>, <matplotlib.axes._subplots.AxesSubplot object at 0x1a2ab5a588>, <matplotlib.axes._subplots.AxesSubplot object at 0x1a2ab83c18>], [<matplotlib.axes._subplots.AxesSubplot object at 0x1a2abb42e8>, <matplotlib.axes._subplots.AxesSubplot object at 0x1a2abdc978>, <matplotlib.axes._subplots.AxesSubplot object at 0x1a2ac0d048>, <matplotlib.axes._subplots.AxesSubplot object at 0x1a2ac366d8>, <matplotlib.axes._subplots.AxesSubplot object at 0x1a2ac60d68>]], dtype=object)
А теперь цвет гистограмм. Это сделать чуть сложнее, поскольку нужно задавать значения аргуметов как *kwargs
, в виде словаря:
# заодно выставим чило столбцов - bins=10
# и прозрачность 50% - alpha=0.5
scatter_matrix(perc, diagonal='hist', figsize=(10, 10), color='green',
hist_kwds = {'color': 'red', 'bins' : 10, 'alpha' : 0.4})
array([[<matplotlib.axes._subplots.AxesSubplot object at 0x1a2acc5588>, <matplotlib.axes._subplots.AxesSubplot object at 0x1a2af06550>, <matplotlib.axes._subplots.AxesSubplot object at 0x1a2b087978>, <matplotlib.axes._subplots.AxesSubplot object at 0x1a2b0b4048>, <matplotlib.axes._subplots.AxesSubplot object at 0x1a2b0db6d8>], [<matplotlib.axes._subplots.AxesSubplot object at 0x1a2b0db710>, <matplotlib.axes._subplots.AxesSubplot object at 0x1a2b137438>, <matplotlib.axes._subplots.AxesSubplot object at 0x1a2b15dac8>, <matplotlib.axes._subplots.AxesSubplot object at 0x1a2b18f198>, <matplotlib.axes._subplots.AxesSubplot object at 0x1a2b1b9828>], [<matplotlib.axes._subplots.AxesSubplot object at 0x1a2b1e0eb8>, <matplotlib.axes._subplots.AxesSubplot object at 0x1a2b211588>, <matplotlib.axes._subplots.AxesSubplot object at 0x1a2b23bc18>, <matplotlib.axes._subplots.AxesSubplot object at 0x1a2b26c2e8>, <matplotlib.axes._subplots.AxesSubplot object at 0x1a2b293518>], [<matplotlib.axes._subplots.AxesSubplot object at 0x1a2b2bcba8>, <matplotlib.axes._subplots.AxesSubplot object at 0x1a2b2ef278>, <matplotlib.axes._subplots.AxesSubplot object at 0x1a2b316908>, <matplotlib.axes._subplots.AxesSubplot object at 0x1a2b340f98>, <matplotlib.axes._subplots.AxesSubplot object at 0x1a2b372668>], [<matplotlib.axes._subplots.AxesSubplot object at 0x1a2b398cf8>, <matplotlib.axes._subplots.AxesSubplot object at 0x1a2b3ca3c8>, <matplotlib.axes._subplots.AxesSubplot object at 0x1a2b3f2a58>, <matplotlib.axes._subplots.AxesSubplot object at 0x1a2b426128>, <matplotlib.axes._subplots.AxesSubplot object at 0x1a2b44d7b8>]], dtype=object)
На этом пока всё.