I got a question from a friend: how do I fit a function, using least-squares, to analyze the residuals. To answer that I cook this notebook up and made a post out of it ;-)
For the example I create a fake tidal signal, that we want fit and remove, added some noise and an offset (mean current).
import numpy as np
def fake_tide(t, M2amp, M2phase):
"""
Generate a minimally realistic-looking fake semidiurnal tide.
t is time in hours
phases are in radians
"""
out = M2amp * np.sin(2 * np.pi * t / 12.42 - M2phase)
return out
N = 500
res = 5
noise = np.random.randn(N)
t = np.arange(N)
amp, phase = 2, 0
u = fake_tide(t, amp, phase) + res + noise
Here is the signal we created.
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(11, 2.75))
ax.plot(t, u, 'darkgreen', label='Signal')
leg = ax.legend()
Now lets try to specify a first guess for the non-linear fitting. Note that the phase is a little off on purpose.
mean = np.mean(u)
std = amp * np.std(u) / (amp**0.5)
# First guess.
first_guess = fake_tide(t, std, 0.25) + mean
fig, ax = plt.subplots(figsize=(11, 2.75))
ax.plot(t, first_guess, 'darkorange', label='First guess')
ax.plot(t, u, 'darkgreen', label='Signal')
leg = ax.legend()
Nice first guess, we did not had to do such a good jib ;-), but now it is time
to feed that into SciPy's optimize.leastsq
to get a least squares fit of the
parameters.
from scipy.optimize import leastsq
optimize_func = lambda x: x[0] * np.sin(2 * np.pi * t / 12.42 + x[1]) + x[2] - u
est_std, est_phase, est_mean = leastsq(optimize_func, [std, phase, mean])[0]
Let's check if the parameters we estimated are close to reality.
print('Orignal: std {:.2f}, phase {:.2f}, and res {:.2f}'.format(std, phase, res))
print('Estimated: std {:.2f}, phase {:.2f}, and res {:.2f}'.format(est_std, est_phase, est_mean))
Awesome! Close enough =)
Now lets re-create the signal using the estimated parameters and plot everything.
fit = fake_tide(t, est_std, est_phase) + est_mean
fig, ax = plt.subplots(figsize=(11, 2.75))
ax.plot(t, fit, 'darkorange', label='Fitted')
ax.plot(t, first_guess, 'crimson', label='First guess')
ax.plot(t, u, 'darkgreen', label='Signal')
ax.plot(t, u-fit, 'crimson', label='Residue')
leg = ax.legend()
_ = ax.set_xlim(100, 150)
HTML(html)