Собесов

Кейс — обеспеченность районов Москвы ночной инфраструктурой

Кейсы и метрикиДашборды и BIСредняяMiddle

Условие

Сделать end-to-end проект: от ETL до выводов:

  1. Продумать обработку, хранение, визуализацию данных и используемое ПО. Нарисовать диаграммы для ETL (Data Flow) и ER (DWH).
  2. Построить отчёт(ы) на основе данных.
  3. Сделать выводы об обеспеченности районов и округов инфраструктурой по трём датасетам.

Источники:

  • data.mos.ru — велопарковки (№916), спортивные залы (№60622), площадки для выгула собак (№2663).
  • Wikipedia — население районов.

Обязательно — отчёт по обеспеченности «ночной» инфраструктурой, при этом фильтры по объектам:

  1. Велопарковки, работающие круглосуточно.
  2. Спортзалы, в адресе которых есть «2».
  3. Освещённые площадки для выгула собак.

Решение

Шаг 1. Архитектура

Стек, обоснованный для малого проекта:

[data.mos.ru API / выгрузки CSV]   [Wikipedia таблицы]
              \                      /
               \                    /
              [Python ETL: pandas, requests]
                       |
                  [Staging tables]
                       |
                 [Cleaning, geocoding, joining]
                       |
               [DWH: PostgreSQL + PostGIS]
                       |
              [BI: Metabase / Superset / Tableau]

Зачем PostGIS: объекты имеют координаты, нужно по ним считать «попадание в район/округ» — это spatial join ST_Within.

Шаг 2. ER-схема DWH

dim_district(district_id, name, okrug, population, area_km2, geom POLYGON)
dim_object_type(type_id, name)        -- 'bike_parking', 'gym', 'dog_area'

fact_objects(
  object_id,
  type_id,
  district_id,    -- определяется spatial join
  geom POINT,
  address,
  is_24h,         -- для парковок
  has_lighting,   -- для собачьих площадок
  attrs JSONB     -- остальные атрибуты
)

Для отчёта рассчитываем витрину:

mart_provision(
  district_id, district_name, okrug,
  population,
  cnt_bikeparking_24h,
  cnt_gym_addr_with_2,
  cnt_dog_area_lighted,
  per_10k_bikeparking_24h,
  per_10k_gym,
  per_10k_dog_area
)

Шаг 3. ETL Data Flow

  1. Extract: pandas-скриптом качаем CSV/JSON из data.mos.ru (или вручную через UI). Wikipedia парсим через pandas.read_html.
  2. Transform:
    • Чистим адреса, нормализуем районы (один и тот же район пишут «Хамовники» / «р-н Хамовники»).
    • Геокодим точки (если в выгрузке нет координат — Yandex Geocoder API).
    • Spatial join с границами районов → проставляем district_id.
    • Применяем фильтры (работа круглосуточно, наличие «2» в адресе, освещение).
  3. Load: вставляем в fact_objects, dim_district. Регенерируем mart_provision на основе фактов (idempotent).

Шаг 4. Расчёт «обеспеченности»

Простейший показатель — на 10 тыс. населения:

SELECT district_name,
       okrug,
       cnt_bikeparking_24h * 10000.0 / NULLIF(population, 0) AS bp_per_10k,
       cnt_gym_addr_with_2 * 10000.0 / NULLIF(population, 0) AS gym_per_10k,
       cnt_dog_area_lighted * 10000.0 / NULLIF(population, 0) AS dog_per_10k
FROM mart_provision
ORDER BY okrug, district_name;

Дополнительно — сводный индекс обеспеченности (например, средневзвешенный z-score по трём метрикам).

Можно перейти от «на 10к» к density на км² — для районов с разной плотностью застройки.

Шаг 5. Дашборд

  1. Карта Москвы с заливкой районов по выбранному показателю (хороплет).
  2. Топ-10 / антитоп-10 районов — bar chart.
  3. Сравнение по округам — boxplot или average.
  4. Таблица с возможностью drill-down: район → конкретные объекты на карте.

Фильтры: тип объекта (велопарковка / спортзал / выгул собак), показатель (на 10k / абсолют / на км²), округ.

Шаг 6. Выводы

Типовые вытекающие наблюдения (в реальной выгрузке могут отличаться):

  • ЦАО — больше всего объектов в абсолюте, но обеспеченность на 10к жителей часто ниже из-за высокой плотности.
  • Спальные округа (СВАО, ЮВАО) — низкая обеспеченность 24/7-парковками.
  • «Освещённые» собачьи площадки — редкость, преимущественно в новых ЖК.
  • Связь «спортзал в адресе с 2» — это формальный фильтр из задания, нужно подсветить, что это не семантика, а именно адрес содержит цифру 2.

Подводные камни

  1. Фильтры из задания — про адрес, не про звёздность. «Спортзалы с 2 в адресе» — это формальный текстовый фильтр. Не надо превращать его в «двухзвёздочные» или «дом 2» — может быть в любой части адреса.
  2. «Освещённость» площадок в датасете — отдельный атрибут (булева/строковая категория). Нужно маппить значения вручную, в данных бывает «есть/нет/неизвестно».
  3. Координаты vs адрес. В выгрузках data.mos.ru обычно есть и то, и другое, но не всегда консистентно. Геокодинг через адрес — резервный путь.
  4. Население районов. На Wikipedia табличка может быть устаревшей или собранной из разных лет. Зафиксировать год.
  5. Spatial join без индекса. На больших объёмах нужен CREATE INDEX ... USING GIST(geom).
  6. Деление на 0. Если у района нет населения (пром-зона) — NULLIF.
  7. Отчёт без цифр на 10к — обманчив. ЦАО «обеспечен» парковками в абсолюте, но плохо «на жителя».
  8. «Ночная инфраструктура» — это интерпретация. В задании прописаны три конкретных фильтра — именно их и применяем.

Эталонный ответ

  1. ETL: Python (pandas, requests) → Postgres+PostGIS (DWH) → BI (Metabase/Superset/Tableau).
  2. DWH: dim_district (с геометрией района и населением), fact_objects (точки с типом и атрибутами), витрина mart_provision с агрегатами.
  3. Расчёт обеспеченности — количество объектов / 10k населения (главный показатель), дополнительно — на км² и сводный индекс.
  4. Фильтры из задания применяем буквально: круглосуточные парковки, спортзалы с «2» в адресе, освещённые собачьи площадки.
  5. Дашборд — карта-хороплет + топ/антитоп + drill-down.
  6. Выводы — обеспеченность распределена неравномерно, абсолютные цифры в центре высокие, а на 10к жителей часто ниже, чем в спальных районах. Подчёркнуть, что результат — функция выбранных фильтров, а сами фильтры довольно искусственные.

Хочешь увидеть разбор?

Зарегистрируйся бесплатно — откроется развёрнутое решение этой задачи и ещё 4 на выбор.

Зарегистрироваться и увидеть разбор
Уже есть аккаунт? Войти