![](https://habrastorage.org/getpro/habr/upload_files/a29/c00/dd6/a29c00dd62b611e596975bc18c29540b.gif)
Результаты выборов в государственную думу, которые проходили 17-19 сентября 2021 вызывают сомнения у многих экспертов. Независимый электоральный аналитик Сергей Шпилькин оценил количество голосов, вброшенных за партию власти, примерно в 14 миллионов. В данной работе применены методы машинного обучения для того, чтобы выявить избирательные участки, на которых подсчет голосов происходил без нарушений и установить истинный результат на тех участках, где , предположительно, были зарегистрированы ошибочные данные.
Результаты выборов можно найти на сайте ЦИК. Кроме того, результаты были выгружены с сайта и помещены в телеграмм канал RuElectionData. В рамках данной работы исследуются результаты выборов для партий «Единая Россия» и «КПРФ», которые по результатам, опубликованным ЦИК, получили 49,82 и 18,93 процента голосов избирателей. В данном исследовании в качестве источника результатов используется часть данных, которые были сохранены в файл ‘edata.csv’. Этот файл можно скачать совместно с исходным кодом с GitHub.
Для начала загрузим данные и проверим их полноту:
#%% Загружаем данные import pandas as pd uiks = pd.read_csv('data/edata.csv', index_col=0)
name |
region |
kprf |
er |
voted |
total_voters |
lat |
lon |
|
0 |
УИК №592 |
Алтайский край |
57 |
49 |
178 |
385 |
51.885025 |
85.307478 |
1 |
УИК №593 |
Алтайский край |
189 |
174 |
569 |
1515 |
51.934707 |
85.326494 |
2 |
УИК №594 |
Алтайский край |
157 |
141 |
464 |
1175 |
51.930130 |
85.333621 |
3 |
УИК №595 |
Алтайский край |
303 |
339 |
962 |
2257 |
51.943233 |
85.336853 |
4 |
УИК №596 |
Алтайский край |
264 |
282 |
843 |
1924 |
51.961639 |
85.335227 |
… |
… |
… |
… |
… |
… |
… |
… |
… |
Подсчитаем итоговый результат выборов для партии КПРФ и Единая Россия:
#%% Итоговый результат КПРФ kprf = uiks['kprf'].sum()/uiks['voted'].sum() 0.18925488494610923 #%% Итоговый результат Единой России er = uiks['er'].sum()/uiks['voted'].sum() 0.4982132868119814
Итоговый результат совпадает с результатом на сайте ЦИК, будем считать данные полными.
Как видно из результатов ЦИК, Единая Россия опередила КПРФ более чем в два раза. Однако есть регионы, где КПРФ одержала победу. Для каждого из участков добавим параметр ‘k-e’ , который равен разнице результата Единой России и КПРФ в регионе, в котором находится участок. Кроме того, создадим таблицу с регионами, где победу одержала КПРФ:
uiks['k-e'] = 0.0 regions = uiks['region'].drop_duplicates() reg = pd.DataFrame() for region in regions: region_data = uiks[uiks['region'] == region] voted = region_data['voted'].sum() kprf_total = region_data['kprf'].sum() kprf_percent = kprf_total/voted er_total = region_data['er'].sum() er_percent = er_total/voted uiks.loc[uiks['region'] == region, 'k-e'] = kprf_percent-er_percent if er_total>kprf_total: uiks.loc[uiks['region'] == region, 'color'] = 'blue' else: uiks.loc[uiks['region'] == region, 'color'] = 'red' reg = reg.append(pd.DataFrame({'name': region,'kprf':[kprf_total], 'kprf_percent':[kprf_percent],'er':[er_total],'er_percent':[er_percent]}), ignore_index=True) reg[reg['kprf']>reg['er']]
name |
kprf |
kprf_percent |
er |
er_percent |
|
1 |
Ненецкий автономный округ |
4917 |
0.319763 |
4469 |
0.290629 |
2 |
Республика Марий Эл |
89018 |
0.362999 |
81969 |
0.334255 |
3 |
Республика Саха (Якутия) |
118683 |
0.351483 |
112160 |
0.332165 |
4 |
Хабаровский край |
113691 |
0.265075 |
105112 |
0.245072 |
Нанесем участки на карту России с помощью библиотеки plotly.express.
import plotly.express as px fig = px.scatter_mapbox(uiks, #our data set lat="lat", lon="lon", color="k-e", range_color = (-0.5,0.5), zoom=2, width=1200, height=800, center = {'lat':60,'lon':105}, title = 'По данным ЦИК') fig.update_layout(mapbox_style="open-street-map") fig.update_traces(marker=dict(size=5)) fig.show(config={'scrollZoom': True})
![](https://habrastorage.org/getpro/habr/upload_files/464/9d8/c0a/4649d8c0a18c7a8774565ebb8448692c.png)
На этой карте участки окрашены в различные цвета, в соответствии с разницей результата КПРФ и Единой России по региону, в котором находится участок(параметр ‘k-e’). Цвет может меняться от темно синего (Результат Единой России на 50% и более выше, чем у КПРФ) до желтого(результат КПРФ на 50% выше, чем у Единой России). На карте преобладают холодные тона. Преимущество Единой России очевидно.
Построим теперь график зависимости результатов Единой России и КПРФ от явки c помощью matplotlib:
import matplotlib.pyplot as plt uiks = uiks[uiks['kprf']>10] uiks = uiks[uiks['er']>10] uiks['er_percent'] = uiks['er'] / (uiks['voted']) uiks['kprf_percent'] = uiks['kprf'] / (uiks['voted']) uiks['turnout'] = uiks['voted']/uiks['total_voters'] plt.scatter(uiks['turnout'], uiks['er_percent'], color='blue', s=0.01) plt.scatter(uiks['turnout'], uiks['kprf_percent'], color='red', s=0.01) plt.show()
![](https://habrastorage.org/getpro/habr/upload_files/476/9de/d1c/4769ded1cdec940625199c9aa30041e6.png)
На графике можно выделить две характерные зоны. Плотное ядро в районе явок 0.2-0.6 и расходящиеся «хвосты» в районе явок свыше 0.6. В своих работах, независимые электоральные аналитики показывают, что подобная картина может наблюдаться при вбросе голосов за партию, результат которой растет с явкой. Причем в ядре находятся участки с «нормальной явкой», на которых не было фальсификаций, а хвосты соответствуют участкам с «аномальной явкой», где результаты выборов недостоверны.
Отделим участки с нормальным голосованием от участков с аномальным голосованием.
Чтобы выделить участки в ядре используем алгоритм DBSCAN(Density Based Scan) из библиотеки scikit-learn. Этот алгоритм выделяет кластеры, в которых для каждой точки в радиусе “eps” имеется количество точек равное “min_samples”. Хороший результат дает eps = 0.009 и min_samples = 175:
#%% Выделение кластера участков с нормальной явкой from sklearn.cluster import DBSCAN er = uiks[['turnout', 'er_percent']] er = er.to_numpy() db = DBSCAN(eps=0.009, min_samples=175).fit(er) plt.scatter(er[:, 0], er[:, 1], c=db.labels_, s=0.01) plt.show() uiks['db'] = db.labels_ uiks_normal = uiks[uiks['db'] == 0] uiks_abnormal = uiks[uiks['db'] != 0]
![](https://habrastorage.org/getpro/habr/upload_files/c7b/4d1/b0f/c7b4d1b0fc128e6be747fe6774801d4c.png)
Далее будем использовать участки из ядра для того, чтобы обучить модель. В качестве алгоритма будем использовать алгоритм k ближайших соседей. В sklearn он реализован в виде класса KNeighborsRegressor. Кроме того, мы создадим объект класса Pipeline, чтобы автоматически нормализовать данные с помощью StandardScaler.
#%% Создаем pipeline для машинного обучения from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler from sklearn.neighbors import KNeighborsRegressor pipe = Pipeline([("scale", StandardScaler()), ("model", KNeighborsRegressor())]) pipe.get_params()
Для обучения модели мы разделим участки в ядре на три части(cv = 3) и проведем оптимизацию результатов по количеству ближайших соседей (‘model__n_neighbors’):
#%% Задаем параметры кросс валидации from sklearn.model_selection import GridSearchCV mod = GridSearchCV(estimator=pipe, param_grid={'model__n_neighbors': [45, 50, 55,60,65,70,75,80,85,90]}, cv=3)
Мы исходим из предположения, что на участках с аномальной явкой недостоверно регистрировался результат партии «Единая Россия» и соответственно явка. А такие параметры, как количество проголосовавших за партию «КПРФ», общее количество человек, которые могли принять участие в голосовании и координаты участка зарегистрированы верно. Именно эти переменные будем использовать для обучения модели:
#%% Обучаем модель X = uiks_normal[['kprf', 'total_voters', 'lat', 'lon']] y = uiks_normal['er'] Xx = uiks_abnormal[['kprf', 'total_voters', 'lat', 'lon']] mod.fit(X, y)
Полученную модель используем для того, чтобы рассчитать результат партии «Единая Россия» на участках с аномальной явкой:
#%% Рассчитываем результат Единой России используя модель prediction = mod.predict(Xx) uiks_abnormal['prediction'] = prediction uiks_abnormal['er_predicted'] = prediction.round()
Так как мы предполагаем, что результат Единой России не мог быть скорректирован в меньшую сторону в момент фальсификаций, на участках, где расчетные значения выше официальных результатов, оставим официальные результаты.
#%% Корректируем результаты, так как предполагаем, что за Единую Россию не было вбросов for index, row in uiks_abnormal.iterrows(): if row['er'] < row['prediction']: uiks_abnormal.loc[index, 'er_predicted'] = row['er'] uiks_normal['er_predicted'] = uiks_normal['er']
Теперь, когда у нас есть расчетные результаты партии Единая Россия на аномальных участках, можно пересчитать явку и другие параметры. Кроме того, создадим объект uiks_predicted, который будет содержать результаты выборов на участках с нормальным и аномальным голосованием:
#%% Вычисляем явку по результатам машинного обучения uiks_abnormal['voted_predicted'] = uiks_abnormal['voted'] - uiks_abnormal['er'] + uiks_abnormal['er_predicted'] uiks_normal['voted_predicted'] = uiks_normal['voted'] uiks_abnormal['turnout_predicted'] = uiks_abnormal['voted_predicted'] / uiks_abnormal['total_voters'] uiks_normal['turnout_predicted'] = uiks_normal['turnout'] uiks_abnormal['er_percent_predicted'] = uiks_abnormal['er_predicted'] / uiks_abnormal['voted_predicted'] uiks_normal['er_percent_predicted'] = uiks_normal['er_percent'] uiks_abnormal['kprf_percent_predicted'] = uiks_abnormal['kprf'] / uiks_abnormal['voted_predicted'] uiks_normal['kprf_percent_predicted'] = uiks_normal['kprf_percent'] uiks_predicted = uiks_normal.append(uiks_abnormal)
Используем данные из uiks_predicted для построения графика зависимости результатов на участках от явки.
#%% Строим график зависимости результатов на участках от явки по результатам машинного обучения plt.scatter(uiks_predicted['turnout_predicted'], uiks_predicted['er_percent_predicted'], color='blue', s=0.01) plt.scatter(uiks_predicted['turnout_predicted'], uiks_predicted['kprf_percent_predicted'], color='red', s=0.01) plt.show()
![](https://habrastorage.org/getpro/habr/upload_files/cd5/002/298/cd500229813df5c216916462a501aa8f.png)
После применения машинного обучения картина больше похожа на ту, что наблюдалась в регионах севера России, где выборы традиционно проходят на высоком уровне. «Расходящиеся хвосты» в области большой явки больше не наблюдаются. Подсчитаем итоговый результат для партии Единая Россия и КПРФ по результатам полученных данных. Кроме того, установим количество вброшенных голосов:
#%% Считаем итоговый результат после применения машинного обучения er_real = uiks_predicted['er_predicted'].sum() //12155992.0 kprf_real = uiks_predicted['kprf'].sum() //10610737 voted_real = uiks_predicted['voted_predicted'].sum() 40145581.0 er_real_percent = er_real / voted_real //0.30279775998259933 kprf_real_percent = kprf_real / voted_real //0.26430647497666054 fake_votes = uiks_predicted['er'].sum() - uiks_predicted['er_predicted'].sum() //14595980.0
Таким образом, после восстановления результатов с помощью модели машинного обучения Единая Россия набирает около 30 процентов при средней явке 40 процентов. Разница количества голосов за Единую Россию в исходных данных и данных, полученных в результате моделирования составляет 14595980. КПРФ набирает 26 процентов. Посмотрим, изменился ли состав регионов, в которых лидирует КПРФ:
#%% Таблица регионов, где победила КПРФ после применения машинного обучения reg_true = pd.DataFrame() for region in regions: region_data = uiks_predicted[uiks_predicted['region'] == region] kprf_total = region_data['kprf'].sum() er_total = region_data['er_predicted'].sum() voted = region_data['voted'].sum() kprf_percent = kprf_total/voted er_percent = er_total/voted uiks_predicted.loc[uiks['region'] == region, 'k-e'] = kprf_percent-er_percent if er_total>kprf_total: uiks_predicted.loc[uiks_predicted['region'] == region, 'color'] = 'blue' else: uiks_predicted.loc[uiks_predicted['region'] == region, 'color'] = 'red' reg_true = reg_true.append(pd.DataFrame({'name': region,'kprf':[kprf_total],'er':[er_total]}), ignore_index=True) reg_true[reg_true['kprf']>reg_true['er']]
name |
kprf |
er |
|
1 |
Алтайский край |
224806 |
205960 |
2 |
Ивановская область |
84969 |
84383 |
3 |
Кабардино-Балкарская Республика |
77074 |
72990 |
4 |
Костромская область |
57588 |
56026 |
5 |
Ненецкий автономный округ |
4863 |
3883 |
6 |
Омская область |
190454 |
162625 |
7 |
Приморский край |
173429 |
142138 |
8 |
Республика Алтай |
22244 |
20992 |
9 |
Республика Калмыкия |
25485 |
24826 |
10 |
Республика Марий Эл |
89013 |
69266 |
11 |
Республика Саха (Якутия) |
118362 |
87085 |
12 |
Ростовская область |
333737 |
333235 |
13 |
Сахалинская область |
43471 |
38457 |
14 |
Ульяновская область |
147069 |
120774 |
15 |
Хабаровский край |
113312 |
85935 |
16 |
Ярославская область |
98695 |
90208 |
17 |
город Москва |
871223 |
529986 |
Количество регионов, где КПРФ одержала победу над Единой Россией, увеличилось с четырех до семнадцати. Проверим, как изменилась раскраска регионов на карте России:
#%%Карта России с разноцветными участками по результатам машинного обучения fig = px.scatter_mapbox(uiks_predicted, #our data set lat="lat", lon="lon", color='k-e', range_color = (-0.5,0.5), zoom=2, width=1200, height=800, center = {'lat':60,'lon':105}, title = 'После машинного обучения') fig.update_layout(mapbox_style="open-street-map") fig.update_traces(marker=dict(size=5)) fig.show(config={'scrollZoom': True})
![](https://habrastorage.org/getpro/habr/upload_files/8cb/2c2/c1a/8cb2c2c1a6a3f14a38b43a9c149468bd.png)
Карта окрасилась в более теплые тона. Во всех регионах результат у партий КПРФ и Единая Россия очень близкий.
В результате моделирования результатов выборов на участках с аномальной явкой можно сделать следующие выводы:
-
Разница количества голосов за партию Единая Россия при подсчетах ЦИК и с использованием модели машинного обучения составила более 14 миллионов.
-
Результат партии Единая Россия составил около 30%
-
Результат партии КПРФ составил 26 %
-
Средняя явка составила около 40%
-
Количество регионов России, в которых результат КПРФ превзошел результат Единой России, увеличилось с четырех до семнадцати.
P.S. К статье уже достаточно много комментариев. Часть из них критические. И это хорошо, когда комментаторы подробно описывают недостатки статьи. В работе используется простая модель с малым количеством признаков. Ее можно улучшить. Пожалуйста, напишите, как бы вы улучшили исследование, если у вас есть идеи.
Многие исходные предположения в работе базируются на исследованиях Сергея Шпилькина. Из некоторых комментариев понятно, что в статье не хватает контекста в этом плане. Я рекомендую посмотреть это видео всем, кто его еще не видел:
ссылка на оригинал статьи https://habr.com/ru/articles/588989/
Добавить комментарий