Если вы занимаетесь аналитикой в ритейле, логистике или просто любите копаться в геоданных, то наверняка сталкивались с задачей оценки насыщенности региона точками присутствия.
Многие используют кластеризацию или простые scatter plot'ы, но когда точек становятся тысячи, они сливаются в одну черную кляксу. Сегодня мы на конкретном примере разберем, как превратить «кучу точек» в понятную и красивую инвертированную тепловую карту, где сразу видно — где у нас «белые пятна» (низкая конкуренция), а где рынок уже перенасыщен.

Итак, у нас есть датасет all_delivery_points.geojson со всеми пунктами выдачи заказов (ПВЗ). Всего их в России уже более 90 000, причем 66% — партнерские. Это огромная сеть, и вручную анализировать ее бесполезно. Если мы просто нанесем их на карту как маркеры, карта превратится в винегрет. Если используем стандартную тепловую карту, где красным обозначено скопление точек, мы увидим только Москву и пару городов-миллионников, а средние и малые города останутся едва заметным синим туманом.
Наша задача — сделать карту, полезную для бизнеса. Нам нужно ответить на вопросы:
Где ПВЗ так много, что открывать новый — каннибализация?
Где есть спрос, но точек нет («белые пятна»)?
Для этого мы пойдем на хитрость: мы не будем показывать абсолютную плотность, мы будем показывать относительную насыщенность внутри каждого города/кластера.
Агрегация и нормализация данных
Первое, что мы делаем — загружаем данные и переводим их в WGS84 (EPSG:4326), так как Folium работает именно в этой проекции.
gdf = gpd.read_file('all_delivery_points.geojson') coords = np.array([(geom.x, geom.y) for geom in gdf.to_crs('EPSG:4326').geometry])
Если мы построим тепловую карту по сырым координатам, она будет необъективной. Почему? Потому что в Москве точек 1000, а в Твери — 10. Алгоритм тепловой карты «съест» Тверь фоновым шумом.
Чтобы этого избежать, мы:
Округляем координаты до 3 знака (точность ~100 метров). Это позволяет сгруппировать точки, стоящие в одном здании или ТЦ.
Считаем уникальные локации и частоту (counts).
Делим карту на виртуальные сетки (в примере grid_size = 0.5 градуса ~ 50-55 км). Это нужно, чтобы обрабатывать каждый город отдельно.
# Агрегация дублей по одному адресу coords_rounded = np.round(coords, decimals=3) unique_coords, counts = np.unique(coords_rounded, axis=0, return_counts=True)
Чем больше ПВЗ в конкретной точке (внутри города), тем БОЛЬШЕ вес.
Звучит логично? На первый взгляд — да. Но для восприятия это означает, что красным будут гореть места скопления ПВЗ (конкуренты), а синим — места, где их мало.
Однако, чтобы сравнение было честным внутри одного города, мы применяем локальную нормализацию.
Мы проходим по каждому "квадрату" сетки (городу), находим максимальную концентрацию ПВЗ в одной точке внутри этого города, и делим все значения на этот максимум.
weights = city_counts / max_count # От 0 до 1
Таким образом, даже если в условном Краснодаре максимум ПВЗ в одной локации = 3, а в Москве = 30, на карте и там, и там самые "красные" точки будут иметь вес 1.0, показывая локальное перенасыщение.
Результат и выводы
Запускаем скрипт и открываем pvz_heatmap_inverted.html.
Что мы видим на примере столицы?
Москва пестрит красными пятнами. Открывать тут новый ПВЗ? Только если вы нашли уникальную нишу или ваш адрес находится в синей зоне внутри Москвы (а таких почти нет).
Подмосковье переливается желто-оранжевым — плотность средняя, но конкуренция уже чувствуется.
Небольшой город N на карте России — мы видим ярко-синюю область. Это сигнал для бизнеса: «Сюда нужно заходить!». В городе есть несколько ПВЗ, но они сконцентрированы в одном ТЦ, а остальные районы — чистое поле.

Такой подход позволяет ритейлерам эффективно распределять бюджет на открытие новых точек, не полагаясь на "экспертную оценку", а опираясь на чистую математику и геоданные.
По итогу мы написали скрипт, который из скучного JSON делает интерактивный бизнес-инструмент. Конечно, это базовая версия. В проде можно докрутить следующие вещи:
Учитывать не только количество ПВЗ, но и их проходимость (если есть данные).
Использовать реальные границы городов вместо сетки 0.5 градуса.
Добавить кластеризацию по времени работы или ассортименту.
Но и в текущем виде эта карта отвечает на главный вопрос: «Где нас не хватает?»
Если вы хотите искать места для бургерной или кофейни — принцип тот же, только вместо "ПВЗ" у вас будут "конкуренты". Удачи в анализе!
Весь код доступен по ссылке на нашем сайте. Берите, дорабатывайте, улучшайте.