Основы программирования в Python

Алла Тамбовцева, НИУ ВШЭ

Библиотека pandas. Продолжение.

Группировка и агрегирование: методы .groupby() и .agg()

Часто случается, что данные необходимо сгруппировать по какому-то признаку – по значениям определенной переменной. На входе имеется таблица (датафрейм), а на выходе хочется получить несколько таблиц: отдельная таблица для каждого значения. Давайте рассмотрим такой пример. У нас есть база данных с результатами выборов, и нам нужно сгруппировать данные по регионам.

Для начала импортируем библиотеку pandas и загрузим файл с данными.

In [2]:
import pandas as pd

Для разнообразия загрузим файл по ссылке с Github:

In [3]:
df = pd.read_csv("elect.csv")

В таблице сохранены результаты выборов президента России 2012 года.

In [4]:
df.head()
Out[4]:
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

In [5]:
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

Таблица достаточно большая, поэтому давайте выберем те столбцы, которые понадобятся нам для работы. Какие именно? Столбцы в этой базе имеют порядковый номер строки в таблице на сайте Центральной избирательной комиссии.

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

In [6]:
d = df[["kom1", "kom2", "kom3", 
        "1", "9", "10", "19", 
        "20", "21", "22", "23"]]
In [7]:
d.head()
Out[7]:
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

Теперь присвоим столбцам более информативные названия:

In [8]:
d.columns = ["region", "tik", "uik", 
             "total", "invalid", "valid", 
             "Zh", "Zu", "Mi", "Pr", "Pu"]
In [9]:
d.head() # опять посмотрим
Out[9]:
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 в таком случае будет не совсем удачно, поскольку в нем будет много повторяющихся значений. Посмотрим только на уникальные:

In [10]:
d.region.unique() # метод unique - уникальные значения
Out[10]:
array(['Республика Адыгея (Адыгея)', 'Республика Алтай',
       'Республика Башкортостан', 'Республика Бурятия',
       'Республика Дагестан', 'Ðåñïóáëèêà Äàãåñòàí',
       'Республика Ингушетия', 'Кабардино-Балкарская Республика',
       'Республика Калмыкия', 'Карачаево-Черкесская Республика',
       'Республика Карелия', 'Республика Коми', 'Республика Марий Эл',
       'Республика Мордовия', 'Республика Саха (Якутия)',
       'Республика Северная Осетия - Алания', 'Республика Тыва',
       'Удмуртская Республика', 'Республика Хакасия',
       'Чувашская Республика - Чувашия', 'Алтайский край',
       'Забайкальский край', 'Камчатский край', 'Краснодарский край',
       'Красноярский край', 'Пермский край', 'Приморский край',
       'Ставропольский край', 'Хабаровский край', 'Õàáàðîâñêèé êðàé',
       'Амурская область', 'Архангельская область',
       'Астраханская область', 'Белгородская область', 'Брянская область',
       'Владимирская область', 'Волгоградская область',
       'Вологодская область', 'Воронежская область', 'Ивановская область',
       'Иркутская область', 'Калужская область', 'Кемеровская область',
       'Кировская область', 'Костромская область', 'Курганская область',
       'Курская область', 'Ленинградская область', 'Липецкая область',
       'Магаданская область', 'Московская область', 'Мурманская область',
       'Ìóðìàíñêàÿ îáëàñòü', 'Нижегородская область',
       'Новгородская область', 'Новосибирская область', 'Омская область',
       'Оренбургская область', 'Орловская область', 'Пензенская область',
       'Псковская область', 'Ростовская область', 'Рязанская область',
       'Самарская область', 'Саратовская область', 'Сахалинская область',
       'Свердловская область', 'Смоленская область', 'Тамбовская область',
       'Тверская область', 'Томская область', 'Тульская область',
       'Тюменская область', 'Ульяновская область', 'Челябинская область',
       'Город Москва', 'Город Санкт-Петербург', 'Ãîðîä Ñàíêò-Ïåòåðáóðã',
       'Еврейская автономная область', 'Ненецкий автономный округ',
       'Чукотский автономный округ', 'Ямало-Ненецкий автономный округ',
       'Город Байконур (Республика Казахстан)',
       'Территория за пределами РФ'], dtype=object)

Видно, что в этом массиве встречаются какие-то крокозябры (названия со странной кодировкой). Давайте уберем эти строки из базы. Воспользуемся методом str.contains() для строк:

In [11]:
# отфильтруем с помощью условий
d = d[~d.region.str.contains("à")]  # ~ для отрицания

Сгруппируем данные по регионам и посчитаем для каждого региона явку в процентах и процент голосов за каждого кандидата. Группировка осуществляется с помощью метода .groupby().

In [12]:
d.groupby('region') # пока ничего не увидели
Out[12]:
<pandas.core.groupby.groupby.DataFrameGroupBy object at 0x108e60f28>

Что выдает метод .groupby()? На самом деле он делает следующее: создает список, состоящий из кортежей. Каждый кортеж – это пара название группы-соответствующий ей фрагмент датафрейма.

In [ ]:
# посмотрим на все сразу
for g in d.groupby('region'):
    print(g)

В таком виде метод .groupby() дает нам немного. Мы же хотим не просто получать отдельные таблицы, а агрегировать данные по регионам ‒ суммировать все показатели (число избирателей, бюллетеней, голосов) по каждому региону. Тут на помощь придет метод .agg(), который выполняет агрегирование по группам.

In [13]:
d.groupby('region').agg('sum')
Out[13]:
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', поскольку нам нужно просто сложить все показатели в пределах одного региона. Применять можно и другие функции, например, считать среднее:

In [14]:
d.groupby('region').agg('mean').head() # mean - среднее
Out[14]:
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() в виде списка.

In [15]:
d.groupby('region').agg(['mean', 'median']).head() # среднее и медиана
Out[15]:
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, которая будет определять такую разность:

In [16]:
def my_diff(x):
    return max(x) - min(x)

Проверим, как она работает:

In [17]:
my_diff([4, 6, 8]) # все верно, 8 - 4 = 4
Out[17]:
4

Теперь используем эту функцию внутри .agg():

In [18]:
d.groupby('region').agg(my_diff).head() # везде смотрим на первые 5 строк
Out[18]:
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 и добавим новый столбец для явки в абсолютных значениях (в голосах).

In [20]:
regs = d.groupby('region').agg('sum')

# новый столбец - сумма двух старых
regs["turnout"] = regs.invalid + regs.valid 
In [21]:
regs.head(3)
Out[21]:
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

Теперь добавим столбец с явкой в процентах:

In [22]:
regs["turnout_perc"] = regs.turnout / regs.total * 100
In [23]:
regs.head(3)
Out[23]:
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 и переводить все в проценты.

In [24]:
def to_perc(x):
    return x / regs.turnout * 100

А теперь выберем из базы данных столбцы с голосами за кандидатов и применим к ним нашу функцию – воспользуемся методом .apply().

In [25]:
# axis = 0 - по столбцам, не по строкам 
perc = regs[['Zh' ,'Zu', 'Mi', 
             'Pr', 'Pu']].apply(to_perc, axis = 0) 
In [26]:
perc.head(3)
Out[26]:
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, чтобы названия столбцов с показателями в процентах отличались от показателей в абсолютных числах.

In [28]:
# rename принимает на вход словарь пар
# старое название : новое название

perc = perc.rename(columns = {'Zh':'Zh_perc', 'Zu':'Zu_perc', 
                       'Mi':'Mi_perc', 'Pr':'Pr_perc', 
                       'Pu':'Pu_perc'})
perc.head()
Out[28]:
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
Астраханская область 5.066539 15.640668 4.298398 5.056137 68.757729
Белгородская область 6.618087 23.453926 3.955785 5.534277 59.303557

Ура! Последний аккорд: соединим нашу таблицу regs с таблицей perc, чтобы все показатели были в одном месте. Способов объединять датафреймы много, но давайте обсудим их в следующий раз. А пока просто склеим две таблицы по столбцам с помощью метода .concat().

In [29]:
# axis = 1 - склеивание по столбцам
final = pd.concat([regs, perc], axis = 1) 
In [30]:
final.head()
Out[30]:
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

Приличную базу мы получили, можно перейти к чему-то более содержательному.

Ещё немного про визуализацию данных

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

In [30]:
import matplotlib # загружаем библиотеку для графиков
In [31]:
%matplotlib inline 

А теперь сама диаграмма рассеяния (scatterplot) для показателей явка в процентах и процент за Зюганова:

In [32]:
final.plot.scatter('turnout_perc', 'Zu_perc')
Out[32]:
<matplotlib.axes._subplots.AxesSubplot at 0x1097b44a8>

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

In [34]:
# цвет red
ax = final.plot.scatter('turnout_perc', 'Zu_perc', color = "red") 
# заголовок для объекта ax
ax.set_title('Scatterplot') 
# подпись для оси x
ax.set_xlabel('Turnout rate (in %)') 
# подпись для оси y
ax.set_ylabel('Votes for Zuganov (in %)') 
Out[34]:
Text(0,0.5,'Votes for Zuganov (in %)')

По графику видно, что, в целом, чем выше явка, тем ниже процент голосов за Зюганова. Углубляться в разные настройки графиков и в статистику не будем, но познакомимся с примером графика средствами библиотеки pandas. Построим матрицу диаграмм рассеяния (scatterplot matrix), сетку с диаграммами рассеяния для всех пар показателей.

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

In [35]:
# импортируем функцию
from pandas.plotting import scatter_matrix
In [36]:
# строим график
scatter_matrix(perc, diagonal='hist', figsize=(10, 10)) 
Out[36]:
array([[<matplotlib.axes._subplots.AxesSubplot object at 0x109a59f60>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x109a92240>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x109ab98d0>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x109ae0f60>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x109b11630>],
       [<matplotlib.axes._subplots.AxesSubplot object at 0x109b11668>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x10cf66390>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x10cf8fa20>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x10cfc30f0>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x10cfe8780>],
       [<matplotlib.axes._subplots.AxesSubplot object at 0x10d011e10>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x10d0454e0>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x10d06ab70>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x10d09c240>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x10d0c78d0>],
       [<matplotlib.axes._subplots.AxesSubplot object at 0x10d0eef60>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x10d11e630>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x10d146cc0>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x10d178390>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x10d1a1a20>],
       [<matplotlib.axes._subplots.AxesSubplot object at 0x10d1d40f0>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x10d1fc780>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x10d223e10>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x10d2544e0>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x10d280b70>]],
      dtype=object)

Аргумент diagonal отвечает за тип графика, который будет находиться на диагонали (в нашем случае гистограмма – 'hist'), а аргумент figsize – за размер графика (по горизонтали и по вертикали). На диагоналях также можно построить сглаженные графики плотности распределения показателей:

In [37]:
# kde - от kernel density estimation
scatter_matrix(perc, diagonal='kde', 
               figsize=(10, 10)) 
Out[37]:
array([[<matplotlib.axes._subplots.AxesSubplot object at 0x10dd72978>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x10e0c1518>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x10e0ddba8>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x10e272278>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x10e299908>],
       [<matplotlib.axes._subplots.AxesSubplot object at 0x10e299940>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x10e2f2668>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x10e31dcf8>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x10e34f3c8>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x10e375a58>],
       [<matplotlib.axes._subplots.AxesSubplot object at 0x10e3a9128>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x10e3ce7b8>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x10e3f8e48>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x10e42a518>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x10e454ba8>],
       [<matplotlib.axes._subplots.AxesSubplot object at 0x10e485278>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x10e647908>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x10e9adf98>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x10e9df668>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x10ea07cf8>],
       [<matplotlib.axes._subplots.AxesSubplot object at 0x10ea393c8>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x10ea60a58>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x10ea94128>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x10eabc7b8>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x10eae3e48>]],
      dtype=object)

Поиграем с цветами. Изменим цвет точек на всех диаграммах рассеяния:

In [38]:
scatter_matrix(perc, diagonal='hist', 
               figsize=(10, 10), 
               color='green')
Out[38]:
array([[<matplotlib.axes._subplots.AxesSubplot object at 0x1a1cb07358>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1a1cce8828>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1a1cd07ba8>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1a1cd39278>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1a1cd5f908>],
       [<matplotlib.axes._subplots.AxesSubplot object at 0x1a1cd5f940>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1a1cdba668>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1a1cde1cf8>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1a1ce143c8>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1a1ce3ca58>],
       [<matplotlib.axes._subplots.AxesSubplot object at 0x1a1ce70128>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1a1ce987b8>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1a1cebfe48>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1a1cef1518>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1a1cf19ba8>],
       [<matplotlib.axes._subplots.AxesSubplot object at 0x1a1cf4a278>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1a1cf72908>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1a1cf9cf98>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1a1cfce668>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1a1cff5cf8>],
       [<matplotlib.axes._subplots.AxesSubplot object at 0x1a1d0243c8>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1a1d04fa58>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1a1d082128>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1a1d0a77b8>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1a1d0d1e48>]],
      dtype=object)

А теперь цвет гистограмм. Это сделать чуть сложнее, поскольку нужно задавать значения аргуметов как *kwargs, в виде словаря:

In [39]:
# заодно выставим чило столбцов - 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})
Out[39]:
array([[<matplotlib.axes._subplots.AxesSubplot object at 0x10dda2eb8>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1a1d4d3e80>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1a1d4f2240>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1a1d5178d0>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1a1d53ff60>],
       [<matplotlib.axes._subplots.AxesSubplot object at 0x1a1d53ff98>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1a1d599cc0>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1a1d5ca390>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1a1d5f3a20>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1a1d6240f0>],
       [<matplotlib.axes._subplots.AxesSubplot object at 0x1a1d64c780>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1a1d675e10>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1a1d6a74e0>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1a1d6cfb70>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1a1d702240>],
       [<matplotlib.axes._subplots.AxesSubplot object at 0x1a1d7298d0>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1a1d752f60>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1a1d783630>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1a1d7abcc0>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1a1d7de390>],
       [<matplotlib.axes._subplots.AxesSubplot object at 0x1a1d805a20>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1a1d8360f0>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1a1d860780>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1a1d887e10>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1a1d8b94e0>]],
      dtype=object)

На этом пока всё.