2025, Nov 12 12:02

Сопоставление нескольких столбцов в pandas одним кодом

Как сопоставить значения из нескольких столбцов в pandas и собрать единую метку без циклов: Series.map, fillna и backfill, объединение словарей через ChainMap.

Сопоставление разнородных столбцов единой нормализованной метке — типичная задача подготовки данных в pandas. Когда ожидается, что в каждой строке лишь одно поле совпадёт со справочной таблицей, наша цель — перевести значения из нескольких столбцов и собрать результат в одном новом столбце без разветвлённой логики и многословного кода.

Постановка задачи

Предположим, у нас есть набор данных с двумя кандидатными столбцами для классификации и числовым полем. В каждой строке значение только одного из этих двух столбцов встречается в заранее заданном отображении. Нужно перевести то значение, которое найдено в справочнике, и поместить код в единый столбец.

import pandas as pd

frame = pd.DataFrame({
    'Event1': ['Music', 'Something else 1', 'Theatre', 'Comedy'],
    'Event2': ['Something else 2', 'Ballet', 'Something else 3', 'Something else 4'],
    'Cost': [10000, 5000, 15000, 2000]
})

code_map_a = {'Music': 'M', 'Cooking': 'C', 'Theatre': 'T', 'Comedy': 'C'}
code_map_b = {'Ballet': 'B', 'Swimming': 'S'}

Что здесь происходит и почему это непросто

Правила классификации зависят от столбца: значения в Event1 нужно расшифровывать по одной таблице соответствий, а в Event2 — по другой. Поскольку в строке ожидается совпадение лишь по одному столбцу, нам нужен аккуратный способ применить правильное сопоставление к каждому столбцу и взять первый успешный перевод. Реализация через построчные if/else быстро превращается в шум и легко упускает крайние случаи.

Краткое решение для фиксированного набора столбцов

Идиоматичный подход — переводить каждый столбец своим словарём, а затем с помощью fillna выбрать первое ненулевое (не NaN) совпадение. Так мы используем векторные операции и сохраняем декларативность преобразования.

frame['Event'] = frame['Event1'].map(code_map_a).fillna(
    frame['Event2'].map(code_map_b)
)

Новый столбец Event будет содержать M, B, T или C для соответствующих строк — из того первого столбца, где нашлось валидное сопоставление.

Масштабирование на большее число столбцов с отдельными словарями

Если «событийных» столбцов становится больше, схема «столбец ↔ свой словарь» и выбор первого успешного перевода остаются простыми. Идея — собрать небольшой реестр соответствий «столбец → словарь», применить нужный справочник к каждому столбцу, затем выполнить backfill по столбцам и взять первое ненулевое значение.

code_map_c = {'Comedy': 'C'}
column_maps = {'Event1': code_map_a, 'Event2': code_map_b, 'Event3': code_map_c}

frame['Event'] = (
    frame[list(column_maps)]
      .apply(lambda col: col.map(column_maps[col.name]))
      .bfill(axis=1)
      .iloc[:, 0]
)

Этот приём предполагает, что отображения привязаны к конкретным столбцам. Он пробует правильный словарь для каждого столбца и объединяет результаты слева направо.

Глобальное сопоставление для всех «событийных» столбцов

Если один и тот же справочник должен применяться ко всем столбцам, удобно объединить несколько словарей в один и выполнить единое преобразование. После сопоставления выберите первое ненулевое значение в строке. Это можно оформить двумя равнозначными способами.

from collections import ChainMap

frame['Event'] = (
    frame.filter(like='Event').stack()
         .map(dict(ChainMap(code_map_a, code_map_b, code_map_c)))
         .groupby(level=0).first()
)

Либо сделать векторный проход с последующим backfill:

from collections import ChainMap

frame['Event'] = (
    frame.filter(like='Event')
         .map(dict(ChainMap(code_map_a, code_map_b, code_map_c)).get)
         .bfill(axis=1)
         .iloc[:, 0]
)

Почему это важно

Такие паттерны позволяют держать логику, зависящую от столбцов, в декларативном виде и избегать построчных циклов и разовых условий. Они масштабируются от двух столбцов до многих без смены ментальной модели, остаются понятными на ревью и легко расширяются — достаточно добавить новую пару «столбец–словарь» или собрать общий справочник, когда бизнес‑правила сближаются.

Итоги

Когда в строке ожидается расшифровка только одного столбца, применяйте Series.map с соответствующим словарём на каждый столбец и используйте fillna, чтобы взять первое совпадение. Для больших схем соберите реестр «столбец → словарь» и сделайте backfill по преобразованным столбцам. Если правила едины для всех «событийных» полей, объедините словари и выбирайте первое найденное значение в строке. Так преобразование остаётся компактным, поддерживаемым и соответствует идиомам pandas.