Визуальное представление выборов в Санкт-Петербурге — магия накрутки голосов

Привет!

В сентябре этого (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])



Заключение


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

Подробнее
Реклама

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

    +5
    Так же вы можете построить нормальные распределения относительно УИКов, и отфильтровать все участки, данные в которых находятся все доверительного интервала. Исходя из правила трех сигм можно посмотреть на разницу в голосах кандидатов, и даже оценить насколько сильно меняется результат голосования при добавлении подозрительных участков.
    Если поделитесь данными могу помочь посчитать.
      +1
      Спасибо за идею, я напишу вам :)
      0
      Классная статья! Вопрос к питонистам. Недавно я (для себя) закончил онлайн курс по python. Пока явных задач нет (не придумал). Что бы не растерять навык и прокачать скилл, может посоветуете какой-нибудь ресурс где есть всякого рода задачки? Возможно глупо, но не хочется забыть язык и заново вспоминать что и как.
        +1
        Советую Codewars.
          +1
          Ничего глупого в этом нет. Codewars, codility — это если навскиду.
          А так разных ресурсов много, есть статьи со списками и описаниями.
            0
            Скажите, а как вы собираетесь применять эти знания?
            Я вот так и не смог понять, каким образом происходит переход от курсов (и даже, например, «сохранения навыков благодаря решению задач») к непосредственному трудоустройству.
              0
              Присматриваете область интересную Вам и смотрите какие там примерно решаются задачи. На начальных курсах дают минимальные основы — работа со строками, массивами, работа с файлами, циклы. В принципе любую проблему можно решить используя минимальный набор операций… Ну а дальше — либо повезет устроиться куда-нибудь, либо — получится найти магистра и стать его падаваном…
                0
                Ну а дальше — либо повезет устроиться куда-нибудь, либо — получится найти магистра и стать его падаваном…

                Как-то странно, не находите? Сначала всё по схеме: «Двигайся от А через B прямо к С», а потом раз и «ну а чтобы пройти вот здесь — должно повезти».
                  0
                  Каждый человек может стать чемпионом, но не каждый им станет, кому-то не хватит терпения и сил идти к цели, а кому-то везения… Каждый ищет свою нишу. Павел Дуров в 200х запустил ВК, до этого он мало кому был известен… Везение? Умения? Или звезды сошлись?)
              0
              Еще можно посоветовать codeabbey
              +12
              В 2012 году, когда я работал наблюдателем на участке на президентских выборах, никаких автобусов не понадобилось, всё происходило гораздо проще. Первично считали правильно, а затем, когда наблюдатели с копией протокола уходили домой, производился повторный пересчёт, и на утро в ГАС Выборы красовались уже совсем другие цифры.

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


              Питерцы съели результаты тех выборов, съели и результаты выборов 2019 года, где на муниципальном уровне вообще творился ад. Впрочем, вопрос прошёл бы Беглов честно во второй тур или нет остаётся открытым, и ваши данные этого не проясняют. Хотя какое честно, всех сильных конкурентов Беглова сняли ещё до выборов, и соревновался он всего с двумя относительными ноунеймами.

              Тем временем сегодняшние вести из Боливии, где попытались накрутить голоса, что привело к массовым протестам. Свежевыбранный Президент Боливии Эво Моралес сперва объявил о повторных выборах, а затем подал в отставку и попытался бежать из страны. Задержали председателя избиркома города Санта-Крус (это крупнейший город в Боливии). Полиция не застала дома боливийскую «Эллу Памфилову» — главу боливийского избиркома — она скрылась и в прощальном письме сообщила, что подала в отставку, а преступлений против воли боливийского народа не совершала.
                +12

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

                  +8

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

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

                    На этой неделе выборы в парламент. В четверг родительское собрание и классная попросила зайти и проголосовать досрочно. Но я не с этого участка, так что мимо.
                      0
                      Поясните, пожалуйста, обозначения на картинке.
                      0
                      Какие-то фантастические агитаторы поработали? Или просто подвезли 10 автобусов людей и заставили голосовать?

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

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

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

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

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

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

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

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

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

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

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

                                0

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

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

                                Самое читаемое