Python: подгонка кривой к замаскированным данным с помощью scipy curve_fit - PullRequest
1 голос
/ 27 марта 2020

Я пытаюсь написать скрипт с python / numpy / scipy для манипулирования данными, подгонки и построения графиков зависимых от угла измерений магнитосопротивления. Я новичок в Python, получил код фрейма от моего доктора философии и сумел добавить несколько сотен строк кода в фрейм. Через некоторое время я заметил, что в некоторых измерениях было несколько грубых ошибок, и, поскольку скрипт должен выполнять все манипуляции автоматически, я попытался замаскировать эти точки и подогнать кривую к немаскированным точкам (кривая представляет собой квадрат синуса, наложенный на линейную функцию, так что numpy .ma.polyfit на самом деле не является выбором). Однако после маскировки координат x и y проблемных c точек фитинг все равно будет учитывать их, даже если они не будут отображаться на графике. Пример упрощен, но происходит то же самое:

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



def Funk(x, k, y0):
 return k*x + y0   

fig,ax= plt.subplots()

x=ma.masked_array([1,2,3,4,5,6,7,8,9,10],mask=[0,0,0,0,0,0,1,1,1,1])
y=ma.masked_array([1,2,3,4,5,30,35,40,45,50], mask=[0,0,0,0,0,1,1,1,1,1])


fitParamsFunk, fitCovariancesFunk = curve_fit(Funk, x, y)

ax.plot(x, Funk(x, fitParamsFunk[0], fitParamsFunk[1]))
ax.errorbar(x, y, yerr = None, ms=3, fmt='-o')
plt.show()

Вторая половина точек замаскирована и не показана на графике, но все еще учитывается.

При написании поста я понял, что могу сделать это:

def Funk(x, k, y0):
    return k*x + y0   

fig,ax= plt.subplots()

x=np.array([1,2,3,4,5,6,7,8,9,10])
y=np.array([1,2,3,4,5,30,35,40,45,50])
mask=np.array([0,0,0,0,0,1,1,1,1,1])

fitParamsFunk, fitCovariancesFunk = curve_fit(Funk, x[mask], y[mask])

ax.plot(x, Funk(x, fitParamsFunk[0], fitParamsFunk[1]))
ax.errorbar(x, y, yerr = None, ms=3, fmt='-o')
plt.show()

Что я на самом деле хотел

Я думаю, что scipy curve_fit не предназначен иметь дело с замаскированными массивами, но я все еще хотел бы знать, есть ли обходной путь для этого (мне нужно работать с замаскированными массивами, потому что число точек данных> 10e6, но я заговариваю только 100 сразу, поэтому я потребуется ли взять маску части массива, которую я хочу построить, и назначить ее другому массиву, копируя значения массива в другой или устанавливая исходную маску в False)? Спасибо за любые предложения

Ответы [ 3 ]

0 голосов
/ 28 марта 2020

Использование маски в численном исчислении эквивалентно использованию функции шага Хевисайда в аналитическом исчислении. Например, это становится очень простым благодаря применению кусочно-линейной регрессии:

enter image description here

Это несколько примеров кусочно-линейной регрессии в статье: https://fr.scribd.com/document/380941024/Regression-par-morceaux-Piecewise-Regression-pdf

Используя метод, показанный в этой статье, очень простое исчисление ниже приводит к ожидаемой форме результата:

enter image description here

Примечание. В случае большого количества точек, если в переходной зоне было несколько точек с немного отличающимися абсциссами, было бы точнее применить рассмотренный случай на страницах 29-31 упомянутой выше статьи.

0 голосов
/ 01 апреля 2020

Я думаю, что вы хотите сделать, это определить маску, которая перечисляет индексы «хороших точек данных», а затем использовать ее как точки для подбора (и / или построения).

Как ведущий автор lmfit, я бы порекомендовал использовать эту библиотеку для подгонки кривых: она имеет много полезных функций, помимо curve_fit. При этом ваш пример может выглядеть так:

import numpy as np
import matplotlib.pyplot as plt
from lmfit import Model

def Funk(x, k, y0, good_points=None):  # note: add keyword argument
    f = k*x + y0
    if good_points is not None:
        f = f[good_points]       # apply mask of good data points
    return f

x = np.array([1,2,3,4,5, 6,7,8.,9,10])
y = np.array([1,2,3,4,5,30,35.,40,45,50]) 
y += np.random.normal(size=len(x), scale=0.19) # add some noise to make it fun

# make an array of the indices of the "good data points"
# does not need to be contiguous.
good_points=np.array([0,1,2,3,4])

# turn your model function Funk into an lmfit Model
mymodel = Model(Funk)

# create parameters, giving initial values. Note that parameters are
# named using the names of your function's argument and that keyword 
# arguments with non-numeric defaults like 'good points' are seen to
#  *not* be parameters. Like the independent variable `x`, you'll 
# need to pass that in when you do the fit.
# also: parameters can be fixed, or given `min` and `max` attributes

params = mymodel.make_params(k=1.4,  y0=0.2)
params['k'].min = 0

# do the fit to the 'good data', passing in the parameters, the 
# independent variable `x` and the `good_points` mask.
result  = mymodel.fit(y[good_points], params, x=x, good_points=good_points)

# print out a report of best fit values, uncertainties, correlations, etc.
print(result.fit_report())

# plot the results, again using the good_points array as needed.
plt.plot(x, y, 'o', label='all data')
plt.plot(x[good_points], result.best_fit[good_points], label='fit to good data')
plt.legend()
plt.show()

Это распечатает

[[Model]]
    Model(Funk)
[[Fit Statistics]]
    # fitting method   = leastsq
    # function evals   = 7
    # data points      = 5
    # variables        = 2
    chi-square         = 0.02302999
    reduced chi-square = 0.00767666
    Akaike info crit   = -22.9019787
    Bayesian info crit = -23.6831029
[[Variables]]
    k:   1.02460577 +/- 0.02770680 (2.70%) (init = 1.4)
    y0: -0.04135096 +/- 0.09189305 (222.23%) (init = 0.2)
[[Correlations]] (unreported correlations are < 0.100)
    C(k, y0) = -0.905

и даст график enter image description here

надеюсь, это поможет вам начать.

0 голосов
/ 27 марта 2020

Если вы хотите учитывать только действительные записи, вы можете использовать обратную маску в качестве индекса:

x = ma.masked_array([1,2,3,4,5,6,7,8,9,10], mask=[0,0,0,0,0,1,1,1,1,1])  # changed mask
y = ma.masked_array([1,2,3,4,5,30,35,40,45,50], mask=[0,0,0,0,0,1,1,1,1,1])

fitParamsFunk, fitCovariancesFunk = curve_fit(Funk, x[~x.mask], y[~y.mask])

PS: обратите внимание, что оба массива должны иметь одинаковое количество действительных записей.

...