Мне нужно рассчитать некоторую метрику, используя скользящее окно по фрейму данных. Если бы для метрики требовался только 1 столбец, я бы использовал rolling. Но как-то не работает с 2+ колонками.
Ниже показано, как я рассчитываю метрику, используя обычный цикл.
def mean_squared_error(aa, bb):
return np.sum((aa - bb) ** 2) / len(aa)
def rolling_metric(df_, col_a, col_b, window, metric_fn):
result = []
for i, id_ in enumerate(df_.index):
if i < (df_.shape[0] - window + 1):
slice_idx = df_.index[i: i+window-1]
slice_a, slice_b = df_.loc[slice_idx, col_a], df_.loc[slice_idx, col_b]
result.append(metric_fn(slice_a, slice_b))
else:
result.append(None)
return pd.Series(data = result, index = df_.index)
df = pd.DataFrame(data=(np.random.rand(1000, 2)*10).round(2), columns = ['y_true', 'y_pred'] )
%time df2 = rolling_metric(df, 'y_true', 'y_pred', window=7, metric_fn=mean_squared_error)
Это занимает около секунды всего для 1000 строк.
Пожалуйста, предложите более быстрый векторизованный способ вычисления такой метрики по скользящему окну.






Вы можете заранее рассчитать квадрат ошибки, а затем использовать .Rolling.mean():
df['sq_error'] = (df['y_true'] - df['y_pred'])**2
%time df['sq_error'].rolling(6).mean().dropna()
Обратите внимание, что в вашем примере фактический размер окна равен 6 (напечатайте длину фрагмента), поэтому я установил его на 6 в своем фрагменте.
Можно даже так написать:
%time df['y_true'].subtract(df['y_pred']).pow(2).rolling(6).mean().dropna()
Если вы не можете уменьшить его до одного столбца, начиная с pandas 1.3.0 вы можете использовать параметр method='table, чтобы применить функцию ко всему DataFrame. Однако для этого есть следующие требования:
numba. Итак, вам нужно установить engine='numba' в apply и установить его.raw=True в apply: это означает, что в вашей функции вы будете работать с массивами numpy вместо DataFrame. Это следствие предыдущего пункта.Таким образом, ваши вычисления могут быть примерно такими:
WIN_LEN = 6
def mean_sq_err_table(arr, min_window=WIN_LEN):
if len(arr) < min_window:
return np.nan
else:
return np.mean((arr[:, 0] - arr[:, 1])**2)
df.rolling(WIN_LEN, method='table').apply(mean_sq_err_table, engine='numba', raw=True).dropna()
Поскольку он использует numba, это также относительно быстро.
@PoeDator Понятно, пожалуйста, проверьте мою правку!
Спасибо, @user2246849! Это близко к тому, что я ожидал. Пользоваться пока не очень удобно — надеюсь, что в ближайших релизах панд этот функционал улучшится. Что касается скорости - я заметил одинаковое время работы для 1000 строк (около 1 секунды), но с numba оно остается почти одинаковым для 10k и 100k строк, что приятно.
это работает, но только для одного случая из моего примера, сводя проблему к случаю с одним столбцом. Однако мне нужно иметь возможность использовать несколько других показателей. Есть ли способ сделать прокрутку + применить к нескольким столбцам?