Условие
В events(user_id, ts, event) для каждой строки добавить колонку seconds_since_last — сколько секунд прошло с прошлого события того же пользователя. Для первого события у юзера — NaN.
Решение
Подход
groupby(user).ts.diff() даёт разницу с предыдущей строкой внутри группы. Перевести в секунды.
import pandas as pd
events = events.sort_values(['user_id', 'ts']).copy()
events['seconds_since_last'] = (
events.groupby('user_id')['ts'].diff().dt.total_seconds()
)Если нужна «секунды до предыдущего события того же типа»
events['seconds_since_prev_same'] = (
events
.groupby(['user_id', 'event'])['ts']
.diff()
.dt.total_seconds()
)Время до следующего события
events['seconds_to_next'] = (
-events.groupby('user_id')['ts'].diff(-1).dt.total_seconds()
)Расширение: время с последнего покупательского события
def time_since_last_purchase(group):
last_purchase = group['ts'].where(group['event'] == 'purchase').ffill()
return (group['ts'] - last_purchase).dt.total_seconds()
events['secs_since_purchase'] = (
events.groupby('user_id', group_keys=False).apply(time_since_last_purchase)
)where(cond).ffill() — мощный паттерн: «возьми значение, если условие, иначе протяни предыдущее».
Подводные камни
- Без
sort_valuesdiffпосчитает мусор; гарантируйте сортировку поtsвнутриuser_id. dt.total_seconds()обязательно — иначе получитеTimedelta, а не число.diff(-1)даёт «текущая − следующая», для «времени до следующего» меняйте знак.where(cond).ffill()для разреженных событий — но первая часть до первого события того типа будет NaN.- На больших данных
groupby.applyмедленный — переписать на чистый pandas (where+groupby.ffill).
Эталонный ответ
df.groupby('user_id')['ts'].diff().dt.total_seconds(). Для «с прошлого события типа X» — паттерн ts.where(event==X).ffill().