I am trying to implement a dynamic moving average and other dynamic moving functions like std
and max
in Pandas. The difference between the 'normal' and the dynamic moving average is the behaviour at the edges. This mainly improves two things:
- the returned data is not shifted as with the 'normal' moving average
- the returned data covers the whole range of the input data. Using the 'normal' moving average the returned data range is cropped depending on the window size of the moving average.
So to get a dynamic moving average there are multiple methods like reflection, repetition of first/last value, ... For me the reflection method is enough. This and the other behaviours are implemented in the filters from scripy.ndimage.filters
, e.g. the uniform_filter1d
. I've looked through Pandas documentation but on rolling
I could not find any arguments which changes the behaviour at the edges...
Adding this myself seems doable but I am asking the question to see if there is already an implementation in Pandas which I've missed.
So my question can be reduced to that: Is there a way to use Pandas rolling
dynamically like I described?
'Normal' moving average:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from scipy.ndimage import filters
def moving_avg(series: pd.Series, window: int) -> pd.Series:
return series.rolling(window=window).mean()
t = np.linspace(0, 100, 1000)
y = np.sin(2 * np.pi * 0.03 * t)
filtered_y = filters.uniform_filter1d(y, 200)
pd_y = pd.Series(y, name="Pandas Moving Average")
pd_y = moving_avg(pd_y, 200) # Using a large window on purpose to show the shift!
plt.plot(y, label="Raw Data")
plt.plot(filtered_y, label="uniform_filter1d")
pd_y.plot()
plt.legend()
plt.show()
Result
So is there something to solve this problem?
CodePudding user response:
I don't think there is what you want implemented. One easy way is to use concat
to add the edges values to create the reflect effect. In a function, it could be something like
def rolling_reflect_center(s, window):
nb_ref = window // 2
rolling_ = pd.concat(
[pd.Series(s.iloc[:nb_ref].values)[::-1],#reflect on the left
s,
pd.Series(s.iloc[-nb_ref 1:].values[::-1]) #reflect on the right
]).rolling(window ,center=True)
return rolling_
then you do
filtered_y = filters.uniform_filter1d(y, 200)
pd_y = pd.Series(y, name="Pandas Moving Average")
pd_y = rolling_reflect_center( pd_y, window=200).mean()
print(np.allclose(pd_y.dropna().to_numpy(),filtered_y))
#True
the plot works well too because it drops the nan
automatically