[Python, Data Mining, Визуализация данных, Веб-аналитика] Волны московской реновации

Автор Сообщение
news_bot ®

Стаж: 6 лет 4 месяца
Сообщений: 27286

Создавать темы news_bot ® написал(а)
27-Авг-2020 16:31


Доброго времени суток дорогие читатели хабра, 12 августа 2020 года были опубликованы этапы переезда по программе реновации (ознакомиться можно здесь) и мне стало интересно, а как это будет выглядеть, если эти этапы визуализировать. Тут нужно уточнить, что я никак ни связан с правительством Москвы, но являюсь счастливым обладателем квартиры в доме под реновацию, поэтому мне было интересно посмотреть, может даже с некоторой точностью предположить, куда возможно будет двигаться волна реновации в моём случае (а может быть и в вашем, если вас дорогой читатель это заинтересует). Конечно точного прогноза не получится, но хотя-бы можно будет увидеть картину под новым углом. Сразу нужно отметить, что у меня получилось не всё, что я хотел сделать, но если кому-то интересно почитать о том, как собирались данные и какие интересные моменты с этим были прошу под кат. Забегая вперёд, скажу, что пометить дома согласно волнам реновации получилось, однако не удалось отобразить на карте стартовые площадки, хотя их можно добавить вручную, ниже покажу как.
1. Введение

Вкратце о программе реновации

SPL
Программа реновации была запущена Правительством Москвы в 2017 году. Благодаря ей 350 тысяч московских семей, то есть более миллиона человек, переедут в новые квартиры с отделкой комфорткласса.
Какие дома войдут в программу, решали сами жители. По итогам голосования в программу было включено 5174 дома.
Участники получат равнозначное жилье в своем районе… (далее можно прочитать здесь)

На основании приказа правительства Москвы от 12 августа 2020 г. № 45/182/ПР-335/20 (прочитать можно здесь) вся программа переселения рассчитана до 2032 года и должна будет пройти в три этапа (три волны):
  • первый этап 2020 — 2024гг., в него вошло 930 домов, страницы 3-29 в приказе
  • второй этап 2025 — 2028гг., в него вошло 1636 домов, страницы 30-76 в приказе
  • третий этап 2029 — 2032гг., в него вошло 1809 домов, страницы 77-128 в приказе
  • без определённого этапа (этапы должны будут определиться до конца 1 квартала 2021г.) — 688 домов, страницы 129-148 в приказе

2. Парсинг данных
Данные я взял из этого приказа, т.к. приказ — это pdf файл с таблицами, то я использовал библиотеку tabula для парсинга pdf файлов.
import pandas as pd
import numpy as np
import requests
from tabula import read_pdf
import json
import os

Первым делом я спарсил одну страницу из этих таблиц, чтобы посмотреть, как дальше чистить данные.
test = read_pdf('prikaz_grafikpereseleniya.pdf', pages='3', pandas_options={'header':None})

test.head()

0
1
2
3
4
5
0
No п/п
АО
Район
NaN
Адрес дома
unom
1
1
ЦАО
Басманный
Бакунинская ул., д.49 c.4
NaN
1316
2
2
ЦАО
Басманный
Бакунинская ул., д.77 c.3
NaN
1327
3
3
ЦАО
Басманный
Балакиревский пер., д.2/26
NaN
19328
4
4
ЦАО
Басманный
Госпитальный Вал ул., д.3
NaN
31354
Как видно из того, что получилось спарсить, чтобы очистить данные необходимо удалить лишние колонки и строчки, что и делает функция parse_pdf_table.
def parse_pdf_table(pages, pdf_file='prikaz_grafikpereseleniya.pdf'):
    df = read_pdf(pdf_file, pages=pages, pandas_options={'header':None})
    # удаляем не нужные строки
    df = df[~(df.iloc[:,0] == 'No п/п')]
    # оставляем только нужные колонки
    df = df.iloc[:,1:4]
    df.columns = ['AO', 'district', 'address']
    return df

Каждая волна находится в своём диапазоне страниц, парсим их и проверяем по документу, т.е. количество строк должно совпадать с тем, что есть в pdf файле. (Также сразу добавляем к данным номер волны, т.к. это пригодится в будущем)
wave_1 = parse_pdf_table('3-29') # 2020 - 2024
wave_1['wave'] = 1

wave_1.shape

(930, 4)

wave_2 = parse_pdf_table('30-76') # 2025 - 2028
wave_2['wave'] = 2

wave_2.shape

(1636, 4)

wave_3 = parse_pdf_table('77-128') # 2029 - 2032
wave_3['wave'] = 3

wave_3.shape

(1809, 4)

unknown = parse_pdf_table('129-148')
unknown['wave'] = 0

unknown.shape

(688, 4)

3. Обработка данных
Обрабатывать данные будем на пандасе (pandas), для этого соберём все волны в один датафрейм df.
df = pd.concat([wave_1, wave_2, wave_3, unknown], ignore_index=True)

Выделим своим цветом метки каждой волны.
df['marker-color'] = df['wave'].map({1:'#0ACF00',  # зеленый
                                     2:'#1142AA',  # синий
                                     3:'#FFFD00',  # жёлтый
                                     0:'#FD0006'}) # красный

Также подпишем каждую метку в зависимости от волны.
df['iconContent'] = df['wave'].map({1:'1',
                                    2:'2',
                                    3:'3',
                                    0:''})

В описание метки добавим адрес.
df['description'] = df['address']

Если не уточнить город — Москва, то по данным, полученным из геокодера получится, что реновация началась по всей стране, да что там, во всём мире. (Даёшь реновацию во всём мире! :)

df['address'] = 'Москва, ' + df['address']

Для определения координат каждого дома по адресу я использовал геокодер яндекса, что очень удобно, т.к. он бесплатный и на него есть очень хорошая документация. Если нужно будет пересчитать координаты, то не забудьте ввести свой ключ.
def geocoder(addr, key='введи свой ключ'):
    url = 'https://geocode-maps.yandex.ru/1.x'
    params = {'format':'json', 'apikey': key, 'geocode': addr}
    response = requests.get(url, params=params)
    try:
        coordinates = response.json()["response"]["GeoObjectCollection"]["featureMember"][0]["GeoObject"]["Point"]["pos"]
        lon, lat = coordinates.split(' ')
    except:
        lon, lat = 0, 0
    return lon, lat

%%time
df['longitude'], df['latitude'] = zip(*df['address'].apply(geocoder))

CPU times: user 2min 11s, sys: 4.31 s, total: 2min 15s
Wall time: 15min 14s

Все координаты определились удачно (именно удачно, т.к. нет гарантий, что геокодер спарсил адрес так как нам нужно), другими словами он хотя-бы что-то вернул.
len(df[df['longitude'] == 0])

0

Сохраним полученные данные.
df.to_csv('waves.csv')

#df = pd.read_csv('waves.csv')

4. Формирование карты волн реновации
Для отображения полученных данных на карте я использовал формат GeoJSON.
def df_to_geojson(df, properties, lat='latitude', lon='longitude'):
    geojson = {'type':'FeatureCollection', 'features':[]}
    for _, row in df.iterrows():
        feature = {'type':'Feature',
                   'properties':{},
                   'geometry':{'type':'Point',
                               'coordinates':[]}}
        feature['geometry']['coordinates'] = [row[lon],row[lat]]
        for prop in properties:
            feature['properties'][prop] = row[prop]
        geojson['features'].append(feature)
    return geojson

Т.к. меток получилось очень много, то полная карта может медленно работать на слабом ПК, поэтому я разделил данные по округам Москвы для удобства.
properties = ['marker-color', 'iconContent', 'description']
if not os.path.exists('data'):
    os.makedirs('data')
for ao, data in df.groupby('AO'):
    geojson = df_to_geojson(data, properties)
    with open('data/' + ao + '.geojson', 'w') as f:
        json.dump(geojson, f, indent=2)

Полученные данные в формате .geojson я сохранил в папку data. В файле ВСЕ_ОКРУГА.geojson записаны данные по всем округам вместе.
geojson = df_to_geojson(df, properties)
with open('data/ВСЕ_ОКРУГА.geojson', 'w') as f:
    json.dump(geojson, f, indent=2)


ссылка на полную карту (может работать медленно) здесь.

В целом получилось не плохо, все метки внутри границ Москвы, однако, есть и несколько ошибок, как например недалеко от Сергиева Посада — Пролетарий СНТ территория (п.Вороновское), д.1 или в окрестностях Орехово-Зуево — Гаражный пер. (пос.ДСК Мичуринец, п.Внуковское), д.8/КБ/Н. (Честно говоря я бы и сам не сразу понял, где это находится)
5. Что хотелось сделать, но не получилось :(
Официальный список стартовых площадок находится здесь.
Также на карту волн реновации я хотел добавить стартовые площадки, однако это не получилось сделать. Проблема даже не в том, что нормально спарсить список не удалось, это можно было бы решить, проблема в том, что геокодер не может точно определить координаты по владению, например, Шмитовский проезд, вл. 39, Мукомольный проезд, вл. 6, или где находится этот адрес — район Южное Медведково, мкр. 1, 2, 3, корп. 38.
Таким образом единственный источник данных это официальная карта реновации (находится здесь), а как получить из неё координаты я не знаю, если кто знает, как получить координаты стартовых площадок, напишите пожалуйста в комментах.
Однако не всё так плохо и выход всё же есть — можно добавить эти метки вручную!

Видео-инструкция о том, как это сделать есть в исходном коде проекта, а также её можно посмотреть/скачать здесь.
Выводы
В целом можно сказать, что затея удалась, однако ещё раз повторю, выводы, которые вы можете получить исходя из этих данных носят лишь примерный характер, даже в самом приказе написано, что сроки указанные в нём являются ориентировочными и могут быть скорректированы, к тому же значительное число домов пока ещё даже не распределено.
Исходный код залит на github и скачать его можно здесь.
Спасибо за внимание.
===========
Источник:
habr.com
===========

Похожие новости: Теги для поиска: #_python, #_data_mining, #_vizualizatsija_dannyh (Визуализация данных), #_vebanalitika (Веб-аналитика), #_renovatsija (реновация), #_volny_renovatsii (волны реновации), #_snos (снос), #_programma_renovatsii (программа реновации), #_python, #_data_mining, #_vizualizatsija_dannyh (
Визуализация данных
)
, #_vebanalitika (
Веб-аналитика
)
Профиль  ЛС 
Показать сообщения:     

Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы

Текущее время: 07-Июл 00:07
Часовой пояс: UTC + 5