2025, Oct 18 11:16
Как избежать KeyError при маппинге из словаря в pandas
Разбираем устойчивое сопоставление в pandas: как безопасно маппить словарь в DataFrame с помощью dict.get, избежать KeyError и сохранить чистую логику кода.
Когда вы дополняете входной DataFrame метаданными из статичной таблицы соответствия, одного отсутствующего ключа достаточно, чтобы сорвать весь запуск с KeyError. В этом материале показано, как сделать такое сопоставление устойчивым с помощью pandas и обычного словаря Python, сохранив при этом скорость и понятность логики.
Воспроизведение проблемы
Начните с справочника в CSV, который вы преобразуете в словарь, где ключ — имя сервера. Затем размечайте входящие строки, отображая столбец server в атрибуты вроде type, cost и location.
import pandas as pd
ref_df = pd.read_csv(r'C:\Location\format1.csv', sep=',')
ref_df['label'] = 'apache'
assoc_map = (
    ref_df
    .set_index('server')
    .T
    .to_dict('list')
)
print(assoc_map)
# {'ABC123': ['IBM', 1000, 'East Coast', 'apache'],
#  'ABC456': ['Dell', 800, 'West Coast', 'apache'],
#  'XYZ123': ['HP', 900, 'West Coast', 'apache']}
events_df = pd.read_csv(r'C:\Location\my_data.csv')
print(events_df)
#    server  busy       datetime
# 0  ABC123   24%  6/1/2024 0:02
# 1  ABC456   45%  6/1/2024 4:01
# 2  GHI100   95%  6/1/2024 9:10
# Прямое индексирование падает, если ключ отсутствует
events_df['type'] = events_df['server'].map(lambda s: assoc_map[s][0])
events_df['cost'] = events_df['server'].map(lambda s: assoc_map[s][1])
events_df['location'] = events_df['server'].map(lambda s: assoc_map[s][2])
# KeyError: 'GHI100'
Стоит только встретить сервер, которого нет в словаре, прямой доступ assoc_map[s] вызывает KeyError.
Почему это ломается
Корень проблемы — сам доступ к словарю. Падает срез assoc_map[s]. Индексация [0], [1], [2] по списку выполняется уже после того, как завершился поиск в словаре. Попытка обернуть не ту часть выражения в .get не помогает. Например, assoc_map.get([s][0], None) кажется защитой индекса списка, но [s][0] просто вычисляется обратно в s. В итоге выражение превращается в assoc_map.get(s, None), которое возвращает весь список атрибутов, если ключ есть, и None — если его нет. При прямом отображении это кладёт весь список в столбец или даёт None — не то, что требуется.
Если хочется проследить логику по шагам, замените lambda на небольшую функцию, чтобы добавить print и по отдельности посмотреть входы и выходы.
Решение
Применяйте .get именно к обращению к словарю и задавайте значение по умолчанию — список той же формы, что и реальное значение. Затем индексируйте этот список. Так индексация всегда будет валидной, даже при отсутствии ключа.
# Используем значение по умолчанию той же формы и индексируем после .get
# В качестве заполнителей возьмём пустые строки
events_df['type'] = events_df['server'].map(lambda s: assoc_map.get(s, [""]*3)[0])
events_df['cost'] = events_df['server'].map(lambda s: assoc_map.get(s, [""]*3)[1])
events_df['location'] = events_df['server'].map(lambda s: assoc_map.get(s, [""]*3)[2])
Чтобы не дублировать похожие строки, пройдитесь циклом по целевым столбцам и их позициям.
for idx, col in enumerate(['type', 'cost', 'location']):
    events_df[col] = events_df['server'].map(
        lambda s: assoc_map.get(s, [""]*3)[idx]
    )
Эквивалентный вариант — выполнить явную проверку наличия ключа и вернуть заполнитель, если его нет.
for idx, col in enumerate(['type', 'cost', 'location']):
    events_df[col] = events_df['server'].map(
        lambda s: assoc_map[s][idx] if s in assoc_map else ""
    )
Можно также обновить несколько столбцов за один проход.
events_df.update(
    (
        col,
        events_df['server'].map(
            lambda s: assoc_map[s][pos] if s in assoc_map else ""
        )
    )
    for pos, col in enumerate(['type', 'cost', 'location'])
)
Почему это важно
Данные из реальных источников редко идеально совпадают со справочниками. Защитное обращение к словарю гарантирует, что один неожиданный ключ не сломает весь этап разметки. Использование dict.get в точке доступа к словарю вместе со значением по умолчанию той же формы делает сопоставление предсказуемым и сохраняет стабильную схему DataFrame, даже когда верхние источники меняются.
Итоги
При сопоставлении атрибутов из справочника на базе pandas применяйте .get к обращению к словарю, а не к индексации списка. Давайте список нужной длины по умолчанию, индексируйте его и используйте заполнители, подходящие вашим последующим шагам. Если требуется внимательно проследить поведение, замените лямбда-выражения на небольшую функцию с печатью входов и промежуточных значений. Это небольшое изменение устраняет KeyError, сохраняя код ясным и в духе векторных операций.