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

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.
Привет!

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



Заключение


Можно долго развлекаться с данными, использовать их в разных сферах и, конечно же, получать определенную пользу, для этого они и существуют. С помощью простых и сложных инструментов визуализации геоданных можно делать прекрасные вещи. О своих успехах пишите в коменты!
Источник: https://habr.com/ru/post/475258/


Интересные статьи

Интересные статьи

Всем привет. Когда я искал информацию о журналировании (аудите событий) в Bitrix, на Хабре не было ни чего, в остальном рунете кое что было, но кто же там найдёт? Для пополнения базы знаний...
Привет, друзья! Меня зовут Петр, я представитель малого белорусского бизнеса со штатом чуть более 20 сотрудников. В данной статье хочу поделиться негативным опытом покупки 1С-Битрикс. ...
У нас был долгий ненастный питерский вечер, конференц-зал Selectel на 70 мест, два десятка техдиров, более пятидесяти it-разработчиков разной степени небритости, несколько HR, мощнейший Дмитрий...
Многие бегуны с утра измеряют пульс покоя. Если с утра он повышен, значит ты дал лиху — перетренировался. Я не бегун, но тоже измеряю. На апсторе есть удобная программа для измерения пульса по ли...
Конференция C++ Russia — пока что самая суровая и одновременно ламповая конференция за последнее время. Она проходила в Конгресс-парке гостиницы «Украина» в Москве и собрала аудиторию более чем в...