我试图表明经济遵循相对正弦的增长模式。我正在构建一个 Python 模拟,以表明即使我们让一定程度的随机性占据主导地位,我们仍然可以产生相对正弦的东西。
我对生成的数据感到满意,但现在我想找到某种方法来获得与数据非常匹配的正弦图。我知道你可以进行多项式拟合,但是你可以进行正弦拟合吗?
这是一个无参数拟合函数
fit_sin()
,不需要手动猜测频率:
import numpy, scipy.optimize
def fit_sin(tt, yy):
'''Fit sin to the input time sequence, and return fitting parameters "amp", "omega", "phase", "offset", "freq", "period" and "fitfunc"'''
tt = numpy.array(tt)
yy = numpy.array(yy)
ff = numpy.fft.fftfreq(len(tt), (tt[1]-tt[0])) # assume uniform spacing
Fyy = abs(numpy.fft.fft(yy))
guess_freq = abs(ff[numpy.argmax(Fyy[1:])+1]) # excluding the zero frequency "peak", which is related to offset
guess_amp = numpy.std(yy) * 2.**0.5
guess_offset = numpy.mean(yy)
guess = numpy.array([guess_amp, 2.*numpy.pi*guess_freq, 0., guess_offset])
def sinfunc(t, A, w, p, c): return A * numpy.sin(w*t + p) + c
popt, pcov = scipy.optimize.curve_fit(sinfunc, tt, yy, p0=guess)
A, w, p, c = popt
f = w/(2.*numpy.pi)
fitfunc = lambda t: A * numpy.sin(w*t + p) + c
return {"amp": A, "omega": w, "phase": p, "offset": c, "freq": f, "period": 1./f, "fitfunc": fitfunc, "maxcov": numpy.max(pcov), "rawres": (guess,popt,pcov)}
初始频率猜测由使用 FFT 的频域峰值频率给出。假设只有一个主频率(零频率峰值除外),拟合结果几乎是完美的。
import pylab as plt
N, amp, omega, phase, offset, noise = 500, 1., 2., .5, 4., 3
#N, amp, omega, phase, offset, noise = 50, 1., .4, .5, 4., .2
#N, amp, omega, phase, offset, noise = 200, 1., 20, .5, 4., 1
tt = numpy.linspace(0, 10, N)
tt2 = numpy.linspace(0, 10, 10*N)
yy = amp*numpy.sin(omega*tt + phase) + offset
yynoise = yy + noise*(numpy.random.random(len(tt))-0.5)
res = fit_sin(tt, yynoise)
print( "Amplitude=%(amp)s, Angular freq.=%(omega)s, phase=%(phase)s, offset=%(offset)s, Max. Cov.=%(maxcov)s" % res )
plt.plot(tt, yy, "-k", label="y", linewidth=2)
plt.plot(tt, yynoise, "ok", label="y with noise")
plt.plot(tt2, res["fitfunc"](tt2), "r-", label="y fit curve", linewidth=2)
plt.legend(loc="best")
plt.show()
即使噪音很大,结果也很好:
振幅=1.00660540618,角频率=2.03370472482,相位=0.360276844224,偏移=3.95747467506,最大。变异系数=0.0122923578658
您可以使用 scipy 中的最小二乘优化函数将任意函数拟合到另一个函数。在拟合 sin 函数的情况下,要拟合的 3 个参数是偏移量 ('a')、幅度 ('b') 和相位 ('c')。
只要您提供对参数的合理初步猜测,优化应该能够很好地收敛。幸运的是,对于正弦函数,其中 2 个的初步估计很容易:可以通过取数据的平均值和幅度来估计偏移通过 RMS(3*标准差/sqrt(2))。
注意:作为后来的编辑,还添加了频率拟合。这效果不太好(可能会导致配合度极差)。因此,请酌情使用,我的建议是不要使用频率拟合,除非频率误差小于百分之几。
这导致以下代码:
import numpy as np
from scipy.optimize import leastsq
import pylab as plt
N = 1000 # number of data points
t = np.linspace(0, 4*np.pi, N)
f = 1.15247 # Optional!! Advised not to use
data = 3.0*np.sin(f*t+0.001) + 0.5 + np.random.randn(N) # create artificial data with noise
guess_mean = np.mean(data)
guess_std = 3*np.std(data)/(2**0.5)/(2**0.5)
guess_phase = 0
guess_freq = 1
guess_amp = 1
# we'll use this to plot our first estimate. This might already be good enough for you
data_first_guess = guess_std*np.sin(t+guess_phase) + guess_mean
# Define the function to optimize, in this case, we want to minimize the difference
# between the actual data and our "guessed" parameters
optimize_func = lambda x: x[0]*np.sin(x[1]*t+x[2]) + x[3] - data
est_amp, est_freq, est_phase, est_mean = leastsq(optimize_func, [guess_amp, guess_freq, guess_phase, guess_mean])[0]
# recreate the fitted curve using the optimized parameters
data_fit = est_amp*np.sin(est_freq*t+est_phase) + est_mean
# recreate the fitted curve using the optimized parameters
fine_t = np.arange(0,max(t),0.1)
data_fit=est_amp*np.sin(est_freq*fine_t+est_phase)+est_mean
plt.plot(t, data, '.')
plt.plot(t, data_first_guess, label='first guess')
plt.plot(fine_t, data_fit, label='after fitting')
plt.legend()
plt.show()
编辑:我假设您知道正弦波的周期数。如果不这样做,安装起来会有些困难。您可以尝试通过手动绘图来猜测周期数,并尝试将其优化为您的第六个参数。
对我们来说更用户友好的是函数 curvefit。这是一个例子:
import numpy as np
from scipy.optimize import curve_fit
import pylab as plt
N = 1000 # number of data points
t = np.linspace(0, 4*np.pi, N)
data = 3.0*np.sin(t+0.001) + 0.5 + np.random.randn(N) # create artificial data with noise
guess_freq = 1
guess_amplitude = 3*np.std(data)/(2**0.5)
guess_phase = 0
guess_offset = np.mean(data)
p0=[guess_freq, guess_amplitude,
guess_phase, guess_offset]
# create the function we want to fit
def my_sin(x, freq, amplitude, phase, offset):
return np.sin(x * freq + phase) * amplitude + offset
# now do the fit
fit = curve_fit(my_sin, t, data, p0=p0)
# we'll use this to plot our first estimate. This might already be good enough for you
data_first_guess = my_sin(t, *p0)
# recreate the fitted curve using the optimized parameters
data_fit = my_sin(t, *fit[0])
plt.plot(data, '.')
plt.plot(data_fit, label='after fitting')
plt.plot(data_first_guess, label='first guess')
plt.legend()
plt.show()
当前将正弦曲线拟合到给定数据集的方法需要首先猜测参数,然后进行交互过程。这是一个非线性回归问题。
另一种方法是通过方便的积分方程将非线性回归转换为线性回归。那么就不需要初始猜测,也不需要迭代过程:直接获得拟合。
如果是函数
y = a + r*sin(w*x+phi)
或 y=a+b*sin(w*x)+c*cos(w*x)
,请参阅发表在 Scribd上的论文
"Régression sinusoidale"
的第 35-36 页
对于函数
y = a + p*x + r*sin(w*x+phi)
:“混合线性和正弦回归”一章的第 49-51 页。
如果是更复杂的函数,一般过程在章节
"Generalized sinusoidal regression"
第54-61页中进行了解释,后面是一个数字示例y = r*sin(w*x+phi)+(b/x)+c*ln(x)
,第62-63页
以上所有答案都是基于曲线拟合,并且大多数使用迭代方法 - 它们都工作得很好,但我想使用 FFT 添加不同的方法。在这里,我们对数据进行变换,将除峰值频率之外的所有数据设置为零,然后进行逆变换。请注意,您可能希望在进行 FFT 之前删除数据均值(并去除趋势),然后可以在之后将其添加回来。
import numpy as np
import pylab as plt
# fake data
N = 1000 # number of data points
t = np.linspace(0, 4*np.pi, N)
f = 1.05
data = 3.0*np.sin(f*t+0.001) + np.random.randn(N) # create artificial data with noise
# FFT...
mfft=np.fft.fft(data)
imax=np.argmax(np.absolute(mfft))
mask=np.zeros_like(mfft)
mask[[imax]]=1
mfft*=mask
fdata=np.fft.ifft(mfft)
plt.plot(t, data, '.')
plt.plot(t, fdata,'.', label='FFT')
plt.legend()
plt.show()
如果您已经知道频率,则可以进行线性拟合,这在计算上比非线性拟合方法更有效。正如@JJacquelin 指出的,不需要初始猜测。
明确地说,您需要将
y=a+b*sin(x)+c*sin(x)
拟合到数据中。
请注意,这相当于三角恒等式的 A*sin(x+phi)
。然而,这是以拟合参数线性的方式表达的(尽管不是 x,y)。因此我们可以在 python 中使用线性拟合。
假设
x1 = sin(x)
和 x2 = cos(x)
是输入,对 y = a + b* x1 + c* x2
使用线性拟合函数
为此使用
from sklearn.linear_model import LinearRegression
reg = LinearRegression()
x= # your x values list
y = # your y values list
X = np.column_stack((np.sin(x), np.cos(x)))
reg.fit(X, y)
您可以通过以下方式访问拟合参数:
a = reg.intercept_
b = reg.coef_[0]
c = reg.coef_[1]
拟合未知幅度、频率和相位的正弦曲线是一个非线性问题。如果频率已知,则可以使用其他提到的线性最小二乘法来求解。
当频率未知时,Kay 的经典著作中描述了高斯白噪声中正弦波的最佳解决方案:统计信号处理基础:估计理论(第 193 页)。它需要搜索使残差平方和最小化的频率,同时解决每一步的幅度和相位的线性问题。
有一个名为 pyestimate 的 python 库实现了这个解决方案。它可以适应嘈杂的正弦波:
#!pip install pyestimate
from pyestimate import sin_param_estimate
import numpy as np
import matplotlib.pyplot as plt
# define a signal to be fitted
n = np.arange(100)
s = 1.234 * np.cos(2*np.pi*0.0345*n + np.pi/7)
# add some noise
x = s + np.random.normal(scale=0.5, size=len(n))
# fit amplitude, frequency and phase
A,f,phi = sin_param_estimate(x)
# plot result
plt.plot(s, '-', label='original sine')
plt.plot(x, '.', label='noisy input data')
plt.plot(A*np.cos(2*np.pi*f*n+phi), 'r--', label='fitted sine')
plt.legend()
pyestimate 还实现了正弦波总和的拟合。