pythonmatplotlibtime-seriesstatsmodels

Decomposing a signal with a custom trend


I wanted to decompose the following signal, a numpy array: enter image description here

I'm currently using the seasonal_decompose function from statsmodels.tsa.seasonal: https://www.statsmodels.org/dev/generated/statsmodels.tsa.seasonal.seasonal_decompose.html

My code:

myPeriod = 60 
decompose_result_mult = seasonal_decompose(mySignal,
model="additive",period=myPeriod)
trend = decompose_result_mult.trend
seasonal = decompose_result_mult.seasonal
residual = decompose_result_mult.resid
moyen=trend
tempo=residual+seasonal

And what I get when I plot the moyen (orange) and tempo (green) variables: enter image description here

The default value used for the trend is the mean value of the signal, and the tempo variable can then be negative. Is it possible to achieve the same thing but by taking the "minimal value" instead of the "mean value" of the function? Which would lead to the tempo variable being only positive. To illustrate what I mean it would be to use something like the following red line as the "trend" data, and get the corresponding tempo data: enter image description here

How can I achieve this (I did not find how to change the way seasonal_decompose works nor how to achieve this with other functions)?


Solution

  • You can use the "rolling ball" algorithm, as implemented in, e.g., scikit-image (see here). An example (with some fake data that looks a bit like yours) would be:

    import numpy as np
    from matplotlib import pyplot as plt
    from scipy.signal import butter, lfilter
    
    from skimage import restoration
    
    # some fake data (low pass filtered to look a bit more like the data in the original question)
    t = np.linspace(0, 10, 500)
    x = np.random.randn(len(t))
    
    b, a = butter(3, 0.25, btype="low")
    
    xf = lfilter(b, a, x)
    
    xf -= xf[0]
    xf += 3 * t
    
    fig, ax = plt.subplots()
    
    ax.plot(t, xf, label="data")
    
    # get "background" using rolling ball algorithm
    background = restoration.rolling_ball(xf, radius=50)
    
    ax.plot(t, background, label="background")
    ax.plot(t, xf - background, label="residual")
    
    ax.legend()
    

    enter image description here

    You can play about with the radius value as required for your problem.