it-swarm.com.de

Wie passe ich mit pylab und numpy eine Sinuskurve an meine Daten an?

Für ein Schulprojekt versuche ich zu zeigen, dass Volkswirtschaften einem relativ sinusförmigen Wachstumsmuster folgen. Abgesehen von der Wirtschaftlichkeit, die zugegebenermaßen zweifelhaft ist, baue ich eine python========================================================================= Zufrieden mit meinen Daten, die ich produziere, aber jetzt möchte ich einen Weg finden, um ein Sinusdiagramm zu erhalten, das ziemlich genau mit den Daten übereinstimmt.

Danke im Vorraus für deine Hilfe. Lassen Sie mich wissen, ob es Teile des Codes gibt, die Sie sehen möchten.

24
ChapmIndustries

Sie können die Funktion Least-Square-Optimierung in scipy verwenden, um eine beliebige Funktion an eine andere anzupassen. Beim Anpassen einer Sinusfunktion sind die 3 anzupassenden Parameter der Offset ('a'), die Amplitude ('b') und die Phase ('c').

Solange Sie eine vernünftige erste Schätzung der Parameter vornehmen, sollte die Optimierung gut konvergieren. Zum Glück für eine Sinusfunktion sind erste Schätzungen von 2 einfach: Der Versatz kann geschätzt werden, indem der Mittelwert der Daten und die Amplitude über verwendet werden die RMS (3 * Standardabweichung/sqrt (2)).

Hinweis: Bei einer späteren Bearbeitung wurde auch die Frequenzanpassung hinzugefügt. Dies funktioniert nicht sehr gut (kann zu extrem schlechten Anfällen führen). Daher würde ich nach eigenem Ermessen raten, keine Frequenzanpassung zu verwenden, es sei denn, der Frequenzfehler ist kleiner als einige Prozent.

Dies führt zu folgendem Code:

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()

enter image description here

Bearbeiten: Ich nahm an, dass Sie die Anzahl der Perioden in der Sinuswelle kennen. Wenn Sie dies nicht tun, ist es etwas schwieriger zu passen. Sie können versuchen, die Anzahl der Perioden durch manuelles Zeichnen zu erraten, und versuchen, sie als sechsten Parameter zu optimieren.

45
Dhara

Hier ist eine parameterfreie Anpassungsfunktion fit_sin(), für die keine manuelle Frequenzermittlung erforderlich ist:

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)}

Die anfängliche Frequenzschätzung wird durch die Spitzenfrequenz im Frequenzbereich unter Verwendung von FFT gegeben. Das Anpassungsergebnis ist nahezu perfekt, vorausgesetzt, es gibt nur eine dominante Frequenz (mit Ausnahme der Nullfrequenzspitze).

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()

Das Ergebnis ist auch bei hohem Lärm gut:

Amplitude = 1.00660540618, Angular Freq. = 2.03370472482, Phase = 0.360276844224, Offset = 3.95747467506, Max. Cov. = 0.0122923578658

With noiseLow frequencyHigh frequency

43
unsym

Noch benutzerfreundlicher ist für uns die Funktion curvefit. Hier ein Beispiel:

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()
12
Vasco

Die aktuellen Methoden zum Anpassen einer Sinuskurve an einen bestimmten Datensatz erfordern ein erstes Erraten der Parameter, gefolgt von einem interaktiven Prozess. Dies ist ein nichtlineares Regressionsproblem.

Eine andere Methode besteht darin, die nichtlineare Regression durch eine geeignete Integralgleichung in eine lineare Regression umzuwandeln. Dann ist keine erste Vermutung und kein iterativer Prozess erforderlich: Die Anpassung wird direkt erhalten.

Für die Funktion y = a + r*sin(w*x+phi) oder y=a+b*sin(w*x)+c*cos(w*x) siehe Seite 35-36 des Papiers "Régression sinusoidale", Das am Scribd veröffentlicht wurde

Bei der Funktion y = a + p*x + r*sin(w*x+phi): Seite 49-51 des Kapitels "Gemischte lineare und sinusförmige Regressionen".

Bei komplizierteren Funktionen wird der allgemeine Vorgang im Kapitel "Generalized sinusoidal regression" Auf den Seiten 54-61 erläutert, gefolgt von einem numerischen Beispiel y = r*sin(w*x+phi)+(b/x)+c*ln(x) auf den Seiten 62-63

4
JJacquelin

Das folgende Python Skript führt die Anpassung der kleinsten Quadrate von Sinuskurven durch, wie in Bloomfield (2000), "Fourier Analysis of Time Series", Wiley, beschrieben. Die wichtigsten Schritte sind die folgenden:

  1. Definieren Sie einen Bereich verschiedener möglicher Frequenzen.
  2. Schätzen Sie für jede der unter Punkt 1 angegebenen Frequenzen alle Parameter der Sinuskurve (Mittelwert, Amplitude und Phase) durch gewöhnliche kleinste Quadrate (OLS).
  3. Wählen Sie unter den verschiedenen unter Punkt 2 geschätzten Parametersätzen den aus, der die verbleibende Quadratsumme (RSS) minimiert.
import pandas as pd
import numpy as np
import statsmodels.api as sm
import matplotlib.pyplot as plt
######################################################################################
# (1) generate the data
######################################################################################
omega=4.5  # frequency
theta0=0.5 # mean
theta1=1.5 # amplitude
phi=-0.25  # phase

n=1000 # number of observations
sigma=1.25 # error standard deviation
e=np.random.normal(0,sigma,n) # Gaussian error

t=np.linspace(1,n,n)/n # time index
y=theta0+theta1*np.cos(2*np.pi*(omega*t+phi))+e # time series
######################################################################################
# (2) estimate the parameters
######################################################################################
# define the range of different possible frequencies
f=np.linspace(1,12,100)

# create a data frame to store the estimated parameters and the associated
# residual sum of squares for each of the considered frequencies
coefs=pd.DataFrame(data=np.zeros((len(f),5)), columns=["omega","theta0","theta1","phi","RSS"])

for i in range(len(f)): # iterate across the different considered frequencies

    x1=np.cos(2*np.pi*f[i]*t) # define the first regressor
    x2=np.sin(2*np.pi*f[i]*t) # define the second regressor

    X=sm.add_constant(np.transpose(np.vstack((x1,x2)))) # add the intercept
    reg=sm.OLS(y,X).fit() # fit the regression by OLS

    A=reg.params[1] # estimated coefficient of first regressor
    B=reg.params[2] # estimated coefficient of second regressor

    # estimated mean
    mean=reg.params[0]

    # estimated amplitude
    amplitude=np.sqrt(A**2+B**2)

    # estimated phase
    if A>0:          phase=np.arctan(-B/A)/(2*np.pi)
    if A<0 and B>0:  phase=(np.arctan(-B/A)-np.pi)/(2*np.pi)
    if A<0 and B<=0: phase=(np.arctan(-B/A)+np.pi)/(2*np.pi)
    if A==0 and B>0: phase=-1/4
    if A==0 and B<0: phase=1/4

    # fitted sinusoid
    s=mean+amplitude*np.cos(2*np.pi*(f[i]*t+phase))

    # residual sum of squares
    RSS=np.sum((y-s)**2)

    # save the estimation results
    coefs["omega"][i]=f[i]
    coefs["theta0"][i]=mean
    coefs["theta1"][i]=amplitude
    coefs["phi"][i]=phase
    coefs["RSS"][i]=RSS

    del x1, x2, X, reg, A, B, mean, amplitude, phase, s, RSS
######################################################################################
# (3) analyze the results
######################################################################################
# extract the set of parameters that minimizes the residual sum of squares
coefs=coefs.loc[coefs["RSS"]==coefs["RSS"].min(),]

# calculate the fitted sinusoid
s=coefs["theta0"].values+coefs["theta1"].values*np.cos(2*np.pi*(coefs["omega"].values*t+coefs["phi"].values))

# plot the fitted sinusoid
plt.plot(y,color="black",linewidth=1,label="actual")
plt.plot(s,color="lightgreen",linewidth=3,label="fitted")
plt.ylim(ymax=np.max(y)*1.3)
plt.legend(loc=1)
plt.show()
0
flaviag