Это тот случай, когда numba
может помочь в поиске эффективного решения.Это явный цикл for
, но JIT-скомпилированный для производительности.
from numba import njit
# convert to timedelta
time_cols = ['Time','Last','Next']
df[time_cols] = (df[time_cols] + ':00').apply(pd.to_timedelta)
# define loopy algorithm
@njit
def get_idx(times, comps, slots):
n = len(times)
res = np.empty(n)
for i in range(n):
mycomp = comps[i]
if mycomp != mycomp:
res[i] = np.nan
else:
for j in range(n, 0, -1):
if times[j-1] < mycomp:
res[i] = slots[j-1]
break
else:
res[i] = np.nan
return res
# extract timedeltas as seconds
arr = df[time_cols].apply(lambda x: x.dt.total_seconds()).values
# apply logic
df['min'] = get_idx(arr[:, 0], arr[:, 1], df['Slot'].values)
df['max'] = get_idx(arr[:, 0], arr[:, 2], df['Slot'].values)
Результат
print(df)
Slot Time Last Next min max
0 1 09:30:00 NaT 09:37:00 NaN 2.0
1 2 09:35:00 09:32:00 09:40:00 1.0 2.0
2 3 09:40:00 09:37:00 09:52:00 2.0 5.0
3 4 09:45:00 09:41:00 09:47:00 3.0 4.0
4 5 09:50:00 09:47:00 10:00:00 4.0 5.0
Сравнение производительности
Вы можете увидеть значительные улучшения производительности для больших фреймов данных:
def nix(df):
min_vals = [(df['Time'] < x)[::-1].idxmax()
if any(df['Time'] < x) else np.nan for x in df['Last']]
df['min'] = df.loc[min_vals,'Slot'].values
max_vals = [(df['Time'] < x)[::-1].idxmax()
if any(df['Time'] < x) else np.nan for x in df['Next']]
df.loc[:,'max'] = df.loc[max_vals,'Slot'].values
return df
def jpp(df):
arr = df[time_cols].apply(lambda x: x.dt.total_seconds()).values
df['min'] = get_idx(arr[:, 0], arr[:, 1], df['Slot'].values)
df['max'] = get_idx(arr[:, 0], arr[:, 2], df['Slot'].values)
return df
df = pd.concat([df]*1000, ignore_index=True)
%timeit nix(df.copy()) # 8.85 s per loop
%timeit jpp(df.copy()) # 5.02 ms per loop
Related: Эффективно вернуть индекс первого значения, удовлетворяющего условию в массиве .