Diese Antwort bezieht sich auf this Beitrag, wo ich über die Anpassung mit x
und y
Fehler diskutiere. Dies erfordert daher nicht das Modul ODR
, sondern kann manuell durchgeführt werden. Daher kann man leastsq
oder minimize
verwenden. Bezüglich der Einschränkungen habe ich in anderen Beiträgen klargestellt, dass ich versuche, sie möglichst zu vermeiden. Dies kann auch hier getan werden, obwohl die Details der Programmierung und Mathematik ein wenig umständlich sind, besonders wenn es stabil und narrensicher sein soll. Ich werde nur eine grobe Vorstellung geben. Sagen wir, wir wollen y0 > m * x0**(-c)
. In Log-Form können wir dies als eta0 > mu - c * xeta0
schreiben. I.e. Es gibt eine alpha
, so dass eta0 = mu - c * xeta0 + alpha**2
. Gleiches gilt für die anderen Ungleichungen. Für die zweite obere Grenze erhalten Sie eine beta**2
, aber Sie können entscheiden, welche die kleinere ist, so dass Sie automatisch die andere Bedingung erfüllen. Gleiches gilt für die unteren Grenzen mit gamma**2
und delta**2
. Angenommen, wir können mit alpha
und gamma
arbeiten. Wir können die Ungleichungsbedingungen kombinieren, um diese beiden ebenfalls in Beziehung zu setzen. Am Ende können wir eine sigma
und alpha = sqrt(s-t)* sigma/sqrt(sigma**2 + 1)
passen, wobei s
und t
von den Ungleichungen abgeleitet sind. Die sigma/sqrt(sigma**2 + 1)
Funktion ist nur eine Option, um alpha
in einem bestimmten Bereich variieren zu lassen, d. H. alpha**2 < s-t
Die Tatsache, dass der Radicand negativ werden kann, zeigt, dass es Fälle ohne Lösung gibt. Mit alpha
bekannt, werden mu
und daher m
berechnet. So Fit-Parameter sind c
und sigma
, die die Ungleichungen berücksichtigt und macht m
abhängig. Ich habe es müde und es funktioniert, aber die vorliegende Version ist nicht die stabilste. Ich würde es auf Anfrage veröffentlichen.
Da wir bereits eine handgemachte Restfunktion haben, haben wir eine zweite Möglichkeit. Wir stellen nur unsere eigene chi**2
Funktion vor und verwenden minimize
, was Einschränkungen erlaubt.Da minimize
und die constraints
Schlüsselwortlösung sehr flexibel sind und die Restfunktion leicht für andere Funktionen modifiziert werden kann und nicht nur für m * x**(-c)
ist die gesamte Konstruktion ziemlich flexibel. Es sieht aus wie folgt:
import matplotlib.pyplot as plt
import numpy as np
from random import random, seed
from scipy.optimize import minimize,leastsq
seed(7563)
fig1 = plt.figure(1)
###for gaussion distributed errors
def boxmuller(x0,sigma):
u1=random()
u2=random()
ll=np.sqrt(-2*np.log(u1))
z0=ll*np.cos(2*np.pi*u2)
z1=ll*np.cos(2*np.pi*u2)
return sigma*z0+x0, sigma*z1+x0
###for plotting ellipses
def ell_data(a,b,x0=0,y0=0):
tList=np.linspace(0,2*np.pi,150)
k=float(a)/float(b)
rList=[a/np.sqrt((np.cos(t))**2+(k*np.sin(t))**2) for t in tList]
xyList=np.array([[x0+r*np.cos(t),y0+r*np.sin(t)] for t,r in zip(tList,rList)])
return xyList
###function to fit
def f(x,m,c):
y = abs(m) * abs(x)**(-abs(c))
#~ print y,x,m,c
return y
###how to rescale the ellipse to make fitfunction a tangent
def elliptic_rescale(x, m, c, x0, y0, sa, sb):
#~ print "e,r",x,m,c
y=f(x, m, c)
#~ print "e,r",y
r=np.sqrt((x - x0)**2 + (y - y0)**2)
kappa=float(sa)/float(sb)
tau=np.arctan2(y - y0, x - x0)
new_a=r*np.sqrt(np.cos(tau)**2 + (kappa * np.sin(tau))**2)
return new_a
###residual function to calculate chi-square
def residuals(parameters,dataPoint):#data point is (x,y,sx,sy)
m, c = parameters
#~ print "m c", m, c
theData = np.array(dataPoint)
best_t_List=[]
for i in range(len(dataPoint)):
x, y, sx, sy = dataPoint[i][0], dataPoint[i][1], dataPoint[i][2], dataPoint[i][3]
#~ print "x, y, sx, sy",x, y, sx, sy
###getthe point on the graph where it is tangent to an error-ellipse
ed_fit = minimize(elliptic_rescale, x , args = (m, c, x, y, sx, sy))
best_t = ed_fit['x'][0]
best_t_List += [best_t]
#~ exit(0)
best_y_List=[ f(t, m, c) for t in best_t_List ]
##weighted distance not squared yet, as this is done by scipy.optimize.leastsq
wighted_dx_List = [ (x_b - x_f)/sx for x_b, x_f, sx in zip(best_t_List,theData[:,0], theData[:,2]) ]
wighted_dy_List = [ (x_b - x_f)/sx for x_b, x_f, sx in zip(best_y_List,theData[:,1], theData[:,3]) ]
return wighted_dx_List + wighted_dy_List
def chi2(params, pnts):
r = np.array(residuals(params, pnts))
s = sum([ x**2 for x in r] )
#~ print params,s,r
return s
def myUpperIneq(params,pnt):
m, c = params
x,y=pnt
return y - f(x, m, c)
def myLowerIneq(params,pnt):
m, c = params
x,y=pnt
return f(x, m, c) - y
###to create some test data
def test_data(m,c, xList,const_sx,rel_sx,const_sy,rel_sy):
yList=[f(x,m,c) for x in xList]
xErrList=[ boxmuller(x,const_sx+x*rel_sx)[0] for x in xList]
yErrList=[ boxmuller(y,const_sy+y*rel_sy)[0] for y in yList]
return xErrList,yErrList
###some start values
mm_0=2.3511
expo_0=.3588
csx,rsx=.01,.07
csy,rsy=.04,.09,
limitingPoints=dict()
limitingPoints[0]=np.array([[.2,5.4],[.5,5.0],[5.1,.9],[5.7,.9]])
limitingPoints[1]=np.array([[.2,5.4],[.5,5.0],[5.1,1.5],[5.7,1.2]])
limitingPoints[2]=np.array([[.2,3.4],[.5,5.0],[5.1,1.1],[5.7,1.2]])
limitingPoints[3]=np.array([[.2,3.4],[.5,5.0],[5.1,1.7],[5.7,1.2]])
####some data
xThData=np.linspace(.2,5,15)
yThData=[ f(x, mm_0, expo_0) for x in xThData]
#~ ###some noisy data
xNoiseData,yNoiseData=test_data(mm_0, expo_0, xThData, csx,rsx, csy,rsy)
xGuessdError=[csx+rsx*x for x in xNoiseData]
yGuessdError=[csy+rsy*y for y in yNoiseData]
for testing in range(4):
###Now fitting with limits
zipData=zip(xNoiseData,yNoiseData, xGuessdError, yGuessdError)
estimate = [ 2.4, .3 ]
con0={'type': 'ineq', 'fun': myUpperIneq, 'args': (limitingPoints[testing][0],)}
con1={'type': 'ineq', 'fun': myUpperIneq, 'args': (limitingPoints[testing][1],)}
con2={'type': 'ineq', 'fun': myLowerIneq, 'args': (limitingPoints[testing][2],)}
con3={'type': 'ineq', 'fun': myLowerIneq, 'args': (limitingPoints[testing][3],)}
myResult = minimize(chi2 , estimate , args=(zipData,), constraints=[ con0, con1, con2, con3 ] )
print "############"
print myResult
###plot that
ax=fig1.add_subplot(4,2,2*testing+1)
ax.plot(xThData,yThData)
ax.errorbar(xNoiseData,yNoiseData, xerr=xGuessdError, yerr=yGuessdError, fmt='none',ecolor='r')
testX = np.linspace(.2,6,25)
testY = np.fromiter((f(x, myResult.x[0], myResult.x[1]) for x in testX), np.float)
bx=fig1.add_subplot(4,2,2*testing+2)
bx.plot(xThData,yThData)
bx.errorbar(xNoiseData,yNoiseData, xerr=xGuessdError, yerr=yGuessdError, fmt='none',ecolor='r')
ax.plot(limitingPoints[testing][:,0],limitingPoints[testing][:,1],marker='x', linestyle='')
bx.plot(limitingPoints[testing][:,0],limitingPoints[testing][:,1],marker='x', linestyle='')
ax.plot(testX, testY, linestyle='--')
bx.plot(testX, testY, linestyle='--')
bx.set_xscale('log')
bx.set_yscale('log')
plt.show()
Providing Ergebnisse
############
status: 0
success: True
njev: 8
nfev: 36
fun: 13.782127248002116
x: array([ 2.15043226, 0.35646436])
message: 'Optimization terminated successfully.'
jac: array([-0.00377715, 0.00350225, 0. ])
nit: 8
############
status: 0
success: True
njev: 7
nfev: 32
fun: 41.372277637885716
x: array([ 2.19005695, 0.23229378])
message: 'Optimization terminated successfully.'
jac: array([ 123.95069313, -442.27114677, 0. ])
nit: 7
############
status: 0
success: True
njev: 5
nfev: 23
fun: 15.946621924326545
x: array([ 2.06146362, 0.31089065])
message: 'Optimization terminated successfully.'
jac: array([-14.39131606, -65.44189298, 0. ])
nit: 5
############
status: 0
success: True
njev: 7
nfev: 34
fun: 88.306027468763432
x: array([ 2.16834392, 0.14935514])
message: 'Optimization terminated successfully.'
jac: array([ 224.11848736, -791.75553417, 0. ])
nit: 7
ich vier verschiedene Grenzpunkte (Zeilen) geprüft. Das Ergebnis wird normal und im logarithmischen Maßstab (Spalten) angezeigt. Mit etwas zusätzlicher Arbeit könnten Sie auch Fehler bekommen.
Update auf asymmetrische Fehler Um ehrlich zu sein, im Moment weiß ich nicht, wie man mit dieser Eigenschaft umzugehen. Naiv würde ich meine eigene asymmetrische Verlustfunktion ähnlich this post definieren. Mit x
und y
Fehler mache ich es Quadranten statt nur positive oder negative Seite zu überprüfen. Meine Fehlerellipse ändert sich daher in vier zusammenhängende Teile. Trotzdem ist es einigermaßen vernünftig. Zum Testen und um zu zeigen, wie es funktioniert, habe ich ein Beispiel mit einer linearen Funktion gemacht. Ich denke, der OP kann die beiden Codeabschnitte nach seinen Anforderungen kombinieren.
Im Fall eines linearen passen sie wie folgt aussehen:
import matplotlib.pyplot as plt
import numpy as np
from random import random, seed
from scipy.optimize import minimize,leastsq
#~ seed(7563)
fig1 = plt.figure(1)
ax=fig1.add_subplot(2,1,1)
bx=fig1.add_subplot(2,1,2)
###function to fit, here only linear for testing.
def f(x,m,y0):
y = m * x +y0
return y
###for gaussion distributed errors
def boxmuller(x0,sigma):
u1=random()
u2=random()
ll=np.sqrt(-2*np.log(u1))
z0=ll*np.cos(2*np.pi*u2)
z1=ll*np.cos(2*np.pi*u2)
return sigma*z0+x0, sigma*z1+x0
###for plotting ellipse quadrants
def ell_data(aN,aP,bN,bP,x0=0,y0=0):
tPPList=np.linspace(0, 0.5 * np.pi, 50)
kPP=float(aP)/float(bP)
rPPList=[aP/np.sqrt((np.cos(t))**2+(kPP*np.sin(t))**2) for t in tPPList]
tNPList=np.linspace(0.5 * np.pi, 1.0 * np.pi, 50)
kNP=float(aN)/float(bP)
rNPList=[aN/np.sqrt((np.cos(t))**2+(kNP*np.sin(t))**2) for t in tNPList]
tNNList=np.linspace(1.0 * np.pi, 1.5 * np.pi, 50)
kNN=float(aN)/float(bN)
rNNList=[aN/np.sqrt((np.cos(t))**2+(kNN*np.sin(t))**2) for t in tNNList]
tPNList = np.linspace(1.5 * np.pi, 2.0 * np.pi, 50)
kPN = float(aP)/float(bN)
rPNList = [aP/np.sqrt((np.cos(t))**2+(kPN*np.sin(t))**2) for t in tPNList]
tList = np.concatenate([ tPPList, tNPList, tNNList, tPNList])
rList = rPPList + rNPList+ rNNList + rPNList
xyList=np.array([[x0+r*np.cos(t),y0+r*np.sin(t)] for t,r in zip(tList,rList)])
return xyList
###how to rescale the ellipse to touch fitfunction at point (x,y)
def elliptic_rescale_asymmetric(x, m, c, x0, y0, saN, saP, sbN, sbP , getQuadrant=False):
y=f(x, m, c)
###distance to function
r=np.sqrt((x - x0)**2 + (y - y0)**2)
###angle to function
tau=np.arctan2(y - y0, x - x0)
quadrant=0
if tau >0:
if tau < 0.5 * np.pi: ## PP
kappa=float(saP)/float(sbP)
quadrant=1
else:
kappa=float(saN)/float(sbP)
quadrant=2
else:
if tau < -0.5 * np.pi: ## PP
kappa=float(saN)/float(sbN)
quadrant=3
else:
kappa=float(saP)/float(sbN)
quadrant=4
new_a=r*np.sqrt(np.cos(tau)**2 + (kappa * np.sin(tau))**2)
if quadrant == 1 or quadrant == 4:
rel_a=new_a/saP
else:
rel_a=new_a/saN
if getQuadrant:
return rel_a, quadrant, tau
else:
return rel_a
### residual function to calculate chi-square
def residuals(parameters,dataPoint):#data point is (x,y,sxN,sxP,syN,syP)
m, c = parameters
theData = np.array(dataPoint)
bestTList=[]
qqList=[]
weightedDistanceList = []
for i in range(len(dataPoint)):
x, y, sxN, sxP, syN, syP = dataPoint[i][0], dataPoint[i][1], dataPoint[i][2], dataPoint[i][3], dataPoint[i][4], dataPoint[i][5]
### get the point on the graph where it is tangent to an error-ellipse
### i.e. smallest ellipse touching the graph
edFit = minimize( elliptic_rescale_asymmetric, x , args = (m, c, x, y, sxN, sxP, syN, syP))
bestT = edFit['x'][0]
bestTList += [ bestT ]
bestA,qq , tau= elliptic_rescale_asymmetric(bestT, m, c , x, y, aN, aP, bN, bP , True)
qqList += [ qq ]
bestYList=[ f(t, m, c) for t in bestTList ]
### weighted distance not squared yet, as this is done by scipy.optimize.leastsq or manual chi2 function
for counter in range(len(dataPoint)):
xb=bestTList[counter]
xf=dataPoint[counter][0]
yb=bestYList[counter]
yf=dataPoint[counter][1]
quadrant=qqList[counter]
if quadrant == 1:
sx, sy = sxP, syP
elif quadrant == 2:
sx, sy = sxN, syP
elif quadrant == 3:
sx, sy = sxN, syN
elif quadrant == 4:
sx, sy = sxP, syN
else:
assert 0
weightedDistanceList += [ (xb - xf)/sx, (yb - yf)/sy ]
return weightedDistanceList
def chi2(params, pnts):
r = np.array(residuals(params, pnts))
s = np.fromiter((x**2 for x in r), np.float).sum()
return s
####...to make data with asymmetric error (fixed); for testing only
def noisy_data(xList,m0,y0, sxN,sxP,syN,syP):
yList=[ f(x, m0, y0) for x in xList]
gNList=[boxmuller(0,1)[0] for dummy in range(len(xList))]
xerrList=[]
for x,err in zip(xList,gNList):
if err < 0:
xerrList += [ sxP * err + x ]
else:
xerrList += [ sxN * err + x ]
gNList=[boxmuller(0,1)[0] for dummy in range(len(xList))]
yerrList=[]
for y,err in zip(yList,gNList):
if err < 0:
yerrList += [ syP * err + y ]
else:
yerrList += [ syN * err + y ]
return xerrList, yerrList
###some start values
m0=1.3511
y0=-2.2
aN, aP, bN, bP=.2,.5, 0.9, 1.6
#### some data
xThData=np.linspace(.2,5,15)
yThData=[ f(x, m0, y0) for x in xThData]
xThData0=np.linspace(-1.2,7,3)
yThData0=[ f(x, m0, y0) for x in xThData0]
### some noisy data
xErrList,yErrList = noisy_data(xThData, m0, y0, aN, aP, bN, bP)
###...and the fit
dataToFit=zip(xErrList,yErrList, len(xThData)*[aN], len(xThData)*[aP], len(xThData)*[bN], len(xThData)*[bP])
fitResult = minimize(chi2, (m0,y0) , args=(dataToFit,))
fittedM, fittedY=fitResult.x
yThDataF=[ f(x, fittedM, fittedY) for x in xThData0]
### plot that
for cx in [ax,bx]:
cx.plot([-2,7], [f(x, m0, y0) for x in [-2,7]])
ax.errorbar(xErrList,yErrList, xerr=[ len(xThData)*[aN],len(xThData)*[aP] ], yerr=[ len(xThData)*[bN],len(xThData)*[bP] ], fmt='ro')
for x,y in zip(xErrList,yErrList)[:]:
xEllList,yEllList = zip(*ell_data(aN,aP,bN,bP,x,y))
ax.plot(xEllList,yEllList ,c='#808080')
### rescaled
### ...as well as a scaled version that touches the original graph. This gives the error shortest distance to that graph
ed_fit = minimize(elliptic_rescale_asymmetric, 0 ,args=(m0, y0, x, y, aN, aP, bN, bP))
best_t = ed_fit['x'][0]
best_a,qq , tau= elliptic_rescale_asymmetric(best_t, m0, y0 , x, y, aN, aP, bN, bP , True)
xEllList,yEllList = zip(*ell_data(aN * best_a, aP * best_a, bN * best_a, bP * best_a, x, y))
ax.plot(xEllList, yEllList, c='#4040a0')
###plot the fit
bx.plot(xThData0,yThDataF)
bx.errorbar(xErrList,yErrList, xerr=[ len(xThData)*[aN],len(xThData)*[aP] ], yerr=[ len(xThData)*[bN],len(xThData)*[bP] ], fmt='ro')
for x,y in zip(xErrList,yErrList)[:]:
xEllList,yEllList = zip(*ell_data(aN,aP,bN,bP,x,y))
bx.plot(xEllList,yEllList ,c='#808080')
####rescaled
####...as well as a scaled version that touches the original graph. This gives the error shortest distance to that graph
ed_fit = minimize(elliptic_rescale_asymmetric, 0 ,args=(fittedM, fittedY, x, y, aN, aP, bN, bP))
best_t = ed_fit['x'][0]
#~ print best_t
best_a,qq , tau= elliptic_rescale_asymmetric(best_t, fittedM, fittedY , x, y, aN, aP, bN, bP , True)
xEllList,yEllList = zip(*ell_data(aN * best_a, aP * best_a, bN * best_a, bP * best_a, x, y))
bx.plot(xEllList, yEllList, c='#4040a0')
plt.show()
die
Der obere Graph Plots zeigt die ursprüngliche lineare Funktion und einige Daten aus diesem mit asymmetrischen Gauß-Fehlern erzeugt. Es werden Fehlerbalken sowie die stückweisen Fehlerellipsen (grau ... und neu skaliert, um die lineare Funktion zu berühren, blau) gezeichnet. Die untere Grafik zeigt zusätzlich die angepasste Funktion sowie die skalierten stückweise Ellipsen und berührt die angepasste Funktion.
Oh ... merkt man nur, sind es nur Obergrenzen oder haben Sie auch Untergrenzen? ... jedenfalls erlaubt meine Lösung beides. – mikuszefski
@mikuszefski Ja, es sind nur obere Grenzen, aber ich habe vergessen zu erwähnen, dass ich auch brauche, dass die y und x Fehler asymmetrisch sein können, dh ich würde yerr_l und yerr_u für die unteren und oberen Fehler und dasselbe eingeben müssen x! – astrostudent
Oh, OK, das würde auch nicht mit dem 'ODR' funktionieren, wenn ich das Paket richtig hätte. Momentan bin ich mir nicht sicher, wie groß der orthogonale Abstand für asymmetrische Fehler wäre ... vielleicht Quadranten-weise? Aber 'x' und' y' sind nicht korreliert, oder sind sie? – mikuszefski