Всем привет! Недавно я наткнулся на сайт vote.duma.gov.ru, на котором представлены результаты голосований Госдумы РФ за весь период её работы — с 1994-го года по сегодняшний день. Мне показалось интересным применить некоторые техники машинного обучения, а так же обычной статистической обработки для выяснения следующих вопросов. Предполагаю, что людям, не знакомым с методами машинного обучения, может быть интересно посмотреть раздел 1, а также заглянуть в выводы — там дана краткая интерпретация всех результатов. Итак, поехали. Пара слов про датасет и принятые допущения. У вышеуказанного сайта есть API, через который можно скачать все интересующие данные и многое другое, однако он слегка замороченный и не всегда работает, поэтому я решил, что проще будет спарсить данные с веб-версии. Код парсера (и весь остальной код, написанный на Python, а также датасеты) можно посмотреть на GitHub. Для своих экспериментов я скачал результаты всех голосований Госдумы 7-го созыва (2016 — 2021 гг.). Были получены данные по 16249 голосованиям (дата сбора информации — 07.06.2021).  Фрагмент полученного датасета выглядит примерно так: В качестве индекса используется соответствующий номер голосования на сайте vote.duma.gov.ru, далее идёт текст вопроса голосования, результат голосования и пофамильные результаты всех депутатов, входящих в текущий созыв на момент голосования. Так как список депутатов, входящих в данный созыв, со временем слегка менялся, было принято решение убрать из датасета тех, кто отсутствовал в составе думы в течение 10 и более процентов всех голосований. Оставшие пропуски были заполнены значением "2", что соответствует тому, что депутат на момент голосования был в составе думы, но по тем или иным причинам не голосовал.  Остальные значения в таблице голосов: "1" — "против", "0" — "воздержался", "-1" — "за". Чтобы при расчёте корреляций нивелировать влияние количества пропусков голосований у каждого депутата (иначе главные прогульщики будут иметь наименьший коэффициент корреляции с остальными, т.е. выглядеть более независимо), я решил для каждого голосования заполнить голоса отсутствующих депутатов результирующим голосом их партии. Т.е. условно принимается, что если депутат отсутствует, то он доверяет свой голос партии. Справедливая Россия  Депутаты, у которых преобладают тёмные клетки, голосуют более независимо, чем остальные. Отдельные светлые клеточки показывают пары депутатов, которые часто голосуют одинаково. Посмотрим на остальные партии. ЛДПР  (Все значения округляются до двух знаков после запятой) КПРФ  Единая Россия
(чтобы рассмотреть хоть что-то, лучше открыть картинку в отдельной вкладке)  Как можно видеть, во всех случаях коэффициент корреляции составил более 0,91 (в подавляющем большинстве — более 0,97).  Теперь посмотрим, существуют ли такие депутаты, которые весь созыв голосовали точно так же, как и их партийное большинство (в данном случае учитывались только голоса "за" и "против"). Справедливая Россия ЛДПР КПРФ Единая Россия "Идентичные" депутаты обнаружились в двух из четырех партий, в одной из которых их оказалось целых 59 человек (19%). Далее отображаем нашу матрицу в двухмерное пространство с помощью PCA, UMAP и t-SNE и смотрим, что получилось. PCA  Видно, что PCA справился плоховато, смешав СР и ЛДПР в одну кучу. t-SNE  UMAP  А вот t-SNE/UMAP сработали отлично, чётко разделив все 4 партии. Глядя на картинки, можно предположить, что сама кластеризация сработает так же успешно, однако, проверим. Алгоритм k-means Определим оптимальное количество кластеров методом силуэтного графика:  По графику видно, что максимальное число кластеров, на которое хорошо делятся данные, равно 4.   Как можно видеть, K-means с настройками по-умолчанию отлично справился с задачей кластеризации. Проверял также иерархический алгоритм (AgglomerativeClustering) — не буду приводить код и картинки, т.к. результат аналогичный. Ниже используется кусок кода для ускоренной лемматизации, подсмотренный в этой статье. Спасибо юзерам Codex1 и serhit. Т.к. в датасете присутствует некоторый дисбаланс классов (результат около 80% голосований — "принят"), в качестве простейшего бейзлайна был взят предиктор, выдающий всегда значение "принят". Судя по метрикам, все алгоритмы классификации справились хорошо. Исключением стал kNN, который тяжело переваривает с датасеты с таким огромным количеством признаков (напомню, в данном случае — 16 тыс.) Думаю, потратив время на хорошую кросс-валидацию, можно было бы вытянуть ещё пару процентов, но так как данная работа носила скорее шуточный характер, а времени было жалко, я остановился на этом результате. Под спойлером также можно посмотреть ROC-кривые, а также степень влияния определённых слов на результат предсказания для модели логистической регрессии.    Основное влияние оказывают технические термины. Например, если рассматривается второе или третье чтение законопроекта, вероятность его принятия повышается, а если первое, либо вопрос вынесен на голосование каким-либо депутатом — понижается. Из графиков видно, что также имеют значение слова, отражающие тематику рассматриваемых законопроектов. Пройдусь вкратце по вопросам, поставленным в начале работы. Благодарю Дмитрия Сергеева и Дмитрия Головина за помощь в подготовке публикации. Также приглашаю всех желающих записаться на Demo Day курса "Промышленный ML на больших данных". В рамках вебинара вы сможете познакомиться с экспертами OTUS, подробно узнать о курсе, а также о процессе обучения. Всем спасибо за внимание! Фрилансер