为由两个不同制度组成的数据拟合曲线

时间:2013-11-13 13:57:49

标签: python numpy matplotlib scipy curve-fitting

我正在寻找一种通过一些实验数据绘制曲线的方法。数据显示了一个具有浅梯度的小线性区域,然后是阈值后的陡峭线性区域。

我的数据在这里:http://pastebin.com/H4NSbxqr
and is plotted here

我可以相对容易地使用两条线拟合数据,但我想要理想地使用连续线 - 这应该看起来像两条线,其中有一条平滑的曲线将它们连接到阈值附近(数据中约为5000,显示上文)。

我尝试使用scipy.optimize curve_fit并尝试包含直线和指数之和的函数:

y = a*x + b + c*np.exp((x-d)/e)

尽管经过多次尝试,却没有找到解决方案。

如果有人有任何建议,无论是选择拟合分布/方法还是curve_fit实施,我们将不胜感激。

4 个答案:

答案 0 :(得分:23)

如果您没有特别的理由相信线性+指数是数据的真正根本原因,那么我认为适合两条线是最有意义的。您可以通过使拟合函数最多包含两行来完成此操作,例如:

import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit

def two_lines(x, a, b, c, d):
    one = a*x + b
    two = c*x + d
    return np.maximum(one, two)

然后,

x, y = np.genfromtxt('tmp.txt', unpack=True, delimiter=',')

pw0 = (.02, 30, .2, -2000) # a guess for slope, intercept, slope, intercept
pw, cov = curve_fit(two_lines, x, y, pw0)
crossover = (pw[3] - pw[1]) / (pw[0] - pw[2])

plt.plot(x, y, 'o', x, two_lines(x, *pw), '-')

如果你真的想要一个连续且可微分的解决方案,我发现双曲线有一个急剧的弯曲,但它必须旋转。这有点难以实现(也许有一种更简单的方法),但这里有一个去:

def hyperbola(x, a, b, c, d, e):
    """ hyperbola(x) with parameters
        a/b = asymptotic slope
         c  = curvature at vertex
         d  = offset to vertex
         e  = vertical offset
    """
    return a*np.sqrt((b*c)**2 + (x-d)**2)/b + e

def rot_hyperbola(x, a, b, c, d, e, th):
    pars = a, b, c, 0, 0 # do the shifting after rotation
    xd = x - d
    hsin = hyperbola(xd, *pars)*np.sin(th)
    xcos = xd*np.cos(th)
    return e + hyperbola(xcos - hsin, *pars)*np.cos(th) + xcos - hsin

将其作为

运行
h0 = 1.1, 1, 0, 5000, 100, .5
h, hcov = curve_fit(rot_hyperbola, x, y, h0)
plt.plot(x, y, 'o', x, two_lines(x, *pw), '-', x, rot_hyperbola(x, *h), '-')
plt.legend(['data', 'piecewise linear', 'rotated hyperbola'], loc='upper left')
plt.show()

bent data fits

我也能够得到线+指数收敛,但看起来很糟糕。这是因为它不是一个很好的数据描述符,它是线性的,指数离线性很远!

def line_exp(x, a, b, c, d, e):
    return a*x + b + c*np.exp((x-d)/e)

e0 = .1, 20., .01, 1000., 2000.
e, ecov = curve_fit(line_exp, x, y, e0)

如果你想保持简单,总会有多项式或样条(分段多项式)

from scipy.interpolate import UnivariateSpline
s = UnivariateSpline(x, y, s=x.size)  #larger s-value has fewer "knots"
plt.plot(x, s(x))

with line+exp and polynomial

答案 1 :(得分:4)

我对Sanford进行了一些研究,应用线性回归,Steiger的相关和回归讲座有一些很好的信息。然而,它们都缺少正确的模型,分段函数应该是

enter image description here

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import lmfit

dfseg = pd.read_csv('segreg.csv')
def err(w):
    th0 = w['th0'].value
    th1 = w['th1'].value
    th2 = w['th2'].value
    gamma = w['gamma'].value
    fit = th0 + th1*dfseg.Temp + th2*np.maximum(0,dfseg.Temp-gamma)
    return fit-dfseg.C

p = lmfit.Parameters()
p.add_many(('th0', 0.), ('th1', 0.0),('th2', 0.0),('gamma', 40.))
mi = lmfit.minimize(err, p)
lmfit.printfuncs.report_fit(mi.params)

b0 = mi.params['th0']; b1=mi.params['th1'];b2=mi.params['th2']
gamma = int(mi.params['gamma'].value)

import statsmodels.formula.api as smf
reslin = smf.ols('C ~ 1 + Temp + I((Temp-%d)*(Temp>%d))' % (gamma,gamma), data=dfseg).fit()
print reslin.summary()

x0 = np.array(range(0,gamma,1))
x1 = np.array(range(0,80-gamma,1))
y0 = b0 + b1*x0
y1 = (b0 + b1 * float(gamma) + (b1 + b2)* x1)
plt.scatter(dfseg.Temp, dfseg.C)
plt.hold(True)
plt.plot(x0,y0)
plt.plot(x1+gamma,y1)

plt.show()

结果

[[Variables]]
    th0:     78.6554456 +/- 3.966238 (5.04%) (init= 0)
    th1:    -0.15728297 +/- 0.148250 (94.26%) (init= 0)
    th2:     0.72471237 +/- 0.179052 (24.71%) (init= 0)
    gamma:   38.3110177 +/- 4.845767 (12.65%) (init= 40)

数据

"","Temp","C"
"1",8.5536,86.2143
"2",10.6613,72.3871
"3",12.4516,74.0968
"4",16.9032,68.2258
"5",20.5161,72.3548
"6",21.1613,76.4839
"7",24.3929,83.6429
"8",26.4839,74.1935
"9",26.5645,71.2581
"10",27.9828,78.2069
"11",32.6833,79.0667
"12",33.0806,71.0968
"13",33.7097,76.6452
"14",34.2903,74.4516
"15",36,56.9677
"16",37.4167,79.8333
"17",43.9516,79.7097
"18",45.2667,76.9667
"19",47,76
"20",47.1129,78.0323
"21",47.3833,79.8333
"22",48.0968,73.9032
"23",49.05,78.1667
"24",57.5,81.7097
"25",59.2,80.3
"26",61.3226,75
"27",61.9194,87.0323
"28",62.3833,89.8
"29",64.3667,96.4
"30",65.371,88.9677
"31",68.35,91.3333
"32",70.7581,91.8387
"33",71.129,90.9355
"34",72.2419,93.4516
"35",72.85,97.8333
"36",73.9194,92.4839
"37",74.4167,96.1333
"38",76.3871,89.8387
"39",78.0484,89.4516

图形

enter image description here

答案 2 :(得分:2)

如果您正在寻找加入似乎是两条直线的双线,这条双曲线在两条线的交点处/附近具有可变半径(这是它的渐近线),我建议您努力寻找< strong>使用双曲线作为过渡模型来拟合双重直线数据,由 Donald G. Watts和David W. Bacon,Technometrics,Vol。 16,No。3(1974年8月),第369-373页

这个公式简单明了,可调节性好,就像魅力一样。从他们的论文(如果你无法访问它):

  

作为一种更有用的替代形式,我们考虑一种双曲线,其中:   (i)因变量y是自变量x的单值函数,
  (ii)左渐近线具有斜率theta_1
  (iii)右渐近线具有斜率theta_2
  (iv)渐近线在(x_o, beta_o)点交叉,
  (v)x = x_o处的曲率半径与数量增量成正比。这样的双曲线可以写成y = beta_o + beta_1*(x - x_o) + beta_2* SQRT[(x - x_o)^2 + delta^2/4],其中beta_1 = (theta_1 + theta_2)/2beta_2 = (theta_2 - theta_1)/2

delta是可调参数,允许您紧密跟随交叉点的直线或从一条线平滑地合并到另一条线。

只需求解交点(x_o, beta_o),然后插入上面的公式 顺便说一下,如果第1行是y_1 = b_1 + m_1 *x而第2行是y_2 = b_2 + m_2 * x,那么它们在x* = (b_2 - b_1) / (m_1 - m_2)y* = b_1 + m_1 * x*相交。因此,要与上述形式主义联系起来,x_o = x*beta_o = y*和两个m_*是两个主题。

答案 3 :(得分:0)

https://fr.scribd.com/document/380941024/Regression-par-morceaux-Piecewise-Regression-pdf中的第12-13页有一种简单明了的方法(无需迭代,无需初步猜测)

enter image description here

数据来自IanRoberts在其问题中发表的人物扫描图。扫描像素坐标不准确。因此,不要因其他偏差而感到惊讶。

请注意,已经将“ absciss”和“纵坐标”量表设计为1000。

两个部分的方程是

enter image description here

五个参数的近似值写在上图。