Skip to main content

Tool for normalizing and standardizing Russian region names

Project description

License: CC BY-NC-SA 4.0 PyPI version Python Versions

Region Normalizer

Region Normalizer — инструмент для нормализации и стандартизации наименований российских регионов, а также добавления нормализующих переменных. Он помогает распознавать регион даже в случаях, когда в названии встречаются опечатки, латинские буквы или другие особенности написания. Инструмент сопоставляет различные формы написания с эталонным справочником и позволяет извлекать дополнительные атрибуты, такие как коды ОКАТО, ISO, английские названия и многое другое.

Разные наименования одного и того же региона — частая проблема в реальных данных, например, на портале ЕМИСС встречается до 275 различных вариантов написания регионов. Особенно многообразны варианты у Тюменской и Архангельской областей — по 8 и 7 вариантов соответственно. В состав этих регионов входят автономные округа (ХМАО, ЯНАО, НАО), и часть ведомств отмечает, что данные приведены без автономных округов, но сокращения и формулировки используются самые разные.

Возможности

  • Поиск и нормализация региона по произвольному названию (с учетом опечаток, сокращений, аббревиатур, смешения латиницы и кириллицы)
  • Пакетная обработка больших таблиц с названиями регионов
  • Гибкая настройка весов алгоритмов сопоставления
  • Добавление дополнительных полей из эталонного справочника (ОКАТО, ISO, английское название и др.)
  • Добавление нормализующих переменных (численность населения, индекс потребительских цен)

Установка

Установите пакет с помощью pip:

pip install reg_normalizer

Установка для разработки

Если вы хотите внести изменения в код:

  1. Клонируйте репозиторий:
git clone https://github.com/tochno-st/reg_normalizer.git
cd reg_normalizer
  1. Создайте виртуальное окружение с помощью uv:
uv venv
  1. Активируйте виртуальное окружение:

На macOS/Linux:

source .venv/bin/activate

На Windows:

.venv\Scripts\activate
  1. Установите зависимости для разработки:
uv pip install -e ".[dev]"

Или используйте традиционный подход с pip:

pip install -r requirements.txt
pip install -e ".[dev]"

Быстрый старт

1. Импорт и инициализация

from reg_normalizer import RegionMatcher

matcher = RegionMatcher()

2. Нормализация одного региона

region_name = "московск область"
match, score = matcher.find_best_match(region_name)
print(f"Input: {region_name}")
print(f"Match: {match}")
print(f"Score: {score:.2f}")

3. Использование с DataFrame

import pandas as pd
sample_data = pd.DataFrame({
    'region_name': [
        'московск Обл',
        'свердловск',
        'петербург',
        'Mосковская област',
        'татарстан респ.',
        'Свердлов обл',
        'aлтайский к',
        'Республика     Алтай',
        'ХМао',
        'Юж федеральный округ',
        'спб',
        'рт',
        'город москва столица российской федерации город федерального значения',
        'тюменская область (кроме ханты мансийского автономного округа югры и ямало ненецкого автономного округа)'
    ]
})

result_df = matcher.match_dataframe(
    sample_data,
    'region_name',
    weights={'levenshtein': 0.4, 'token_set': 0.6},
    approach_weights={'original': 0.3, 'stemmed': 0.7},
    threshold=70
)

4. Добавление дополнительных полей

# Одно поле: передайте список из одного элемента
result_df = matcher.attach_fields(result_df, 'region_name', ['name_eng'])

# Несколько полей
result_df = matcher.attach_fields(result_df, 'region_name',
                                  ['name_eng', 'okato', 'iso_code'])

print(result_df.head())

5. Просмотр журнала преобразований

После вызова match_dataframe или attach_fields на экземпляре класса становится доступен журнал всех произведенных преобразований — метод get_match().

matcher.match_dataframe(df, 'region_name')

log = matcher.get_match()
print(log)

Возвращает pd.DataFrame, где каждая строка — одно уникальное исходное значение. Результаты отсортированы по сложности: сначала самые нетривиальные случаи.

Колонка Описание
original Исходное название из данных
normalized Итоговое нормализованное название
score Балл совпадения (0–100), None если совпадение не найдено
event Тип события (см. ниже)
note Дополнительное пояснение

Типы событий (event) — по приоритету

Событие Описание
parent_resolved Регион-родитель переопределен на вариант «без АО», т.к. в данных найдены автономные округа отдельно (напр. Архангельская область → без НАО)
low_score Совпадение не найдено выше порога — нужна ручная проверка
parent_kept Регион-родитель оставлен в варианте «с АО», т.к. автономные округа отдельно не встречаются
match Обычное успешное совпадение

Пример фильтрации проблемных случаев:

log = matcher.get_match()

# Только то, что требует внимания
log[log['event'].isin(['parent_resolved', 'low_score'])]

6. Работа с показателями (индикаторами)

Пакет позволяет присоединять к данным региональные статистические показатели (население, стоимость потребительской корзины, индекс бюджетных расходов и др.) из встроенной таблицы normalizers.csv. Данные доступны в разбивке по годам (2000–2025).

Список доступных показателей

Код Описание
pop_total Численность населения — всего
pop_men Численность населения — мужчины
pop_women Численность населения — женщины
pop_urban Численность населения — городское население
pop_rural Численность населения — сельское население
pop_0_17 Численность населения — 0–17 лет
pop_18_plus Численность населения — 18 лет и старше
pop_below_working Численность населения — моложе трудоспособного
pop_working Численность населения — в трудоспособном возрасте (муж. 16–59, жен. 16–54)
pop_above_working Численность населения — старше трудоспособного
pop_pension Численность населения — пенсионного возраста (66 лет и старше)
pop_total_avg Численность населения — всего (в среднем за год)
pop_men_avg Численность населения — мужчины (в среднем за год)
pop_women_avg Численность населения — женщины (в среднем за год)
pop_urban_avg Численность населения — городское население (в среднем за год)
pop_rural_avg Численность населения — сельское население (в среднем за год)
pop_0_17_avg Численность населения — 0–17 лет (в среднем за год)
pop_18_plus_avg Численность населения — 18 лет и старше (в среднем за год)
pop_below_working_avg Численность населения — моложе трудоспособного (в среднем за год)
pop_working_avg Численность населения — в трудоспособном возрасте (в среднем за год)
pop_above_working_avg Численность населения — старше трудоспособного (в среднем за год)
pop_pension_avg Численность населения — пенсионного возраста (в среднем за год)
fixed_basket Стоимость фиксированного набора потребительских товаров и услуг
ibr Индекс бюджетных расходов

Получить этот список программно можно так:

from reg_normalizer import RegionMatcher

matcher = RegionMatcher()
descriptions = matcher.get_indicator_descriptions()
for code, description in descriptions.items():
    print(f"{code}: {description}")

Присоединение показателей к данным

Метод attach_indicators поддерживает три сценария:

Сценарий 1. В данных есть столбец с годом:

import pandas as pd
from reg_normalizer import RegionMatcher

matcher = RegionMatcher()

df = pd.DataFrame({
    'region': ['Московская область', 'Республика Татарстан'],
    'year': [2023, 2023]
})

# Нормализуем названия
df = matcher.match_dataframe(df, 'region')

# Присоединяем показатели — merge по региону и году
df = matcher.attach_indicators(
    df,
    indicators=['pop_total', 'ibr'],
    name_col='region',
    year_col='year'
)

Сценарий 2. В данных нет столбца с годом — указываем год явно:

df = pd.DataFrame({
    'region': ['Московская область', 'Республика Татарстан']
})

df = matcher.match_dataframe(df, 'region')

# Присоединяем показатели за конкретный год
df = matcher.attach_indicators(
    df,
    indicators='pop_total',  # можно передать один код строкой
    name_col='region',
    year=2023
)

Сценарий 3. Несколько показателей сразу:

df = matcher.attach_indicators(
    df,
    indicators=['pop_total', 'pop_urban', 'pop_rural', 'fixed_basket', 'ibr'],
    name_col='region',
    year=2020
)

Параметры attach_indicators

Параметр Тип Описание
df DataFrame Таблица с нормализованными названиями регионов
indicators str или list[str] Код показателя или список кодов (см. таблицу выше)
name_col str Имя столбца с названием региона. По умолчанию 'object_name'
year_col str (опц.) Столбец с годом в данных. Если указан — merge по региону и году
year int (опц.) Год для фильтрации. Используется, если year_col не задан
how str Тип соединения: 'left' (по умолчанию) или 'outer'

Необходимо указать либо year_col, либо year. Если не указан ни один — будет вызвана ошибка ValueError.

Требования к формату таблицы

Метод attach_indicators ожидает, что входная таблица имеет «длинный» (long) формат — регионы и годы должны идти в строках, а не в столбцах:

region year ...
Московская область 2020 ...
Московская область 2021 ...
Республика Татарстан 2020 ...

Если в вашей таблице годы расположены по столбцам («широкий» формат), ее нужно предварительно привести к длинному формату, например с помощью pd.melt:

# Было: столбцы — годы
#   region              | 2020 | 2021 | 2022
#   Московская область  | ...  | ...  | ...

df_long = df.melt(id_vars='region', var_name='year', value_name='value')
df_long['year'] = df_long['year'].astype(int)

# Теперь можно присоединять показатели
df_long = matcher.attach_indicators(df_long, indicators='pop_total',
                                    name_col='region', year_col='year')

Как работает алгоритм

При вызове find_best_match или match_dataframe каждое название региона проходит через несколько последовательных шагов:

1. Препроцессинг — приведение строки к стандартному виду:

  • замена визуально похожих латинских символов на кириллические (oо, pр и т.д.)
  • дефисы и тире → пробелы, нормализация пробелов, lowercase
  • расширение аббревиатур «Республика»: Респ. X и X Респ.республика X
  • удаление сносок (1);2), 3);4)) и единиц измерения (, млн т, , млрд. руб.)
  • удаление служебных фраз (в границах, без учета новых субъектов и др.)

2. Замена аббревиатур — если preprocessed строка целиком совпадает с ключом в справочнике сокращений, она заменяется на полное название. Например: спбСанкт-Петербург, хмаоХанты-Мансийский автономный округ — Югра.

3. Обнаружение составных строк — если строка содержит разделители (и, ,, ;), каждая часть матчится отдельно. Если набор частей соответствует одному из правил (напр. Тюменская область + ХМАО + ЯНАО), возвращается канонический составной регион. Если правило не найдено — возвращается None.

4. Нечеткое сопоставление — для каждого эталонного региона вычисляется взвешенная оценка из четырех компонент:

  • Levenshtein ratio по оригинальному тексту
  • token_set_ratio по оригинальному тексту
  • Levenshtein ratio по стеммированному тексту (Snowball, русский)
  • token_set_ratio по стеммированному тексту

Итоговая оценка = взвешенная сумма по алгоритмам × взвешенная сумма по подходам. Если лучшая оценка ниже threshold — совпадение не засчитывается.

5. Пост-анализ (только для match_dataframe) — после матчинга всего датасета анализируется весь набор совпадений. Если в данных одновременно присутствует регион-родитель (напр. Архангельская область) и его автономный округ (Ненецкий АО), родитель автоматически переопределяется на вариант «без АО». Если автономный округ в данных не найден — родитель остается в варианте «с АО».

Разработка и тестирование

Запуск тестов

Проект использует pytest для тестирования. Все тесты находятся в директории tests/.

Запустить все тесты:

pytest tests/ -v

Запустить тесты с подробным выводом:

pytest tests/ -v -s

Запустить конкретный тестовый файл:

pytest tests/test_indicators.py -v
pytest tests/test_regions_validator.py -v

Запустить конкретный тест:

pytest tests/test_indicators.py::test_get_indicator_descriptions -v

Запустить тесты с покрытием кода:

pytest tests/ --cov=reg_normalizer --cov-report=html

После выполнения команды отчет о покрытии будет доступен в htmlcov/index.html.

Структура тестов

  • tests/test_indicators.py — тесты для функций работы с индикаторами (get_indicator_descriptions, attach_indicators)
  • tests/test_regions_validator.py — тесты для RegionMatcher и вспомогательных функций (preprocess_name, stem_region_name, find_best_match, match_dataframe, attach_fields)

Работа с виртуальным окружением uv

Если вы используете uv для управления зависимостями:

Создание нового окружения:

uv venv

Установка зависимостей:

uv pip install -e ".[dev]"

Обновление зависимостей:

uv pip install --upgrade -e ".[dev]"

Деактивация окружения:

deactivate

Кастомизация

  • weights — веса для алгоритмов сравнения ('levenshtein', 'token_set')
  • approach_weights — веса для подходов ('original', 'stemmed')
  • threshold — пороговое значение для принятия совпадения

Пример:

custom_weights = {'levenshtein': 0.3, 'token_set': 0.7}
custom_approach_weights = {'original': 0.2, 'stemmed': 0.8}
match, score = matcher.find_best_match(
    "свердловск",
    weights=custom_weights,
    approach_weights=custom_approach_weights,
    threshold=60
)

TODO

  • Добавить обработку сокращений "Республика" (респ., р., Респ.)
  • Решить проблему с фразами, в которых встречается "Российская Федерация", но это не регион
  • Проверить, что хорошо обрабатываются вложенные автономные округа

Лицензия

Creative Commons License Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0).

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

reg_normalizer-1.5.0.tar.gz (271.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

reg_normalizer-1.5.0-py3-none-any.whl (261.7 kB view details)

Uploaded Python 3

File details

Details for the file reg_normalizer-1.5.0.tar.gz.

File metadata

  • Download URL: reg_normalizer-1.5.0.tar.gz
  • Upload date:
  • Size: 271.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for reg_normalizer-1.5.0.tar.gz
Algorithm Hash digest
SHA256 c73c026f0561ba0ed11af26ec09aad28d8290b83dbd576d64fa0d973261051a5
MD5 32164a4554a121c939e30404298483cd
BLAKE2b-256 79daf5faf5601ac0496c91bf28934e9509a7a211f6eceefb64b3ca7baa959d1c

See more details on using hashes here.

Provenance

The following attestation bundles were made for reg_normalizer-1.5.0.tar.gz:

Publisher: python-publish.yml on tochno-st/reg_normalizer

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file reg_normalizer-1.5.0-py3-none-any.whl.

File metadata

  • Download URL: reg_normalizer-1.5.0-py3-none-any.whl
  • Upload date:
  • Size: 261.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for reg_normalizer-1.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 240243990a4b7ca2ac32963bf1ba76382006aafb7c0e88eb8fc0e808bdc7018c
MD5 ccf60bc1c41b63c3d4aed98610b244ec
BLAKE2b-256 243c7c5b119867d2f218b860543306f007c99181225700d3e5e13de3dd84a90f

See more details on using hashes here.

Provenance

The following attestation bundles were made for reg_normalizer-1.5.0-py3-none-any.whl:

Publisher: python-publish.yml on tochno-st/reg_normalizer

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page