Всем привет!


Сегодня мы поговорим о визуализации геоданных. Имея на руках статистику, явно имеющую пространственную привязку, всегда хочется сделать красивую карту. Желательно, с навигацией да инфоокнами В тетрадках. И, конечно же, чтоб потом можно было показать всему интернету свои успехи в визуализации!


В качестве примера возьмем недавно отгремевшие муниципальные выборы в Москве. Сами данные можно взять с сайта мосгоризбиркома, в можно просто забрать датасеты с https://gudkov.ru/. Там даже есть какая-никакая визуализация, но мы пойдем глубже. Итак, что же у нас в итоге должно получиться?


Потратив некоторое время на написание парсера сайта Избиркома, я получил нужные мне данные. Итак, начнем с импортов.


импорты
import pandas as pd
import numpy as np
import os
import pickle

Я работаю в jupyter notebook на Linux-машине. Если вы захотите использовать мой код на Windows машине, то обращайте внимание на написание путей, а также на важные отступления в тексте.


Обычно я использую отдельную папку для проекта, поэтому для простоты задаю текущую директорию:


os.chdir('/data01/jupyter/notebooks/habr/ods_votes/')

Дальше нам трубуется забрать данные с самого сайта Избиркома. Для разбора данных я написал отдельный парсер. Весь процесс занимает 10-15 минут. Забрать его можно из репозитория.


Я решил создать большой словарь с датафреймами внутри. Для превращения html-страниц в датафреймы я использовал read_html, эмпирически подбирал нужные датафрейс, а после этого делал небольшую обработку, выкидывая лишнее и добавляя недостающее. Предварительно я уже обработал данные по партиям. Изначально они были не особо читаемы. К тому же, встречается разное написание одних и тех же партий (забавно, но в некоторых случаях это не разное написание, а реально разные партии).


Разбор данных избиркома


Непосредственно сборка справочника. Что здесь происходит:


  • собираются структура административных и муниципальных округов;
  • собираются все ссылки на Территориальные Избирательные Комиссии (ТИК);
  • для каждого ТИК собирается список кандидатов;
  • внутри каждого ТИК собираются Окружные Избирательные Комиссии (ОИК);
  • для каждого ОИК собирается статистика по ОИК и статистика по кандидатам;
  • из полученного датасета собираем статистику по муниципальным округам.

В репозитории этой статьи лежат уже готовые данные. Их мы и будем использовать.


Разбор данных
import glob

# забираем справочник сокращений для партий
with open('tmp/party_aliases.pkl', 'rb') as f:
    party_aliases = pickle.load(f)

votes = {}
# забираем список округов и мунициальных образований
votes['atd'] = pd.read_csv('tmp/atd.csv', index_col=0, sep=';')
votes['data'] = {}
# идем по мунициальным образованиям и собираем статистику ТИК
for v in votes['atd']['municipal'].values:
    votes['data'][v] = {}
    # забираем статистику по кандидатам
    candidates = glob.glob('tmp/data_{}_candidates.csv'.format(v))[0]
    votes['data'][v]['candidates'] = pd.read_csv(candidates, index_col=0, sep=';')
    votes['data'][v]['votes'] = {}
    # теперь по каждому ОИК собираем его статистику
    # статистика по УИК
    okrug_stats_list = glob.glob('tmp/data_{}*_okrug_stats.csv'.format(v))
    for okrug_stats in okrug_stats_list:
        okrug = int(okrug_stats.split('_')[2])
        try:
            votes['data'][v]['votes'][okrug]
        except:
            votes['data'][v]['votes'][okrug] = {}
        votes['data'][v]['votes'][okrug]['okrug_stats'] = pd.read_csv(okrug_stats, index_col=0, sep=';')
    # статистика по кандидатам
    candidates_stats_list = glob.glob('tmp/data_{}*_candidates_stats.csv'.format(v))
    for candidates_stats in candidates_stats_list:
        okrug = int(candidates_stats.split('_')[2])
        votes['data'][v]['votes'][okrug]['candidates_stats'] = pd.read_csv(candidates_stats, index_col=0, sep=';')

# теперь собираем статистику в удобной нам форме
data = []

# пройдемся по муниципальным округам
for okrug in list(votes['data'].keys()):

    #чистим данные
    candidates = votes['data'][okrug]['candidates'].replace(to_replace={'party':party_aliases})
    group_parties = candidates[['party','elected']].groupby('party').count()

    # создаем общую статистику по избирателям
    stats = np.zeros(shape=(12))
    for oik in votes['data'][okrug]['votes'].keys():
        stat = votes['data'][okrug]['votes'][oik]['okrug_stats'].iloc[:,1]
        stats += stat

    # создаем статистику по партиям
    # количество мест
    sum_parties = group_parties.sum().values[0]

    # количество полученных мест
    data_parties = candidates[['party','elected']].groupby('party').count().reset_index()

    # процент полученных мест
    data_parties['percent'] = data_parties['elected']/sum_parties*100

    # собираем итоговую таблицу по округу
    tops = data_parties.sort_values('elected', ascending=False)
    c = pd.DataFrame({'okrug':okrug}, index=[0])
    c['top1'], c['top1_elected'], c['top1_percent'] = tops.iloc[0,:3]
    c['top2'], c['top2_elected'], c['top2_percent'] = tops.iloc[1,:3]
    c['top3'], c['top3_elected'], c['top3_percent'] = tops.iloc[2,:3]
    c['voters_oa'], c['state_rec'], c['state_given'], c['state_anticip'], c['state_out'], c['state_fired'], c['state_box'], c['state_move'], c['state_error'], c['state_right'], c['state_lost'] , c['state_unacc'] = stats 
    c['voters_percent'] = (c['state_rec'] - c['state_fired'])/c['voters_oa']*100
    c['total'] = sum_parties
    c['full'] = (c['top1_elected']== sum_parties)

    # добавляем полученный датафрейм в список
    data.append(c)

# создаем итоговый датафрейм
winners = pd.concat(data,axis=0)

Мы получили датафрейм со статистикой явки, бюллютеней (от количества выданных до количества испорченных), распределением мест между партиями.
Можно приступать к визуализации!


Базовая работа с геоданными в geopandas


Для работы с геоданными мы будем использовать библиотеку geopandas. Что такое geopandas? Это расширение функциональности pandas географическими абстракциями (унаследованными из Shapely), которые позволяют нам проводит аналитические географические операции с геоданными: выборки, оверлей, аггрегация (как, например, в PostGIS для Postgresql).


Напомню, что существует три базовых типа геометрии — точка, линия (а точнее, полилиния, так как состоит из соединенных отрезков) и полигон. У всех у них бывает вариант мульти-(Multi), где геометрия представляет собой объединение отдельных географических образований в один. Например, выход метро может быть точкой, но несколько выходов, объединенных в сущность "станция", уже являются мультиточкой.


Важное отступление

Следует обратить внимание, что geopandas неохотно ставится через pip в стандартной установке Python в среде Windows. Проблема, как обычно, в зависимостях. Geopandas опирается на абстракции библиотеки fiona, у которой нет официальных сборок под Windows. Идеально использовать среду Linux, например, в docker-контейнере. Кроме того, в Windows можно использовать менеджер conda, он все зависимости подтягивает из своих репозиториев.


C геометрией муниципальных образований все достаточно просто. Их можно легко забрать из OpenStreetMap (подробнее тут) или, например, из выгрузок NextGIS. Я использую уже готовые шейпы.


Итак, начнем! Выполняем нужные импорты, активируем графики matplotlib...


import geopandas as gpd
%matplotlib inline
mo_gdf = gpd.read_file('atd/mo.shp')
mo_gdf.head()


Как видите, это привычный DataFrame. Поле geometry — представление географических объектов (в данном случае — полигонов) в виде WKT, well known text (подробнее — https://en.wikipedia.org/wiki/Well-known_text). Можно довольно просто построить карту наших объектов.


mo_gdf.plot()


Угадывается Москва! Правда, не совсем привычно выглядит. Причина в проекции карты. На Хабре уже есть отличный ликбез по ним.


Итак, представим наши данные в более привычной проекции Web Mercator (исходную проекцию можно легко получить по параметру crs). Окрасим полигоны по названию Административного округа. Ширину линий выставим 0,5. Метод окраски cmap использует стандартные значения matplotlib (если вы, как и я, не помните их наизусть, то вот шпаргалка). Чтобы увидеть легенду карты, задаем параметр legend. Ну а figsize отвечает за размер нашей карты.


mo_gdf_wm = mo_gdf.to_crs({'init' :'epsg:3857'}) #непосредственно преобразование проекции
mo_gdf_wm.plot(column = 'ABBREV_AO', linewidth=0.5, cmap='plasma', legend=True, figsize=[15,15])


Можно построить карту и по типу муниципального образования:


mo_gdf_wm.plot(column = 'TYPE_MO', linewidth=0.5, cmap='plasma', legend=True, figsize=[15,15])


Итак, построим карту статистики по муниципальным округам. Ранее мы уже создали датафрейм winners.
Нам необходимо соединить наш датафрейм с геодатафреймом для создания карты. Немного причешем названия мунициальных округов, чтобы соединение произошло без сюрпризов.


winners['municipal_low'] = winners['okrug'].str.lower()
winners['municipal_low'] = winners['municipal_low'].str.replace('ё', 'е')
mo_gdf_wm['name_low'] = mo_gdf_wm['NAME'].str.lower()
mo_gdf_wm['name_low'] = mo_gdf_wm['name_low'].str.replace('ё', 'е')
full_gdf = winners.merge(mo_gdf_wm[['geometry', 'name_low']], left_on='municipal_low', right_on='name_low', how='left')
full_gdf = gpd.GeoDataFrame(full_gdf)

Построим простую категориальная карту, где показываются партии-победители. В районе Щукино в этом году и правда не было выборов.


full_gdf.plot(column = 'top1', linewidth=0, cmap='GnBu', legend=True, figsize=[15,15])


Явка:


full_gdf.plot(column = 'voters_percent', linewidth=0, cmap='BuPu', legend=True, figsize=[15,15])


Жители:


full_gdf.plot(column = 'voters_oa', linewidth=0, cmap='YlOrRd', legend=True, figsize=[15,15])


Отлично! У нас получилась симпатичная визуализация. Но хочется и базовую карту, и навигацию! На помощь нам придет библиотека cartoframes.


Визуализация геоданных с помощью cartoframes


Одним из самых удобных инструментов для визуализации геоданных является Carto. Для работы с этим сервисом существуюет библиотеке cartoframes, которая позволяет работать с функциями сервиса прямо из тетрадок Jupyter.


Важное отступление

Библиотека cartoframes требует внимательного обращения под Windows в силу особенностей разработки (например, при заливке датасета библиотека пытается использовать стиль папок linux, что приводит к печальным последствиям). С кириллическими данными можно легко отстрелить себе ногу (кодировка cp1251 может быть превращена в кракозябры). Лучше ее использовать или в docker-контейнере, или на полноценном Linux. Ставится библиотека только через pip. В windows ее можно успешно установить, предварительно поставив geopandas через conda (или поставив все зависимости руками).


Cartoframes работает с проекцией WGS84. В нее и перепроецируем наш датасет. После соединения двух датафреймов может теряться информация о проекции. Зададим ее заново и перепроецируем.


full_gdf.crs = ({'init' :'epsg:3857'})
full_gdf = full_gdf.to_crs({'init' :'epsg:4326'})

Делаем нужные импорты...


import cartoframes
import json
import warnings
warnings.filterwarnings("ignore")

Добавляем данные от аккаунта Carto:


USERNAME = 'ваш пользователь Carto'
APIKEY = 'ваш ключ API'

И, наконец, подключаемся к Carto и заливаем наш датасет:


cc = cartoframes.CartoContext(api_key=APIKEY, base_url='https://{}.carto.com/'.format(USERNAME))
cc.write(full_gdf, encode_geom=True, table_name='mo_votes', overwrite=True)

Датасет можно выгрузить с Carto обратно. Но полноценный геодатафрейм пока только в проекте. Правда, можно с помощью gdal и shapely сконвертировать бинарное представление геометрии PostGIS снова в WKT.


Особенностью работы плагина является приведением типов. Увы, в текущей версии датафрейм заливается в таблицу с назначением типа str для каждого столбца. Об этом надо помнить при работе с картами.


Наконец, карта! Разукрасим данные, положим на базовую карту и включим навигацию. Подсмотреть схемы окрашивания можно здесь.


Для нормальной работы с разбиением классов напишем запрос с приведением типов. Синтаксис PostgreSQL


query_layer = 'select cartodb_id, the_geom, the_geom_webmercator, voters_oa::integer, voters_percent::float, state_out::float from mo_votes'

Итак, явка:


from cartoframes import Layer, BaseMap, styling, QueryLayer
l = QueryLayer(query_layer, color={'column': 'voters_percent', 'scheme': styling.darkMint(bins=7)})
map = cc.map(layers=[BaseMap(source='light', labels='front'), l], size=(990, 500), interactive=False)


Количество жителей


l = QueryLayer(query_layer, color={'column': 'voters_oa', 'scheme': styling.burg(bins=7)})
map = cc.map(layers=[BaseMap(source='light', labels='front'), l], size=(990, 500), interactive=False)


И, например, надомное голосование


l = QueryLayer(query_layer, color={'column': 'state_out', 'scheme': styling.sunsetDark(bins=5)})
map = cc.map(layers=[BaseMap(source='light', labels='front'), l], size=(990, 500), interactive=False)


Следует заметить, что в данный момент cartoframes не позволяет встроить инфоокна прямо в окно тетрадки, показывать легенду, а также публиковать карты на Carto. Но эти опции в процессе имплементации.


А теперь попробуем более сложный, но весьма гибкий способ встраивания карт в Jupyter Notebook...


Визуализация геоданных с помощью folium


Итак, нам хотелось бы получить не только навигацию, но и инфоокна на карте. А еще получить возможность публикации визуализации на своем сервере или на github. Нам поможет folium.


Важное отступление

Библиотека folium — довольно специфичная штука. Она представляет собой python-обертку вокруг JS-библиотеки Leaflet, которая как раз и отвечает за картографическую визуализацию. Следующие манипуляции выглядят не очень pythonic, но не пугайтесь, я все поясню.


import folium

Простая визуализация наподобие Carto делается достаточно просто. Что происходит?


  • мы создаем инстанс карты, m, с центром в выбранных координатах;
  • добавляем инстанс картограммы (choropleth)
    В инстансе картограммы мы задаем много атрибутов:
  • geo_data — геоданные, мы конвертируем данные нашего датафрейма в geojson;
  • name — задаем имя слоя;
  • data — непосредственно данные, их мы выбираем тоже из датафрейма;
  • key_on — ключ для соединения (обратите внимание, в geojson все атрибуты сложены в отдельный элемент, properties);
  • columns — ключ и атрибут для раскрашивания;
  • fill_color, fill_opacity, line_weight, line_opacity — цветовая шкала заливки, прозрачность заливки, ширина и прозрачность линий;
  • legend_name — заголовок легенды;
  • highlight — добавление интерактива (подсветки при наведении и приближения при клике) у объектов.

Цветовая шкала основывается на библиотеке Color Brewer. Я крайне рекомендую при работе с картами пользоваться ей.


m = folium.Map(location=[55.764414, 37.647859])
m.choropleth(
    geo_data=full_gdf[['okrug', 'geometry']].to_json(),
    name='choropleth',
    data=full_gdf[['okrug', 'voters_oa']],
    key_on='feature.properties.okrug',
    columns=['okrug', 'voters_oa'],
    fill_color='YlGnBu',
    line_weight=1,
    fill_opacity=0.7,
    line_opacity=0.2,
    legend_name='type',
    highlight = True
)
m


Итак, у нас получилась интерактивная картограмма. Но хотелось бы и инфоокон...


Здесь нам придется немного хакнуть библиотеку. У нас есть партии-победители в каждом ТИК. Для каждой из них мы определим базовый цвет. Но не в каждом округе победа партии означает 100% голосов. К каждому базовому цвету мы определим 3 градации: абсолютная власть (100%), контрольный пакет (>50%) и кооперация (<50%). Напишем функцию определения цвета:


def party_color(feature):
    party = feature['properties']['top1']
    percent = feature['properties']['top1_percent']
    if party == 'Единая Россия':
        if percent == 100:
            color = '#969696'
        elif 50 < percent < 100:
            color = '#bdbdbd'
        else:
            color = '#d9d9d9'
    elif party == 'Яблоко':
        if percent == 100:
            color = '#78c679'
        elif 50 < percent < 100:
            color = '#addd8e'
        else:
            color = '#d9f0a3'
    elif party == 'КПРФ':
        if percent == 100:
            color = '#ef3b2c'
        elif 50 < percent < 100:
            color = '#fb6a4a'
        else:
            color = '#fc9272'
    elif party == 'Справедливая Россия':
        if percent == 100:
            color = '#2171b5'
        elif 50 < percent < 100:
            color = '#4292c6'
        else:
            color = '#6baed6'
    elif party == 'Самовыдвижение':
        if percent == 100:
            color = '#ec7014'
        elif 50 < percent < 100:
            color = '#fe9929'
        else:
            color = '#fec44f'
    return {"fillColor":color, "fillOpacity":0.8,"opacity":0}

Теперь напишем функцию формирования html для инфоокна:


def popup_html(feature):
    html = '<h5> Распределение мест в ТИК {}</h5>'.format(feature['properties']['okrug'])
    for p in ['top1', 'top2', 'top3']:
        if feature['properties'][p + '_elected'] > 0:
            html += '<br><b>{}</b>: {} мест'.format(feature['properties'][p], feature['properties'][p + '_elected'])
    return html

Наконец, мы конвертируем каждый объект датафрейма в geojson и добавляем его к карте, привязывая к каждому стиль, поведение при наведении и инфоокно


m = folium.Map(location=[55.764414, 37.647859], zoom_start=9)

for mo in json.loads(full_gdf.to_json())['features']:
    gj = folium.GeoJson(data=mo, style_function = party_color, control=False, highlight_function=lambda x:{"fillOpacity":1, "opacity":1}, smooth_factor=0)
    folium.Popup(popup_html(mo)).add_to(gj)
    gj.add_to(m)
m



Наконец, мы сохраняем нашу карту. Ее можно опубликовать, например, на Github:


m.save('tmp/map.html')

Заключение


С помощью простых инструментов визуализации геоданных можно найти бесконечный простор для инсайтов. А немного поработав над данными и визуализацией, можно успешно опубликовать ваши инсайты на Carto или на github. Репозиторий этой статьи.


Поздравляю, теперь вы политолог!

Вы научились анализировать результаты выборов. Поделитесь инсайтами в коментах!

Комментарии (37)


  1. Meklon
    25.09.2017 15:25
    +1

    Изумительно, спасибо. Я как раз искал инструменты для финансового анализа. Грубо говоря оценить затраты на рекламу по городам в регионе и поток пациентов из территории этих городов.


    1. fall_out_bug Автор
      25.09.2017 15:28
      +1

      Осторожнее при работе с регулярной сеткой в geopandas. Он у меня сетку 500х500м на Москву рендерит по полчаса. Однако пространственные операции проводит очень шустро.


      1. Meklon
        25.09.2017 15:41
        +1

        А что можно использовать для тепловой карты с центрами в городах использовать? Мне формальные границы районов не так и интересны.


        1. fall_out_bug Автор
          25.09.2017 15:45
          +2

          Carto хорошо справляется с рендером. Есть еще звездолет github.com/OpenGeoscience/geonotebook, там прям рендер-сервер в упаковке.


          1. Meklon
            25.09.2017 15:47
            +1

            Почитаю, спасибо)


          1. Meklon
            25.09.2017 15:51
            +1

            А что-нибудь легковесное без интерактива? Мне в принципе только табличные данные и статичные изображения нужны.


            1. Kamrus
              25.09.2017 15:55
              +2

              Как вариант можно использовать google API
              developers.google.com/maps/documentation/javascript/heatmaplayer?hl=ru

              Ещё мне нравится datawrapper(для визуализации геоданных можно использовать не ширину и долготу, а их сокращения согласно ISO ru.m.wikipedia.org/wiki/ISO_3166-1)
              И carto, конечно, тоже удобно, но уже с шириной и долготой придётся работать

              Можно без проблем выгрузить изображения карт, да и других инструментов там много.


              1. Meklon
                25.09.2017 16:09
                +1

                Не умею в JavaScript) в любом случае посмотрю. Спасибо.


            1. fall_out_bug Автор
              25.09.2017 16:10
              +1

              github.com/bokeh/datashader — не то чтобы легковесное, но довольно шустрое.


  1. vrag86
    25.09.2017 18:04
    +1

    Получается, в районах с низкой явкой — победитель едро… Интересный факт, наводит на мысли


    1. fall_out_bug Автор
      25.09.2017 18:04
      +2

      Предлагаю Вам составить карту корреляции! :)


    1. mephistopheies
      25.09.2017 18:26

      да, и еще обратите внимание, что зоны с низкой явкой так же являются и самыми густонаселенными — вот он электорат


    1. StjarnornasFred
      25.09.2017 22:17
      +1

      Скажу более того: низкая явка на этих выборах была главным, если не сказать единственным, оружием данной партии. А войска — ветераны из санаториев, полк солдат из квартиры на Арбате, ну и фольксштурм, проинструктированный в школах на родительских собраниях. Даже обязательные плакаты о месте проведения выборов появились не за 10 дней, а за 2-3 дня, а кое-где — лишь в день выборов. Фактически, кроме наших листовок, вне интернета информации о выборах не было.


      1. erty
        26.09.2017 01:34
        +1

        Ну теоретически за отсутствие информирования можно призвать ответственного за исполнение к ответу (секретарь цик?). Наказания там не предусмотрено, но если бы было достаточно вменяемых людей в цик, можно было бы поднять вопрос о снятии полномочий с Гришиной, на основании халатного исполнения обязанностей. Но это всё фантастика.


      1. erwins22
        26.09.2017 09:38
        -1

        Может просто всем пофиг или вы предлагаете ввести штраф за неголосование или уголовную ответственность?
        Или предлагаете обеспечивать явку ЛЮБЫМ путем?
        Информация о выборах была секретной?

        Вы просто не смогли убедить людей.


        1. mephistopheies
          26.09.2017 09:42
          +2

          а вам не кажется, что при неравномерном доступе к информационным ресурсам получается немного не често? государство финансирует разные системные/марионеточные партии, одному только лдпр выделяют миллард рублей в год, им дают время на гос каналах и все такое, а остальным не дают ничего вообще, как тут можно убедить людей прийти, если все что у тебя есть это интернет, а твоих активистов арестовывают, а твои агитматериалы изымают мусора?

          пс: в австралии штраф за не явку на выборы


          1. erwins22
            26.09.2017 11:42
            -2

            " пс: в австралии штраф за не явку на выборы"
            я против принудиловки.

            Просто вам нечего предложить позитивного.


            1. mephistopheies
              26.09.2017 11:48
              +2

              я тоже против штрафов, но я за равный доступ к ресурсам (будь то бабки или информационный ресурс) для всех кандидатов, а и еще за свободный доступ к возможности быть избранным

              предлагаю позитивное:
              — давайте дадим всем кандидатам доступ к первому каналу
              — давайте запретим государству спонсировать любые партии
              — давайте следовать конституции и допускать на выборы всех кто хочет


              1. erwins22
                26.09.2017 12:05

                — давайте дадим всем кандидатам доступ к первому каналу

                я хочу по 3 часа в день!!! КАЖДЫЙ ДЕНЬ!!!
                Есть интернет и вы можете создать там 1000 000 каналов и каждый может создать канал.

                — давайте запретим государству спонсировать любые партии

                Согласен. Мне пофиг на неравноправие партий, но это экономия денег.

                — давайте следовать конституции и допускать на выборы всех кто хочет

                А разве этот пункт нарушается?
                Насколько я помню Навальный не стал опротестовывать приговор в Европейском суде.

                И все только о выборах.


                1. mephistopheies
                  26.09.2017 12:10

                  >я хочу по 3 часа в день!!! КАЖДЫЙ ДЕНЬ!!!
                  не конструктивно, нужно выделять в день скажем суммарно на всех 3 часа в разное время, а потом делить 3 часа на всех, пусть выкручиваются, ну или всем запретить доступ к каналу, это не важно, главное что бы все были в равных положениях

                  >Насколько я помню Навальный не стал опротестовывать приговор в Европейском суде.
                  ну посмотрим как его зарегистрируют кандидатом


                  1. erwins22
                    26.09.2017 12:17

                    не конструктивно, нужно выделять в день скажем суммарно на всех 3 часа в разное время, а потом делить 3 часа на всех, пусть выкручиваются, ну или всем запретить доступ к каналу, это не важно, главное что бы все были в равных положениях

                    В России более 140 миллионов человек, т.е. каждый должен иметь право передать привет родным? Чем кто то лучше?

                    >Насколько я помню Навальный не стал опротестовывать приговор в Европейском суде.
                    ну посмотрим как его зарегистрируют кандидатом

                    А не будут, причем полностью по закону, сам виноват, надо было идти до конца в Европейский суд, но если бы его признали там виновным, то он бы сел.


                    1. mephistopheies
                      26.09.2017 13:14
                      +2

                      >В России более 140 миллионов человек, т.е. каждый должен иметь право передать привет родным? Чем кто то лучше?
                      любой, кто соберет достаточное количество подписей

                      >А не будут, причем полностью по закону, сам виноват, надо было идти до конца в Европейский суд, но если бы его признали там виновным, то он бы сел.
                      бла-бла-бла


                1. jex
                  26.09.2017 13:42
                  +1

                  >>Насколько я помню Навальный не стал опротестовывать приговор в Европейском суде.

                  www.rbc.ru/rbcfreenews/59c3f09b9a7947bfa507d694

                  #24
                  search.coe.int/cm/pages/result_details.aspx?objectid=090000168074ced2


                  1. erwins22
                    27.09.2017 18:42

                    Прочитал. Суд НЕ ВЫНЕС решение о невиновности Навального. Много слов, декламаций и т д.


    1. menfromearth
      26.09.2017 12:01
      +1

      Попробуйте поизучать выборы и их статистику. Как правило, высокая явка свидетельствует о проблемным местах, где есть «вбросы», как раз за счет разницы явок.


      1. mephistopheies
        26.09.2017 14:29
        +2

        сейчас как раз наоборот, причем вполне объяснимо, политика партии была минимизировать информированность населения, что бы проголосовали только их люди; ну и видно, что там где оппозиционные и независимые кандидаты прошли, там и явка выше, тк они работали на информирование населения (ну и стоит конечно учесть, что это самые малонаселенные зоны без муравейников, сама выборка людей сильно смещенная)


  1. StjarnornasFred
    25.09.2017 22:12
    +1

    Спасибо автору от одного из тех, кто «не прошёл». Утащу к себе на память =)

    P.S. А интересующиеся могут почитать ещё и гауссовский анализ выборов в некоторых районах от Леонида Каганова на его сайте. Правда, там только 2 района — мой и его — но скрипт выложен и его можно заюзать для любого.


    1. fall_out_bug Автор
      25.09.2017 22:24
      +1

      Сайт у него, конечно, до жути неудобный… Но да, такие графики можно запросто в питоне порисовать. У меня были в планах УИКи с графиками, но это заняло бы еще и третьи выходные :) А там и четвертые-пятые.


      1. StjarnornasFred
        25.09.2017 22:37
        +2

        Да, не для всех выборы закончились :) кому-то ещё в суд ходить, доказывая, где и что не так посчитали…


        1. erty
          26.09.2017 01:40
          +4

          Это правильно. Мы вот в Раменках ходили, фиг что доказали конечно, зато теперь образцово-показательный район. Так что даже без шанса на победу ходить всё-равно надо, чтобы у людей не возникало иллюзии, что им всё может сойти с рук.


  1. QtRoS
    26.09.2017 08:37
    +2

    Очень познавательная статья, спасибо!
    P.S. В очередной раз убеждаюсь, как важно поддержать существующий интерфейс. Загрузка карты в pandas это идеальный метод сделать просто, авторам лайк.


    1. fall_out_bug Автор
      26.09.2017 09:01
      +2

      Как говорят мудрецы: «Beautiful is better than ugly...»


  1. junior_it
    26.09.2017 15:10
    -4

    Увидев ссылку на сайт Гудкова, я понял что лучше эту статью не читать. Запарили уже тут с своей политикой лесть… неокрепшие умы молодых программистов затуманивать. Им учиться надо, книги читать… а вы тут Гудкова проталкиваете


    1. mephistopheies
      26.09.2017 15:16
      +2

      а расскажите какие книги им читать нужно? и еще подскажите сайт где можно эти данные скачать, что бы не проталкивать гудкова?


      1. junior_it
        27.09.2017 07:13
        -1

        в этой статье можно было поделиться опытом и не ссылаясь на сайты политического характера. Книги по программированию, больше надо знать и уметь… повышать свой skill. Я одинаково против ссылок на Гудкова и Зюганова, и президента…. Вы почитайте комментарии, о технических моментах обсуждают 30%… остальные начали обсуждать «ядро» и электорат… Вот и вопрос зачем ссылка на Гудкова? может хотели протолкнуть кое чего ;) в сознание


        1. mephistopheies
          27.09.2017 07:21

          дата саенс — это метан + программирование + предметная область, если убрать третье, то получатся просто методы численное оптимизации; ты просто далек от анализа данных, вот и бесишся; и кстати как можно дать ссылку на результаты выборов, не дав ссылку ни на один полит ресурс и не воруя данные? пусть даже на такой прекрасный как у гудкова?

          пс: ты так про книги и не ответил, может все таки не будешь сливаться?


  1. KiloLeo
    27.09.2017 15:25
    +2

    Экскюзе муа, но в парсере первые две строчки ошибочные. Я поправил на
    df = pd.read_html(url)[7]
    df.columns = ['admin','municipal']
    и оно отработало. Но соответствует ли изначальному замыслу и правильно ли — пока не понял.