如何使用pylab和numpy将正弦曲线拟合到我的数据中?

Cha*_*ies 26 python trigonometry numpy economics

对于一个学校项目,我试图表明经济遵循相对正弦的增长模式.除了它的经济学,这是不确定的,我正在构建一个python模拟,以表明,即使我们让某种程度的随机性保持不变,我们仍然可以产生一些相对正弦的东西.我对我正在制作的数据感到满意,但现在我想找到一些方法来获得与数据非常匹配的正弦图.我知道你可以做多项式拟合,但你能做正确的吗?

感谢您的帮助.如果您想要查看代码的任何部分,请告诉我.

Dha*_*ara 46

您可以在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()
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

编辑:我假设您知道正弦波中的周期数.如果你不这样做,那么适合它会有些棘手.您可以通过手动绘图尝试猜测周期数,并尝试将其优化为第6个参数.

  • 这个解决方案虽然被OP接受,但似乎跳过了最棘手的部分:_frequency_`f`,如'y = Amplitude*sin(频率*x +相位)+ Offset`.如果`f`未知,这种方法的效果如何? (3认同)
  • 我认为你提供函数的初始参数值的顺序是错误的.那么`est_a,est_b,est_c = leastsq(optimize_func,[guess_b,guess_a,guess_c])[0]`?为清楚起见,我建议用_offset替换_a,用_amp替换_b,用_phase替换_c,并在lambda中使用x [i]的递增顺序. (2认同)
  • 这是一个很好的答案,也感谢你的工作 - 我将其剪切并粘贴到我正在做的事情上,这完全成功了.谢谢! (2认同)

uns*_*sym 45

这是一个无参数拟合函数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)}
Run Code Online (Sandbox Code Playgroud)

初始频率猜测由频域中的峰值频率使用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()
Run Code Online (Sandbox Code Playgroud)

即使噪音很高,效果也很好:

幅度= 1.00660540618,角频率= 2.03370472482,相位= 0.360276844224,偏移= 3.95747467506,最大值.冠状病毒.= 0.0122923578658

有噪音 低频率 高频

  • FFT和曲线拟合的非常好的组合 - 这个答案应该被赞成.可以在这里完成一些小的优化:使用rfft进行实值信号,使用np.angle()从FFT中提取相位以在猜测数组中使用,并使用余弦代替正弦,因为它们自然地来自FFT系数.@hwlau的代码按原样运行,但我相信曲线拟合会在添加建议的改进后更快地执行. (3认同)
  • 亲爱的 unsym 我试图运行您的代码,但不幸的是我收到以下消息:TypeError: 'numpy.float64' object cannot be interpret as an integer 当我尝试绘制函数时。你有什么想法来解决这个问题吗? (2认同)

Vas*_*sco 13

对我们更友好的是功能曲线.这是一个例子:

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()
Run Code Online (Sandbox Code Playgroud)


小智 5

将正弦曲线拟合到给定数据集的当前方法需要先猜测参数,然后进行交互过程。这是一个非线性回归问题。

借助便利的积分方程,可以将非线性回归转换为线性回归。这样,就不需要初始猜测,也不需要迭代过程:直接获得拟合。

如果具有y = a + r*sin(w*x+phi)或功能y=a+b*sin(w*x)+c*cos(w*x),请参见"Régression sinusoidale"Scribd上发表的论文的第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页中


Adr*_*ins 5

上述所有答案都基于曲线拟合,并且大多数都使用迭代方法 - 它们都工作得非常好,但我想使用 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()
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述