2025, Nov 20 12:02
Объединение в pandas с временным лагом по ID: Funding N-2 для Output N
Показываем, как в pandas объединять таблицы по ID с учётом смещённого года: брать Funding за N-2 для Output года N. Код с apply и точным выбором по Year.
Когда метрики хранятся в отдельных таблицах и зависят от временных сдвигов, простого объединения недостаточно. Типичный сценарий такой: каждая строка итогового набора привязана к году N для Output, но ей требуются Funding за N-2 и Staff за N-1. Задача — динамически выбрать верную ячейку из других таблиц по ID и смещённому году, особенно если в каждой исходной таблице на один ID приходится несколько лет.
Пример данных
Ниже показан набор данных, иллюстрирующий ситуацию с Output и Funding. В каждой таблице по одному ID хранится несколько лет, а мы хотим собрать объединённую таблицу, где Output остаётся в году N, а Funding подтягивается из N-2.
import pandas as pd
data_out = {
'Name': ['ABC', 'DEF'],
'ID': [1, 2],
'Year': [2023, 2022],
'Output': [25, 51]
}
out_tbl = pd.DataFrame(data=data_out)
data_fund = {
'Name': ['ABC', 'DEF', 'ABC'],
'ID': [1, 2, 1],
'Year': [2021, 2020, 2023],
'Funding': [500, 1000, 17]
}
fund_tbl = pd.DataFrame(data=data_fund)
# Базовый объединённый фрейм, привязанный к году из Output
combined_base = out_tbl.rename(columns={'Year': 'Year_Output'})
combined_base['Year_Funding'] = combined_base['Year_Output'] - 2
Что здесь на самом деле происходит
Здесь дело не в простом объединении по общим ключам — мы делаем поиск по двум ключам, один из которых вычисляемый. Для каждой строки нужная ячейка Funding — та, где совпадает ID и Year в таблице финансирования равен Year_Output этой строки минус два. Поскольку в таблицах на один ID приходится несколько лет, наивный merge не учтёт условие «N-2», пока мы явно не вычислим смещённый год и не выберем единственную соответствующую запись.
Решение: построчный поиск с apply
Проще всего выразить эту логику построчным поиском: для каждой строки получать точное значение Funding по ID и Year_Funding. Код ниже сохраняет поведение программы и заполняет столбец Funding корректным значением из таблицы финансирования.
def pick_funding(rec):
return fund_tbl.loc[
(fund_tbl['ID'] == rec['ID']) & (fund_tbl['Year'] == rec['Year_Funding']),
'Funding'
].squeeze()
combined_base['Funding'] = combined_base.apply(pick_funding, axis=1)
# Необязательно: выбрать и упорядочить нужные столбцы для вывода
final_view = combined_base[[
'Name', 'ID', 'Year_Output', 'Output', 'Year_Funding', 'Funding'
]]
Подход срабатывает потому, что каждая строка сама вычисляет свой Year_Funding и затем вытягивает ровно одно значение из таблицы финансирования. Вызов squeeze сворачивает выборку из одного элемента в скаляр — так удобнее присваивать его в столбец.
Почему это важно понимать
Связи с временным лагом между несколькими таблицами встречаются во многих аналитических процессах. Когда сдвиги у метрик различаются, перед объединением нужен явный мэппинг между опорным годом и годом поиска. Осознание того, что вы работаете с вычисляемым ключом соединения, защищает от случайных объединений «многие-ко-многим» и гарантирует, что для каждой строки подтянется именно та ячейка, которая нужна.
Итоги
Зафиксируйте объединённый фрейм на годе из Output, вычислите смещённый год для Funding, а затем получите верное значение по совпадению ID и рассчитанного года. Ту же схему легко распространить на другие метрики с лагом, если их исходные таблицы имеют ту же структуру ID и Year. Так логика остаётся прозрачной, а соединения — точными, даже когда у каждой сущности в источниках хранится по нескольку лет.