Fit a time series in python with a mean value as boundary condition

Question:

I have the following boundary conditions for a time series in python.
The notation I use here is t_x, where x describe the time in milliseconds (this is not my code, I just thought this notation is good to explain my issue).

t_0 = 0 
t_440 = -1.6
t_830 = 0
mean_value = -0.6

I want to create a list that contains 83 values (so the spacing is 10ms for each value).
The list should descibe a "curve" that starts at zero, has the minimum value of -1.6 at 440ms (so 44 in the list), ends with 0 at 880ms (so 83 in the list) and the overall mean value of the list should be -0.6.

I absolutely could not come up with an idea how to "fit" the boundaries to create such a list.

I would really appreciate help.

Asked By: MonkeyDLuffy

||

Answers:

It is a quick and dirty approach, but it works:

X = list(range(0, 830 +1, 10))
Y = [0.0 for x in X]
Y[44] = -1.6
b = 12.3486
for x in range(44):
    Y[x] = -1.6*(b*x+x**2)/(b*44+44**2)  
for x in range(83, 44, -1):
    Y[x] = -1.6*(b*(83-x)+(83-x)**2)/(b*38+38**2)  
print(f'{sum(Y)/len(Y)=:8.6f}, {Y[0]=}, {Y[44]=}, {Y[83]=}')

from matplotlib import pyplot as plt
plt.plot(X,Y)
plt.show()

With the code giving following output:

sum(Y)/len(Y)=-0.600000, Y[0]=-0.0, Y[44]=-1.6, Y[83]=-0.0

And showing following diagram:

XYplot-1

The first step in coming up with the above approach was to create a linear sloping ‘curve’ from the minimum to the zeroes. I turned out that linear approach gives here too large mean Y value what means that the ‘curve’ must have a sharp peak at its minimum and need to be approached with a polynomial. To make things simple I decided to use quadratic polynomial and approach the minimum from left and right side separately as the curve isn’t symmetric. The b-value was found by trial and error and its precision can be increased manually or by writing a small function finding it in an iterative way.

Update providing a generic solution as requested in a comment

The code below provides a

meanYboundaryXY(lbc = [(0,0), (440,-1.6), (830,0), -0.6], shape='saw')

function returning the X and Y lists of the time series data calculated from the passed parameter with the boundary values:

def meanYboundaryXY(lbc = [(0,0), (440,-1.6), (830,0), -0.6]): 
    lbcXY = lbc[0:3] ; meanY_boundary = lbc[3]
    minX = min(x for x,y in lbcXY)
    maxX = max(x for x,y in lbcXY)
    minY = lbc[1][1]
    step = 10
    X = list(range(minX, maxX + 1, step))
    lenX = len(X) 
    Y = [None for x in X]
    sumY = 0
    for x, y in lbcXY:
        Y[x//step] = y
        sumY += y
    target_sumY = meanY_boundary*lenX

    if shape == 'rect': 
        subY = (target_sumY-sumY)/(lenX-3)
        for i, y in enumerate(Y):
            if y is None:
                Y[i] = subY
    elif shape == 'saw':
        peakNextY = 2*(target_sumY-sumY)/(lenX-1)
        iYleft  = lbc[1][0]//step-1
        iYrght  = iYleft+2
        iYstart = lbc[0][0] // step
        iYend   = lbc[2][0] // step
        for i in range(iYstart, iYleft+1, 1):
            Y[i] = peakNextY * i / iYleft  
        for i in range(iYend, iYrght-1, -1):
            Y[i] = peakNextY * (iYend-i)/(iYend-iYrght)  
    else: 
        raise ValueError( str(f'meanYboundaryXY() EXIT, {shape=} not in ["saw","rect"]') )

    return (X, Y)

X, Y = meanYboundaryXY()

print(f'{sum(Y)/len(Y)=:8.6f}, {Y[0]=}, {Y[44]=}, {Y[83]=}')

from matplotlib import pyplot as plt
plt.plot(X,Y)
plt.show()

The code outputs:

sum(Y)/len(Y)=-0.600000, Y[0]=0, Y[44]=-1.6, Y[83]=0

and creates following two diagrams for shape='rect' and shape='saw':

XYplot-2

XYplot-3

Answered By: Claudio

As an old geek, i try to solve the question with a simple algorithm.
First calculate points as two symmetric lines from 0 to 44 and 44 to 89 (orange on the graph).
Calculate sum except middle point and its ratio with sum of points when mean is -0.6, except middle point.
Apply ratio to previous points except middle point. (blue curve on the graph)
Obtain curve which was called "saw" by Claudio.
For my own, i think quadratic interpolation of Claudio is a better curve, but needs trial and error loops.

import matplotlib

# define goals
nbPoints = 89
msPerPoint = 10
midPoint = nbPoints//2
valueMidPoint = -1.6
meanGoal = -0.6


def createSerieLinear():
    # two lines 0 up to 44, 44 down to 88 (89 values centered on 44)
    serie=[0 for i in range(0,nbPoints)]
    interval =valueMidPoint/midPoint
    for i in range(0,midPoint+1):
        serie[i]=i*interval
        serie[nbPoints-1-i]=i*interval
    return serie

# keep an original to plot
orange = createSerieLinear()
# work on a base
base = createSerieLinear()

# total except midPoint
totalBase = (sum(base)-valueMidPoint) 
#total goal except 44
totalGoal = meanGoal*nbPoints - valueMidPoint 

# apply ratio to reduce 
reduceRatio = totalGoal/totalBase
for i in range(0,midPoint):
    base[i] *= reduceRatio
    base[nbPoints-1-i] *= reduceRatio 
 

# verify 
meanBase = sum(base)/nbPoints 
print("new mean:",meanBase)


# draw 
from matplotlib import pyplot as plt
X =[i*msPerPoint for i in range(0,nbPoints)]
plt.plot(X,base)
plt.plot(X,orange)
plt.show()

new mean: -0.5999999999999998

enter image description here

Hope you enjoy simple things 🙂

Answered By: pirela
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.