Перейти к основному содержимому

Pandas: работа с таблицами КБК

Зачем и для кого

Инструкция для аналитиков и разработчиков, которые склеивают фактические выгрузки (исполнение, закупки, реестры) со справочником наименований КБК: нужно единообразно привести код к 20 знакам в виде строки, не потерять ведущие нули и отловить строки без пары в справочнике. Смысл кода и тип (доход / расход / источники финансирования дефицита) — в глоссарии КБК; разбор позиций по полям года — в расшифровке КБК.

Входные данные

  • Таблица операций с колонкой реквизита КБК (как в выгрузках бюджетного контура или ЕИС): часто смесь строк, целых и float (1.23e+19), иногда пробелы по краям и пустые значения. Код может быть записан с пробелами между группами цифр (как в печатных формах), например 386 0110 47 4 01 92072 611 12 — перед join такие значения приводят к сплошной строке из 20 цифр; если после удаления пробелов получается не 20 знаков, это сигнал проверить поле (не тот реквизит, обрезка колонки, лишняя группа).
  • Справочник КБК на тот же финансовый год и тот же тип кода, что и в факте — машиночитаемые наборы ищите на стороне открытых данных Минфина (загрузка — см. how-to по API Минфина); при работе только с порталом budget.gov.ru ориентируйтесь на поля паспортов наборов и доступ к API.
  • Зафиксируйте год наблюдения и дату выгрузки — перечень кодов и расшифровки меняются между годами (обзор классификации).

Инструменты

  • Python 3.10+ и pandas 2.x (pip install "pandas>=2.0").
  • По желанию — pyarrow для parquet; для больших CSV — dtype=str при чтении колонки с КБК.

Шаги

  1. Импортируйте факт и справочник в DataFrame; колонку с кодом из факта не превращайте заранее в float, если есть риск потери точности для длинных целых — читайте как строку (dtype, converters или engine="python" при необходимости).
  2. Нормализуйте значения в одну строковую колонку kbk: обрежьте края, удалите все пробельные символы внутри значения (\s+ → пусто), затем артефакт .0 у чисел из Excel; пустое и nan — в пропуск; оставшиеся строки из цифр длиной не больше 20 приведите к длине 20 через str.zfill(20); если после снятия пробелов цифр больше 20 или встречаются нецифровые символы — отложите строку в контроль ошибок.
  3. В справочнике приведите ключ к тому же виду (строка длины 20); убедитесь, что не смешиваете разные типы КБК в одном merge.
  4. Выполните merge(..., how="left", indicator=True) по kbk; посчитайте долю left_only, выгрузите их в контрольный файл.
  5. Проверьте суммы до и после join по ключу периода (год, месяц) — join не должен дублировать строки; при дубликатах в справочнике сначала дедуплицируйте справочник по kbk.

Воспроизводимый пример

Минимальный синтетический пример без сети (в проекте подставьте свои CSV и колонки):

import pandas as pd


def normalize_kbk(series: pd.Series, *, width: int = 20) -> pd.Series:
"""Края, все пробелы внутри кода, хвост .0; только цифры, длина <= width -> zfill."""
s = series.astype("string").str.strip()
s = s.replace({"": pd.NA})
s = s.str.replace(r"\s+", "", regex=True) # "386 0110 47 …" -> без пробелов
s = s.str.replace(r"\.0$", "", regex=True) # хвост Excel float в CSV
out = pd.Series(pd.NA, index=series.index, dtype="string")
m = s.notna()
digits = m & s.str.fullmatch(rf"[0-9]{{1,{width}}}", na=False)
too_long = m & s.str.fullmatch(rf"[0-9]{{{width + 1},}}", na=False)
if too_long.any():
raise ValueError(
"После удаления пробелов длина КБК > 20 цифр — проверьте поле и лишние группы: "
+ repr(s.loc[too_long].head(2).tolist())
)
bad = m & ~digits & ~too_long & (s.str.len() > 0)
if bad.any():
raise ValueError("Нецифровые символы после очистки: " + repr(s.loc[bad].head(2).tolist()))
out.loc[digits] = s.loc[digits].str.zfill(width)
return out


facts = pd.DataFrame(
{
"kbk_raw": [
"07104010201081000110",
" 07104010201081000110 ",
"07104010201081000110.0",
"071 04 01 0201 0810 0011 0", # те же 20 цифр, с пробелами между группами
],
"amount": [1_000_000.0, 2_000_000.0, 500_000.0, 250_000.0],
}
)

dict_kbk = pd.DataFrame(
{
"kbk": ["07104010201081000110"],
"kbk_name": ["Условное наименование строки (пример)"],
}
)

facts["kbk"] = normalize_kbk(facts["kbk_raw"])
merged = facts.merge(dict_kbk, on="kbk", how="left", indicator=True)
orphan_rows = merged.loc[merged["_merge"] == "left_only"]
print(merged.drop(columns=["_merge"]))
print("Строк без расшифровки:", len(orphan_rows))

Проверка результата

  • Длина ключа: все непустые kbkровно 20 цифр; значения вида 386 0110 47 … сначала дают 22 цифры без пробелов — это не нормализуемый КБК без отдельного разбора источника.
  • Сверка сумм: facts["amount"].sum() совпадает с merged["amount"].sum() при отсутствии дубликатов в справочнике.
  • Контроль left_only: доля нулевая или объяснена (новые коды года, другой тип КБК, срез по ведомству).
  • Смысл: расшифровка из справочника того же года и того же типа кода, что и в факте — иначе совпадения по строке будут вводящими в заблуждение.

Ограничения и типовые ошибки

  • Пробелы «для читаемости» внутри кода не должны попадать в ключ как есть — всегда снимайте \s+ до проверки длины; не путайте с разделителями тысяч в суммах в соседней колонке.
  • Числовой тип в Excel/CSV превращает КБК в научную запись или обрезает точность — храните и джойните как строку (заметки в глоссарии).
  • merge размножает строки, если в справочнике несколько строк на один kbk — сначала drop_duplicates(subset=["kbk"], keep="first") или осмысленная агрегация.
  • Смешение доходного и расходного кода в одной колонке без поля «тип» даёт ложные наименования — разделяйте контуры до join.
  • Разные годы без переходных таблиц дают «пропавшие» коды — ведите версию справочника в метаданных пайплайна.

Связанные страницы