<jupyter_start><jupyter_text>Lmfit Model ExampleThis notebook shows a simple example of using `lmfit.Model`. Preliminary imports:<jupyter_code>import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
#%config InlineBackend.figure_format='retina'  # for hi-dpi displays<jupyter_output><empty_output><jupyter_text>The ``Model`` class is a flexible, concise curve fitter. I will illustrate fitting example data to an exponential decay.<jupyter_code>def decay(t, N, tau):
    return N*np.exp(-t/tau)<jupyter_output><empty_output><jupyter_text>The parameters are in no particular order. We'll need some example data. I will use ``N=7`` and ``tau=3``, and I'll add a little noise.<jupyter_code>t = np.linspace(0, 5, num=1000)
data = decay(t, 7, 3) + np.random.randn(*t.shape)<jupyter_output><empty_output><jupyter_text>Simplest Usage<jupyter_code>from lmfit import Model

model = Model(decay, independent_vars=['t'])
result = model.fit(data, t=t, N=10, tau=1)<jupyter_output><empty_output><jupyter_text>The Model infers the parameter names by inspecting the arguments of the function, ``decay``. Then I passed the independent variable, ``t``, and initial guesses for each parameter. A residual function is automatically defined, and a least-squared regression is performed.We can immediately see the best-fit values<jupyter_code>result.values<jupyter_output><empty_output><jupyter_text>and easily pass those to the original model function for plotting:<jupyter_code>fig = result.plot(fig_kws={'figsize': (8, 7)})<jupyter_output><empty_output><jupyter_text>We can review the best-fit `Parameters` accessing `result.params`:<jupyter_code>result.params.pretty_print()<jupyter_output><empty_output><jupyter_text>More information about the fit is stored in the result,which is an [``lmfit.Mimimizer``](http://newville.github.io/lmfit-py/fitting.htmlMinimizer) object.  Specifying Bounds and Holding Parameters Constant Above, the ``Model`` class implicitly builds ``Parameter`` objects from keyword arguments of ``fit`` that match the argments of ``decay``. You can build the ``Parameter`` objects explicity; the following is equivalent.<jupyter_code>from lmfit import Parameter

result = model.fit(data, t=t, 
                   N=Parameter(value=10), 
                   tau=Parameter(value=1))
result.params<jupyter_output><empty_output><jupyter_text>By building ``Parameter`` objects explicitly, you can specify bounds (``min``, ``max``) and set parameters constant (``vary=False``).<jupyter_code>result = model.fit(data, t=t, 
                   N=Parameter(value=7, vary=False), 
                   tau=Parameter(value=1, min=0))
result.params<jupyter_output><empty_output><jupyter_text>Defining Parameters in Advance Passing parameters to ``fit`` can become unwieldly. As an alternative, you can extract the parameters from ``model`` like so, set them individually, and pass them to ``fit``.<jupyter_code>params = model.make_params()
params['N'].value = 10  # initial guess
params['tau'].value = 1
params['tau'].min = 0
result = model.fit(data, params, t=t)
result.params<jupyter_output><empty_output><jupyter_text>Keyword arguments override ``params``, resetting ``value`` and all other properties (``min``, ``max``, ``vary``).<jupyter_code>result = model.fit(data, params, t=t, tau=1)
result.params<jupyter_output><empty_output><jupyter_text>The input parameters are not modified by ``fit``. They can be reused, retaining the same initial value. If you want to use the result of one fit as the initial guess for the next, simply pass ``params=result.params``.  A Helpful Exception All this implicit magic makes it very easy for the user to neglect to set a parameter. The ``fit`` function checks for this and raises a helpful exception.<jupyter_code>result = model.fit(data, t=t, tau=1)  # N unspecified<jupyter_output><empty_output><jupyter_text>An *extra* parameter that cannot be matched to the model function will throw a ``UserWarning``, but it will not raise, leaving open the possibility of unforeseen extensions calling for some parameters.  Weighted Fits Use the ``sigma`` argument to perform a weighted fit. If you prefer to think of the fit in term of ``weights``, ``sigma=1/weights``.<jupyter_code>weights = np.arange(len(data))
result = model.fit(data, params, t=t, weights=weights)
result.params<jupyter_output><empty_output><jupyter_text>Handling Missing Data By default, attemping to fit data that includes a ``NaN``, which conventionally indicates a "missing" observation, raises a lengthy exception. You can choose to drop (i.e., skip over) missing values instead.<jupyter_code>data_with_holes = data.copy()
data_with_holes[[5, 500, 700]] = np.nan  # Replace arbitrary values with NaN.

model = Model(decay, independent_vars=['t'], missing='drop')
result = model.fit(data_with_holes, params, t=t)
result.params<jupyter_output><empty_output><jupyter_text>If you don't want to ignore missing values, you can set the model to raise proactively, checking for missing values before attempting the fit.<jupyter_code># Uncomment to see the error
#model = Model(decay, independent_vars=['t'], missing='raise')
#result = model.fit(data_with_holes, params, t=t)<jupyter_output><empty_output><jupyter_text>The default setting is ``missing='none'``, which does not check for NaNs. This interface is consistent with the ``statsmodels`` project.Null-chekcing  relies on ``pandas.isnull`` if it is available. If pandas cannot be imported, it silently falls back on ``numpy.isnan``.  Data Alignment Imagine a collection of time series data with different lengths. It would be convenient to define one sufficiently long array ``t`` and use it for each time series, regardless of length. The [``pandas``](http://pandas.pydata.org/pandas-docs/stable/) provides tools for aligning indexed data. And, unlike most wrappers to ``scipy.leastsq``, ``Model`` can handle pandas objects out of the box, using its data alignment features.Here I take just a slice of the ``data`` and fit it to the full ``t``. It is automatically aligned to the correct section of ``t`` using Series' index.<jupyter_code>from pandas import Series

model = Model(decay, independent_vars=['t'])
truncated_data = Series(data)[200:800]  # data points 200-800
t = Series(t)  # all 1000 points
result = model.fit(truncated_data, params, t=t)
result.params<jupyter_output><empty_output><jupyter_text>Data with missing entries and an unequal length still aligns properly.<jupyter_code>model = Model(decay, independent_vars=['t'], missing='drop')
truncated_data_with_holes = Series(data_with_holes)[200:800]
result = model.fit(truncated_data_with_holes, params, t=t)
result.params<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.11162651503815652, 'fraction_numerical': 0.008529103695944935, 'mean_word_length': 6.187096774193549, 'pattern_counts': {'":': 0, '<': 69, '<?xml version=': 0, '>': 69, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '17', 'dir': 'jupyter-structured-clean-dedup', 'id': '85529', 'license': 'BSD-3-Clause', 'n_tokens_mistral': 2095, 'n_tokens_neox': 1994, 'n_words': 687, 'path': 'examples/lmfit-model.ipynb', 'repo_name': 'andyfaff/lmfit-py', 'size': '6683'}