Home > Enterprise >  Dynamic rolling function in pandas
Dynamic rolling function in pandas

Time:09-23

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

Difference in rolling behaviour

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

  • Related