Хабрахабр

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

Привет!

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

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

%%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

2. Построения вполне сносные, но в ходе работы выяснилось, что в среднем на участке 400 человек и процент за Беглова от 50 до 70, но есть два участка с явкой >1200 чел и процентом 90+-0. Какие-то фантастические агитаторы поработали? Интересно, что такое произошло на этих участках. Так или иначе, мы взволнованы, небольшое такое расследование получается. Или просто подвезли 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()
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])

Заключение

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

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть