What's the most efficient way to calculate a rolling (aka moving window) trimmed mean with Python?
For example, for a data set of 50K rows and a window size of 50, for each row I need to take the last 50 rows, remove the top and bottom 3 values (5% of the window size, rounded up), and get the average of the remaining 44 values.
Currently for each row I'm slicing to get the window, sorting the window and then slicing to trim it. It works, slowly, but there has to be a more efficient way.
Example
[10,12,8,13,7,18,19,9,15,14] # data used for example, in real its a 50k lines df
for a window size of 5. For each row we look at the last 5 rows, sort them and discard 1 top and 1 bottom row (5% of 5 = 0.25, rounded up to 1). Then we average the remaining middle rows.
Code to generate this example set as a DataFrame
pd.DataFrame({
'value': [10, 12, 8, 13, 7, 18, 19, 9, 15, 14],
'window_of_last_5_values': [
np.NaN, np.NaN, np.NaN, np.NaN, '10,12,8,13,7', '12,8,13,7,18',
'8,13,7,18,19', '13,7,18,19,9', '7,18,19,9,15', '18,19,9,15,14'
],
'values that are counting for average': [
np.NaN, np.NaN, np.NaN, np.NaN, '10,12,8', '12,8,13', '8,13,18',
'13,18,9', '18,9,15', '18,15,14'
],
'result': [
np.NaN, np.NaN, np.NaN, np.NaN, 10.0, 11.0, 13.0, 13.333333333333334,
14.0, 15.666666666666666
]
})
Example code for the naive implementation
window_size = 5
outliers_to_remove = 1
for index in range(window_size - 1, len(df)):
current_window = df.iloc[index - window_size + 1:index + 1]
trimmed_mean = current_window.sort_values('value')[
outliers_to_remove:window_size - outliers_to_remove]['value'].mean()
# save the result and the window content somewhere
A note about DataFrame vs list vs NumPy array
Just by moving the data from a DataFrame to a list, I'm getting a 3.5x speed boost with the same algorithm. Interestingly, using a NumPy array also gives almost the same speed boost. Still, there must be a better way to implement this and achieve an orders-of-magnitude boost.