Привет!

В сентябре этого (2019) года прошли выборы Губернатора Санкт-Петербурга. Все данные о голосовании находятся в открытом доступе на сайте избирательной комиссии, мы не будем ничего ломать, а просто визуализируем информацию с этого сайта www.st-petersburg.vybory.izbirkom.ru в нужном для нас виде, проведем совсем несложный анализ и определим некоторые «волшебные» закономерности.

Обычно для подобных задач я использую Google Colab. Это сервис, который позволяет запускать Jupyter Notebook'и, имея доступ к GPU (NVidia Tesla K80) бесплатно, это заметно ускорит парсинг данных и их дальнейшую обработку. Мне понадобились некоторые подготовительные работы перед импортом.

%%time 
!apt update
!apt upgrade
!apt install gdal-bin python-gdal python3-gdal 
# Install rtree - Geopandas requirment
!apt install python3-rtree 
# Install Geopandas
!pip install git+git://github.com/geopandas/geopandas.git
# Install descartes - Geopandas requirment
!pip install descartes

Далее импорты.

import requests 
from bs4 import BeautifulSoup 
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import geopandas as gpd
import xlrd

Описание используемых библиотек


  • requests — модуль для запроса на подключение к сайту

  • BeautifulSoup — модуль для парсинга html и xml документов; позволяет получить доступ напрямую к содержимому любых тегов в html

  • numpy — математический модуль с базовым и необходимым набором математических функций

  • pandas — библиотека для анализа данных

  • matplotlib.pyplot — модуль-набор методов построения

  • geopandas — модуль для построения карты выборов

  • xlrd — модуль для чтения табличных файлов

Настал момент собирать сами данные, парсим. Избирком позаботился о нашем времени и предоставил отчетность в таблицах, это удобно.

### Parser

list_of_TIKS = []
for i in range (1, 31):
    list_of_TIKS.append('Территориальная избирательная комиссия №' + str(i))

num_of_voters = []
num_of_voters_voted = []
appearence = []
votes_for_Amosov_percent = []
votes_for_Beglov_percent = []
votes_for_Tikhonova_percent = []

url = "http://www.st-petersburg.vybory.izbirkom.ru/region/region/st-petersburg?action=show&root=1&tvd=27820001217417&vrn=27820001217413&region=78&global=&sub_region=78&prver=0&pronetvd=null&vibid=27820001217417&type=222"
response = requests.get(url)
page = BeautifulSoup(response.content, "lxml")

main_links = page.find_all('a')
for TIK in list_of_TIKS:
    for main_tag in main_links:
        main_link = main_tag.get('href')
        if TIK in main_tag:
            current_TIK = pd.read_html(main_link, encoding='cp1251',  header=0)[7]
            
            num_of_voters.extend(int(current_TIK.iloc[0,i]) for i in range (len(current_TIK.columns)))
            num_of_voters_voted.extend(int(current_TIK.iloc[2,i]) + int(current_TIK.iloc[3,i]) for i in range (len(current_TIK.columns)))
            appearence.extend(round((int(current_TIK.iloc[2,i]) + int(current_TIK.iloc[3,i]))/int(current_TIK.iloc[0,i])*100, 2) for i in range (len(current_TIK.columns)))
            
            votes_for_Amosov_percent.extend(round(float(current_TIK.iloc[12,i][-6]+current_TIK.iloc[12,i][-5]+current_TIK.iloc[12,i][-4]+current_TIK.iloc[12,i][-3]+current_TIK.iloc[12,i][-2]),2) for i in range (len(current_TIK.columns)))
            votes_for_Beglov_percent.extend(round(float(current_TIK.iloc[13,i][-6]+current_TIK.iloc[13,i][-5]+current_TIK.iloc[13,i][-4]+current_TIK.iloc[13,i][-3]+current_TIK.iloc[13,i][-2]),2) for i in range (len(current_TIK.columns)))
            votes_for_Tikhonova_percent.extend(round(float(current_TIK.iloc[14,i][-6]+current_TIK.iloc[14,i][-5]+current_TIK.iloc[14,i][-4]+current_TIK.iloc[14,i][-3]+current_TIK.iloc[14,i][-2]),2) for i in range (len(current_TIK.columns)))

Итак, то, о чем и шла речь. Данные в Google Colab собираются шустро, но их не так уж и много.

Прежде, чем строить различные графики и карты, нам хорошо иметь представление, что мы называем «датасетом».

Разбор данных избиркома


По городу Санкт-Петербургу расположены 30 территориальных комиссий к ним же в 31-ый столбец относим цифровые избирательные участки.

image

В каждой территориальной комиссии несколько десятков УИКов (участковых избирательных комиссий).

image

Основное, что нас интересует, это явка на каждом избирательном участке, и какие зависимости мы можем наблюдать. Я буду отталкиваться от следующих:

  • зависимость явки и количества избирательных участков;

  • зависимость процента голосов за кандидатов от явки;

  • зависимость явки от количества избирателей на участке.

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

Построим то, что придумали.

### Plots Data

#Votes in percent (appearence) - no need in extra computations

#Appearence (num of voters) - no need in extera computations

#Number of UIKs (appearence)
interval = 1

interval_num_of_UIKs = []

for i in range (int(100/interval+1/interval)):
    interval_num_of_UIKs.append(0)

for i in range (0, int(100/interval+1/interval), interval):
    for j in range (len(appearence)):
        if appearence[j] < (i + interval/2) and appearence[j] >= (i - interval/2):
            interval_num_of_UIKs[i] = interval_num_of_UIKs[i] + 1

### Plotting

#Number of UIKs (appearence)

plt.figure(figsize=(10, 6))
plt.plot(interval_num_of_UIKs)
plt.axis([0, 100, 0, 200])
plt.ylabel('Number of UIKs in a 1% range')
plt.xlabel('Appearence')
plt.show()

#Votes in percent (appearence)

plt.figure(figsize=(10, 10))
plt.scatter(appearence, votes_for_Amosov_percent, c = 'g', s = 6)
plt.scatter(appearence, votes_for_Beglov_percent, c = 'b', s = 6)
plt.scatter(appearence, votes_for_Tikhonova_percent, c = 'r', s = 6)
plt.ylabel('Votes in % for each candidate')
plt.xlabel('Appearence')
plt.show()

#Appearence (num of voters)

plt.figure(figsize=(10, 6))
plt.scatter(num_of_voters, appearence, c = 'y', s = 6)
plt.ylabel('Appearence')
plt.xlabel('Number of voters registereg in UIK')
plt.show()

Зависимость явки и количества избирательных участков

image

Зависимость процента голосов за кандидатов от явки

  • «зеленый» — голоса за Амосова

  • «синий» — за Беглова

  • «красный» — за Тихонову

image

Зависимость явки от количества избирателей на участке

image

Построения вполне сносные, но в ходе работы выяснилось, что в среднем на участке 400 человек и процент за Беглова от 50 до 70, но есть два участка с явкой >1200 чел и процентом 90+-0.2. Интересно, что такое произошло на этих участках. Какие-то фантастические агитаторы поработали? Или просто подвезли 10 автобусов людей и заставили голосовать? Так или иначе, мы взволнованы, небольшое такое расследование получается. Но нам еще карты рисовать. Продолжим.

Визуальное представление и работа с geopandas


### Extra data for visualization: appearence and number of voters by municipal districts

current_UIK = pd.read_html(url, encoding='cp1251',  header=0)[7]

num_of_voters_dist = []
num_of_voters_voted_dist = []
appearence_dist = []

for j in [num_of_voters_dist, num_of_voters_voted_dist, appearence_dist]:
    j.extend(0 for i in range (18))

districts = {
    '0' : [1], #Адмиралтейский
    '1' : [2], #Василеостровский 
    '2' : [18], #Петроградский
    '3' : [16, 30], #Центральный
    '4' : [10, 14, 22], #Выборгский
    '5' : [11, 17], #Калининский
    '6' : [4, 25], #Красногвардейский
    '7' : [5, 24], #Невский
    '8' : [23, 29], #Фрунзенский
    '9' : [9, 12, 28], #Приморский
    '10' : [13], #Курортный
    '11' : [15], #Кронштадтский
    '12' : [21], #Колпинский
    '13' : [20], #Пушкинский
    '14' : [19, 27], #Московский
    '15' : [3, 7], #Кировский
    '16' : [6, 26], #Красносельский
    '17' : [8] #Петродворцовый
}

for i in districts.keys():
    for k in range (1, 31):
        if k in districts[i]:
            num_of_voters_dist[int(i)]= num_of_voters_dist[int(i)] + int(current_UIK.iloc[0,k-1])
            num_of_voters_voted_dist[int(i)] = num_of_voters_voted_dist[int(i)] + int(current_UIK.iloc[2,k-1]) + int(current_UIK.iloc[3,k-1])

for i in range (18):
    appearence_dist[i] = round(num_of_voters_voted_dist[i]/num_of_voters_dist[i]*100, 2)

### GeoDataFrame 

SPb_shapes= gpd.read_file('./shapes/Administrative_Discrits.shp', encoding='cp1251')

temp = pd.DataFrame({'Количество избирателей': num_of_voters_dist, 'Явка':appearence_dist })
temp['Район'] = SPb_shapes[['Район']]
temp['geometry'] = SPb_shapes[['geometry']]
SPB_elections_visualization = gpd.GeoDataFrame(temp)

### Colored districts

SPB_elections_visualization.plot(column = 'Район', linewidth=0, cmap='plasma', legend=True, figsize=[15,15])



Окрасили административные районы города и подписали их, выглядит привычно, похоже на Питер, но Невы все-таки не хватает.

Количество избирателей

### Number of voters gradient

SPB_elections_visualization.plot(column = 'Количество избирателей', linewidth=0, cmap='plasma', legend=True, figsize=[15,15])



Явка

### Appearence gradient

SPB_elections_visualization.plot(column = 'Явка', linewidth=0, cmap='plasma', legend=True, figsize=[15,15])



Заключение


Можно долго развлекаться с данными, использовать их в разных сферах и, конечно же, получать определенную пользу, для этого они и существуют. С помощью простых и сложных инструментов визуализации геоданных можно делать прекрасные вещи. О своих успехах пишите в коменты!

Комментарии (25)


  1. Undiabler
    11.11.2019 00:49
    +2

    Так же вы можете построить нормальные распределения относительно УИКов, и отфильтровать все участки, данные в которых находятся все доверительного интервала. Исходя из правила трех сигм можно посмотреть на разницу в голосах кандидатов, и даже оценить насколько сильно меняется результат голосования при добавлении подозрительных участков.
    Если поделитесь данными могу помочь посчитать.


    1. uncontrollable Автор
      11.11.2019 02:52

      Спасибо за идею, я напишу вам :)


  1. Gerrero
    11.11.2019 03:55

    Классная статья! Вопрос к питонистам. Недавно я (для себя) закончил онлайн курс по python. Пока явных задач нет (не придумал). Что бы не растерять навык и прокачать скилл, может посоветуете какой-нибудь ресурс где есть всякого рода задачки? Возможно глупо, но не хочется забыть язык и заново вспоминать что и как.


    1. Tanner
      11.11.2019 05:35

      Советую Codewars.


    1. peacekeeper
      11.11.2019 07:42
      +1

      Ничего глупого в этом нет. Codewars, codility — это если навскиду.
      А так разных ресурсов много, есть статьи со списками и описаниями.


    1. A114n
      11.11.2019 11:11

      Скажите, а как вы собираетесь применять эти знания?
      Я вот так и не смог понять, каким образом происходит переход от курсов (и даже, например, «сохранения навыков благодаря решению задач») к непосредственному трудоустройству.


      1. Newcss
        11.11.2019 14:20

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


        1. A114n
          11.11.2019 14:28

          Ну а дальше — либо повезет устроиться куда-нибудь, либо — получится найти магистра и стать его падаваном…

          Как-то странно, не находите? Сначала всё по схеме: «Двигайся от А через B прямо к С», а потом раз и «ну а чтобы пройти вот здесь — должно повезти».


          1. Newcss
            11.11.2019 17:11

            Каждый человек может стать чемпионом, но не каждый им станет, кому-то не хватит терпения и сил идти к цели, а кому-то везения… Каждый ищет свою нишу. Павел Дуров в 200х запустил ВК, до этого он мало кому был известен… Везение? Умения? Или звезды сошлись?)


    1. megazloj
      11.11.2019 14:28

      Еще можно посоветовать codeabbey


  1. tcapb1
    11.11.2019 04:08
    +2

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

    Вот например сравнение данных от наблюдателей (СМС-цик) и того, что было вывешено наутро в ГАС Выборы
    1. Shtucer
      11.11.2019 06:07

      Так то Боливия. У них климат.


    1. mSnus
      11.11.2019 10:29
      +2

      "вы что, хотите как в Боливии?!"


    1. DimanDimanyich
      11.11.2019 11:53

      В Беларуси еще проще.
      Выборы президента 2015 — 36% избирателей уже проголосовали — досрочно! Если точнее, 36,05%. Бюллетени лежат без присмотра ночами, камер нету.

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


    1. kochetkov_ii
      11.11.2019 18:26

      Поясните, пожалуйста, обозначения на картинке.


  1. resetme
    11.11.2019 06:53

    Какие-то фантастические агитаторы поработали? Или просто подвезли 10 автобусов людей и заставили голосовать?

    Хотелось бы увидеть в статье не догадки, а факты.


    1. uncontrollable Автор
      11.11.2019 08:35

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


  1. 2PAE
    11.11.2019 07:39
    +1

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

    1) устанавливаться кол-во избирателей
    2) устанавливатся процент первого кандидата, процент второго кандидата, процент третьего
    3) разные дополнительные факторы по вкусу, предназначенные зашумить конечные цифры.

    И на основании нормального распределения выдат красивую кривую распределения по количеству голосов. Которую будут опубликованна в качестве официальных документах от избиркома?

    Существуют же исследования показывающие что официальные данные не соответствуют математическим выкладкам. Что мешает взять те же формулы и посчитать в обратную сторону?

    Подозреваю, наплевательство на мнение избирателей.


    1. A114n
      11.11.2019 11:11

      наплевательство на мнение избирателей.

      В основном наплевательство со стороны самих избирателей.


    1. Matshishkapeu
      11.11.2019 11:43

      1)…
      2)…
      3)…
      4)обосновываться нормальность распределения величин, не являющихся ни случайными, ни независимыми. Про четвертый пункт можно написать неплохой диссер.


      1. 2PAE
        11.11.2019 11:50

        Хм, думаете никто ещё не сподобился? Предполагаю, что подобные работы УЖЕ есть, может только не по теме выборов. Но так ли это важно?

        Ещё раз,
        1) берем работы указывающие что результаты выборов сфальсифицированы,
        2) изучаем математическое доказательство фальсификации,
        3) строим обратную систему,
        4) Profit!


        1. Matshishkapeu
          12.11.2019 01:55

          >> Ещё раз,
          1) берем работы указывающие что результаты выборов сфальсифицированы,
          2) изучаем математическое доказательство фальсификации,
          3) строим обратную систему,
          4) Profit!

          Псевдонаука как она есть. Берем феноменологическое описание одного явления. Без доказательства общности переносим модель на другое явление — получаем говно на палочке. Для примера — берем распространение акустических волн, натягивем сову на глобус — получаем теорию эфира в распространении света. Точно также перенос моделей с разных выборов на разных популяциях, с разными кандидатами и разным населением (и разными методами фальсификаций если они там были) — это натягивание совы на глобус. Обычно по среднему УИКу в России если досрочно проголосовало больше 2 процентов — это ахтунг. Но если вдруг выборы попадают под сезон отпусков — это может быть вполне натуральное явление. Аналогично с кучей других классических индикаторов: с открепительными, с выездными, с корреляцией явки и распределения голосов (бабушки любят КПРФ и ходят на выборы свято, а мелиораторы любят партию аграриев, но на выбрры ходят лениво, получаем — чем больше бабушек на участке тем выше явка и процент КПРФ). Если это не те же самые люди в пределах одного-двух избирательных циклов, пока они не успели перераспределиться по кандидатам или перейти между активными/неактивными. Тащемта почти вся выборная аналитика которая мне попадалась сделана людьми, не заморачивающимися такими мелочами как «независимые случайные величины».


  1. prostofilya
    11.11.2019 07:51
    +2

    А сегодня на уроке мы будем измерять длину и вес черенка, пронизывающего…


  1. Kane
    11.11.2019 12:57

    но есть два участка с явкой >1200 чел и процентом 90+-0.2.

    Один из них располагался в 107 гимназии. Там желающих проголосовать подвозили на автобусах. В итоге несколько тысяч голосов за беглова.


  1. Squoworode
    11.11.2019 12:59

    Питер похож на птицу, съедающую самолёт.