{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "%matplotlib inline" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\nFit using the Model interface\n=============================\n\nThis notebook shows a simple example of using the ``lmfit.Model`` class. For\nmore information please refer to:\nhttps://lmfit.github.io/lmfit-py/model.html#the-model-class.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import numpy as np\nfrom pandas import Series\n\nfrom lmfit import Model, Parameter, report_fit" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The ``Model`` class is a flexible, concise curve fitter. I will illustrate\nfitting example data to an exponential decay.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "def decay(t, N, tau):\n return N*np.exp(-t/tau)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The parameters are in no particular order. We'll need some example data. I\nwill use ``N=7`` and ``tau=3``, and add a little noise.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "t = np.linspace(0, 5, num=1000)\ndata = decay(t, 7, 3) + np.random.randn(*t.shape)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Simplest Usage**\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "model = Model(decay, independent_vars=['t'])\nresult = model.fit(data, t=t, N=10, tau=1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The Model infers the parameter names by inspecting the arguments of the\nfunction, ``decay``. Then I passed the independent variable, ``t``, and\ninitial guesses for each parameter. A residual function is automatically\ndefined, and a least-squared regression is performed.\n\nWe can immediately see the best-fit values:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "print(result.values)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "and use these best-fit parameters for plotting with the ``plot`` function:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "result.plot()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can review the best-fit `Parameters` by accessing `result.params`:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "result.params.pretty_print()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "More information about the fit is stored in the result, which is an\n``lmfit.MimimizerResult`` object (see:\nhttps://lmfit.github.io/lmfit-py/fitting.html#lmfit.minimizer.MinimizerResult)\n\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Specifying Bounds and Holding Parameters Constant**\n\nAbove, the ``Model`` class implicitly builds ``Parameter`` objects from\nkeyword arguments of ``fit`` that match the argments of ``decay``. You can\nbuild the ``Parameter`` objects explicity; the following is equivalent.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "result = model.fit(data, t=t,\n N=Parameter('N', value=10),\n tau=Parameter('tau', value=1))\nreport_fit(result.params)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "By building ``Parameter`` objects explicitly, you can specify bounds\n(``min``, ``max``) and set parameters constant (``vary=False``).\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "result = model.fit(data, t=t,\n N=Parameter('N', value=7, vary=False),\n tau=Parameter('tau', value=1, min=0))\nreport_fit(result.params)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Defining Parameters in Advance**\n\nPassing parameters to ``fit`` can become unwieldly. As an alternative, you\ncan extract the parameters from ``model`` like so, set them individually,\nand pass them to ``fit``.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "params = model.make_params()\n\nparams['N'].value = 10\nparams['tau'].value = 1\nparams['tau'].min = 0\n\nresult = model.fit(data, params, t=t)\nreport_fit(result.params)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Keyword arguments override ``params``, resetting ``value`` and all other\nproperties (``min``, ``max``, ``vary``).\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "result = model.fit(data, params, t=t, tau=1)\nreport_fit(result.params)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The input parameters are not modified by ``fit``. They can be reused,\nretaining the same initial value. If you want to use the result of one fit\nas the initial guess for the next, simply pass ``params=result.params``.\n\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#TODO/FIXME: not sure if there ever way a \"helpful exception\", but currently\n#it raises a ``ValueError: The input contains nan values``.\n\n#*A Helpful Exception*\n\n#All this implicit magic makes it very easy for the user to neglect to set a\n#parameter. The ``fit`` function checks for this and raises a helpful exception.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# #result = model.fit(data, t=t, tau=1) # N unspecified" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#An *extra* parameter that cannot be matched to the model function will\n#throw a ``UserWarning``, but it will not raise, leaving open the possibility\n#of unforeseen extensions calling for some parameters.\n\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "*Weighted Fits*\n\nUse the ``sigma`` argument to perform a weighted fit. If you prefer to think\nof the fit in term of ``weights``, ``sigma=1/weights``.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "weights = np.arange(len(data))\nresult = model.fit(data, params, t=t, weights=weights)\nreport_fit(result.params)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "*Handling Missing Data*\n\nBy default, attemping to fit data that includes a ``NaN``, which\nconventionally indicates a \"missing\" observation, raises a lengthy exception.\nYou can choose to ``omit`` (i.e., skip over) missing values instead.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "data_with_holes = data.copy()\ndata_with_holes[[5, 500, 700]] = np.nan # Replace arbitrary values with NaN.\n\nmodel = Model(decay, independent_vars=['t'], nan_policy='omit')\nresult = model.fit(data_with_holes, params, t=t)\nreport_fit(result.params)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you don't want to ignore missing values, you can set the model to raise\nproactively, checking for missing values before attempting the fit.\n\nUncomment to see the error\n#model = Model(decay, independent_vars=['t'], nan_policy='raise')\n#result = model.fit(data_with_holes, params, t=t)\n\nThe default setting is ``nan_policy='raise'``, which does check for NaNs and\nraises an exception when present.\n\nNull-chekcing relies on ``pandas.isnull`` if it is available. If pandas\ncannot be imported, it silently falls back on ``numpy.isnan``.\n\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "*Data Alignment*\n\nImagine a collection of time series data with different lengths. It would be\nconvenient to define one sufficiently long array ``t`` and use it for each\ntime series, regardless of length. ``pandas``\n(https://pandas.pydata.org/pandas-docs/stable/) provides tools for aligning\nindexed data. And, unlike most wrappers to ``scipy.leastsq``, ``Model`` can\nhandle pandas objects out of the box, using its data alignment features.\n\nHere I take just a slice of the ``data`` and fit it to the full ``t``. It is\nautomatically aligned to the correct section of ``t`` using Series' index.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "model = Model(decay, independent_vars=['t'])\ntruncated_data = Series(data)[200:800] # data points 200-800\nt = Series(t) # all 1000 points\nresult = model.fit(truncated_data, params, t=t)\nreport_fit(result.params)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Data with missing entries and an unequal length still aligns properly.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "model = Model(decay, independent_vars=['t'], nan_policy='omit')\ntruncated_data_with_holes = Series(data_with_holes)[200:800]\nresult = model.fit(truncated_data_with_holes, params, t=t)\nreport_fit(result.params)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.6" } }, "nbformat": 4, "nbformat_minor": 0 }