Sobes.tech
Back to questions
Middle — Senior
8

Какие проблемы возникают при join'ах фич с историей?

Companies where asked
ОстровокОстровок
ГазпромбанкГазпромбанк

Answer from AI

sobes.tech AI

Что хотят услышать интервьюеры:

При join'ах фич с историей чаще всего возникают утечки таргета, ошибки во времени и дубли строк. Важно понимать, что данные для обучения должны соответствовать состоянию объекта на момент предсказания, а не после него. Также нужно уметь контролировать гранулярность, лаги и пропуски в истории.

Определение:

Join фич с историей — это объединение текущего состояния объекта с его прошлым поведением, событиями или агрегатами за предшествующий период. Основная сложность в том, что при неправильном соединении легко добавить в признаки информацию из будущего или исказить число наблюдений.

Типовые проблемы:

  • leakage, когда в признаки попадает информация после момента прогноза;
  • некорректное окно по времени, если history joining делается без учета timestamp;
  • many-to-many join, который размножает строки;
  • несоответствие гранулярности, например, дневные фичи присоединяют к событию на уровне транзакции;
  • пропуски или неоднозначность при отсутствии данных на нужную дату.

Пример использования:

Например, нужно предсказать отток клиента на дату 2024-06-01. В признаки можно добавить число покупок за последние 30 дней, но нельзя использовать покупки, совершенные после 2024-06-01.

import pandas as pd

events = pd.DataFrame({
    "user_id": [1, 1, 1, 2, 2],
    "event_time": pd.to_datetime([
        "2024-05-01", "2024-05-20", "2024-06-05",
        "2024-05-10", "2024-06-03"
    ]),
    "amount": [100, 50, 200, 80, 120]
})

snapshot = pd.DataFrame({
    "user_id": [1, 2],
    "snapshot_time": pd.to_datetime(["2024-06-01", "2024-06-01"])
})

# Корректно: берем только события строго до snapshot_time
joined = snapshot.merge(events, on="user_id", how="left")
joined = joined[joined["event_time"] < joined["snapshot_time"]]

features = (
    joined.groupby("user_id", as_index=False)
          .agg(purchases_30d=("amount", "sum"))
)

print(features)

Пояснение кода:

Код показывает безопасный паттерн: сначала задается момент среза (snapshot_time), затем из истории отбираются только события раньше этого момента. После этого строится агрегат по прошлым событиям, который можно использовать как фичу. Если убрать фильтр по времени, в признаки попадет покупка от 2024-06-05, хотя на момент 2024-06-01 ее еще не было.

Ключевые моменты:

  • Всегда фиксировать time cutoff: фичи должны быть доступны на момент предсказания.
  • Проверять, что join не создает дубли и не меняет число строк неожиданно.
  • Следить за granularity: уровень агрегации истории должен совпадать с уровнем объекта.
  • Использовать временные фильтры и лаги, а не просто join по ключу.
  • Отдельно валидировать отсутствие leakage на train/validation split.
  • Для event-based данных часто безопаснее сначала строить агрегаты по окнам, а потом присоединять их к snapshot-таблице.