этот метод использует реляционную алгебру для ускорения операции, а не векторизации
с использованием pandas.merge_asof
, мы можем объединить два кадра данных, выбирая последнюю строку из 2-го кадра, где выполняется сравнениеполе меньше, чем поле сравнения 1-го кадра.
Создайте столбец с именем until
.Это временный столбец, который мы отбросим позже
df['until'] = df.created_dt - pd.Timedelta(days=5)
Объединить df с самим собой, используя & then_dt, то есть последнюю строку, такую, что created_dt
right-df предшествует until
и * left-df number_id
одинаково для обоих dfs
merged = pd.merge_asof(df, df, left_on='until', right_on='created_dt', by='number_id', suffixes=('', '_y'), allow_exact_matches=False)
Установите status_y
в np.nan
, где created_dt_y
до created_dt
- 20 days
merged.loc[~(merged.created_dt_y >= merged.created_dt - pd.Timedelta(days=20)), 'status_y'] = np.nan
Здесь мынеобходимо отменить условие после , поскольку merged.created_dt_y
содержит нули, которые не соответствуют фильтру.
Наконец, выберите нужные столбцы:
merged[['number_id', 'created_dt', 'status', 'status_y']]
# outputs:
number_id created_dt status status_y
0 BBB 2018-05-18 20:28:51.388001 u NaN
1 BBB 2018-05-19 12:28:51.388001 u NaN
2 CCC 2018-05-19 23:28:51.388001 u NaN
3 CCC 2018-05-20 22:28:51.388001 a NaN
4 CCC 2018-05-21 05:28:51.388001 u NaN
5 BBB 2018-05-21 12:28:51.388001 r NaN
6 AAA 2018-05-24 21:28:51.388001 a NaN
7 CCC 2018-05-30 16:28:51.388001 a u
8 AAA 2018-05-31 23:28:51.388001 r a
9 CCC 2018-06-01 00:28:51.388001 r u
10 BBB 2018-06-01 11:28:51.388001 r r
11 BBB 2018-06-01 19:28:51.388001 r r
12 AAA 2018-06-03 14:28:51.388001 a a
13 CCC 2018-06-04 15:28:51.388001 u u
14 AAA 2018-06-05 06:28:51.388001 u a
15 AAA 2018-06-05 20:28:51.388001 r a
16 AAA 2018-06-06 04:28:51.388001 a r
17 BBB 2018-06-06 18:28:51.388001 r r
18 AAA 2018-06-07 15:28:51.388001 r r
19 BBB 2018-06-08 09:28:51.388001 r r
20 BBB 2018-06-08 21:28:51.388001 u r
21 BBB 2018-06-09 04:28:51.388001 a r
22 AAA 2018-06-09 16:28:51.388001 r a
23 AAA 2018-06-12 07:28:51.388001 r a
24 BBB 2018-06-13 03:28:51.388001 u r
25 AAA 2018-06-14 08:28:51.388001 a r
26 CCC 2018-06-14 14:28:51.388001 r u
27 CCC 2018-06-15 17:28:51.388001 u u
28 BBB 2018-06-16 02:28:51.388001 a a
29 AAA 2018-06-16 08:28:51.388001 r r
30 AAA 2018-06-17 02:28:51.388001 a r
Результаты тестов:
Мы видим примерно 7-кратное улучшение производительности даже при использовании небольшого 30-строчного DataFrame
%timeit slow(df)
# outputs:
41 ms ± 1.11 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit fast(df)
# outputs:
5.69 ms ± 34 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
используемого кода:
def slow(df):
idx = 0
last_status_in_range = np.array([None] * len(df), dtype=str)
for row in df.itertuples():
created_dt = row.created_dt
created_id = row.number_id
last_status_in_range[idx] = get_last_status_in_range(df, created_dt, created_id)
idx += 1
return df.assign(prev_status=last_status_in_range)
def fast(df):
d = df.assign(until = df.created_dt - pd.Timedelta(days=5))
merged = pd.merge_asof(
d, d, left_on='until', right_on='created_dt',
by='number_id', suffixes=('', '_y'),
allow_exact_matches=False
)
merged.loc[
~(merged.created_dt_y >= merged.created_dt - pd.Timedelta(days=20)),
'status_y'
] = np.nan
return merged[['number_id', 'created_dt', 'status', 'status_y']]