Tool for normalizing and standardizing Russian region names
Project description
Region Normalizer
Region Normalizer — инструмент для нормализации и стандартизации наименований российских регионов, а также добавления нормализующих переменных. Он помогает распознавать регион даже в случаях, когда в названии встречаются опечатки, латинские буквы или другие особенности написания. Инструмент сопоставляет различные формы написания с эталонным справочником и позволяет извлекать дополнительные атрибуты, такие как коды ОКАТО, ISO, английские названия и многое другое.
Разные наименования одного и того же региона — частая проблема в реальных данных, например, на портале ЕМИСС встречается до 275 различных вариантов написания регионов. Особенно многообразны варианты у Тюменской и Архангельской областей — по 8 и 7 вариантов соответственно. В состав этих регионов входят автономные округа (ХМАО, ЯНАО, НАО), и часть ведомств отмечает, что данные приведены без автономных округов, но сокращения и формулировки используются самые разные.
Возможности
- Поиск и нормализация региона по произвольному названию (с учетом опечаток, сокращений, аббревиатур, смешения латиницы и кириллицы)
- Пакетная обработка больших таблиц с названиями регионов
- Гибкая настройка весов алгоритмов сопоставления
- Добавление дополнительных полей из эталонного справочника (ОКАТО, ISO, английское название и др.)
- Добавление нормализующих переменных (численность населения, индекс потребительских цен)
Установка
Установите пакет с помощью pip:
pip install reg_normalizer
Установка для разработки
Если вы хотите внести изменения в код:
- Клонируйте репозиторий:
git clone https://github.com/tochno-st/reg_normalizer.git
cd reg_normalizer
- Создайте виртуальное окружение с помощью
uv:
uv venv
- Активируйте виртуальное окружение:
На macOS/Linux:
source .venv/bin/activate
На Windows:
.venv\Scripts\activate
- Установите зависимости для разработки:
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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c73c026f0561ba0ed11af26ec09aad28d8290b83dbd576d64fa0d973261051a5
|
|
| MD5 |
32164a4554a121c939e30404298483cd
|
|
| BLAKE2b-256 |
79daf5faf5601ac0496c91bf28934e9509a7a211f6eceefb64b3ca7baa959d1c
|
Provenance
The following attestation bundles were made for reg_normalizer-1.5.0.tar.gz:
Publisher:
python-publish.yml on tochno-st/reg_normalizer
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
reg_normalizer-1.5.0.tar.gz -
Subject digest:
c73c026f0561ba0ed11af26ec09aad28d8290b83dbd576d64fa0d973261051a5 - Sigstore transparency entry: 1185804144
- Sigstore integration time:
-
Permalink:
tochno-st/reg_normalizer@015928f4385f296a8276f92c16f98433323a8054 -
Branch / Tag:
refs/tags/v1.5.0 - Owner: https://github.com/tochno-st
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@015928f4385f296a8276f92c16f98433323a8054 -
Trigger Event:
release
-
Statement type:
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
240243990a4b7ca2ac32963bf1ba76382006aafb7c0e88eb8fc0e808bdc7018c
|
|
| MD5 |
ccf60bc1c41b63c3d4aed98610b244ec
|
|
| BLAKE2b-256 |
243c7c5b119867d2f218b860543306f007c99181225700d3e5e13de3dd84a90f
|
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
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
reg_normalizer-1.5.0-py3-none-any.whl -
Subject digest:
240243990a4b7ca2ac32963bf1ba76382006aafb7c0e88eb8fc0e808bdc7018c - Sigstore transparency entry: 1185804157
- Sigstore integration time:
-
Permalink:
tochno-st/reg_normalizer@015928f4385f296a8276f92c16f98433323a8054 -
Branch / Tag:
refs/tags/v1.5.0 - Owner: https://github.com/tochno-st
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@015928f4385f296a8276f92c16f98433323a8054 -
Trigger Event:
release
-
Statement type: