pandas_GUI.fit_Pandas_GUI

   1import sys
   2
   3import numpy as np
   4
   5def calcresid(result):
   6    '''
   7    lmfit has empty values for residuals where the weighting is infinite or not defined.
   8    This calculates all the residuals based on the actual data and fit results.
   9
  10    :param ModelResult result: An lmfit ModelResult.
  11
  12    :return np.array residuals: The residuals.
  13    '''
  14    import numpy as np
  15    import lmfit as lmfit
  16    resid = []
  17    for i in range(0, len(results.data)):
  18        resid.append(results.data[i] - results.best_fit[i])
  19    return np.array(resid)
  20
  21def fit_pandas_GUI(df_info=None, show_text_col = False, **kwargs):
  22    """
  23    If passed no parameters this will look for all the dataframes in the user
  24    namespace and make them available for plotting. Once a
  25    dataframe is chosen only the numerical columns from that dataframe will
  26    be available for inclusion in the plotting expression.
  27
  28    This GUI produces code to use the lmfit package to fit data and the plotly
  29    interactive plotting package to display the results.
  30
  31    If you wish to allow only certain dataframes or have them show up as
  32    user friendly names in the menus provide that information in the first
  33    paramater df_info.
  34
  35    To allow inclusion of text columns pass True for show_text_col.
  36
  37    :param bool show_text_col: (default = False). When True columns
  38    containing text will be shown.
  39
  40    :param list df_info: List of Lists [[object,globalname,
  41    userfriendly]],..]
  42      * object -- pandas.DataFrame
  43      * globalname -- string name of the object in the user global name space.
  44      * userfriendly -- string name to display for user selection.
  45      
  46    :keyword string figname: string used to override default python name for
  47    figure.
  48
  49    :keyword string fitname: string used to override default python name for
  50    fit.
  51    
  52    :keyword bool findframes: default = True. If set to false and dataframes
  53    are passed in dfs_info, will not search for dataframes in the user
  54    namespace.
  55    """
  56    from ipywidgets import Layout, Box, HBox, VBox, GridBox, Tab, \
  57        Accordion, Dropdown, Label, Text, Button, Checkbox, FloatText, \
  58        RadioButtons, BoundedIntText, Output
  59    from ipywidgets import HTML as richLabel
  60    from ipywidgets import HTMLMath as texLabel
  61    from IPython.display import display, HTML
  62    from IPython.display import Javascript as JS
  63    from IPython import get_ipython
  64    from JPSLUtils.utils import new_cell_immediately_below,\
  65        select_cell_immediately_below, move_cursor_in_current_cell, \
  66        insert_text_into_next_cell, insert_text_at_beginning_of_current_cell, \
  67        insert_newline_at_end_of_current_cell, select_containing_cell, \
  68        delete_selected_cell, iconselector, notice_group, \
  69        replace_text_of_current_cell, pseudoLatexToLatex
  70    from lmfit import models
  71
  72    from .utils import find_pandas_dataframe_names, build_run_snip_widget
  73    from IPython import get_ipython
  74    global_dict = get_ipython().user_ns
  75    JPSLUtils = global_dict["JPSLUtils"]
  76    if JPSLUtils.notebookenv != 'colab':
  77        import plotly.graph_objects as go
  78    dfs_info = []
  79    if isinstance(df_info,list):
  80        for k in df_info:
  81            dfs_info.append(k)
  82    findframes = kwargs.pop('findframes',True)
  83    if findframes:
  84        for k in find_pandas_dataframe_names():
  85            dfs_info.append([global_dict[k],k,k])
  86    friendly_to_globalname = {k[2]:k[1] for k in dfs_info}
  87    friendly_to_object = {k[2]:k[0] for k in dfs_info}
  88
  89    fitname = kwargs.pop('fitname',None)
  90    from .utils import find_fit_names
  91    fitlst = find_fit_names()
  92    if fitname in fitlst:
  93        raise UserWarning (str(fitname) + ' already exists. Choose a '
  94                                          'different name for the fit.')
  95    if fitname == None:
  96        fitname = 'Fit_'+str(len(fitlst)+1)
  97
  98    figname = kwargs.pop('figname',None)
  99    from .utils import find_figure_names
 100    figlst = find_figure_names()
 101    if figname in figlst:
 102        raise UserWarning (str(figname) + ' already exists. Choose a '
 103                                          'different name for the figure.')
 104    if figname == None:
 105        figname = str(fitname) + '_Figure'
 106
 107    fitmodels = ['LinearModel','PolynomialModel','ExponentialModel',
 108                 'GaussianModel','SineModel']
 109    fitmodeleqns = {
 110    'LinearModel':r'$fit = \color{red}{a}x+\color{red}{b}$, where $\color{'
 111                  r'red}{a}$ = slope, $\color{red}{b}$ = intercept',
 112    'PolynomialModel': r'$fit = \sum_{n=0}^{\le7}{\color{red}{c_n}x^n} = '
 113                       r'\color{red}{c_0} + \color{red}{c_1}x + \color{red}{'
 114                       r'c_2}x^2 + ...$',
 115    'ExponentialModel': r'$fit = \color{red}{A} \exp \left( \frac{-x} ' \
 116                        r'{\color{red}{\tau}}\right)$, where $\color{red}{A}$ '
 117                        r'= amplitude, $ \color{red}{\tau}$ = decay',
 118    'GaussianModel': r'$fit = \frac{\color{red}{A}}{\color{red}{\sigma} ' \
 119                     r'\sqrt{2 \pi}} \exp \left( \frac{-(x-\color{red}' \
 120                     r'{\mu})^2}{2 \color{red}{\sigma}^2} \right)$, where ' \
 121                     r'$\color{red}{A}$ = amplitude, $\color{red}{\sigma}$ = sigma, '
 122                     r'$\color{red}{\mu}$ = center',
 123        'SineModel': r'$fit = \color{red}{A} \sin \left ( \color{red}{f}x + '
 124                     r'\color{red}{\phi} \right)$, '
 125                     r'where $\color{red}{A}$ = ' \
 126                     r'amplitude, $\color{red}{f}$ = frequency, '\
 127                     r'$\color{red}{\phi}$ = shift'
 128    }
 129
 130    def polymodelresultstr(resultname):
 131        template = '' \
 132          'fitstr = r\'$fit = \'\n' \
 133          'termcount = int(0)\n' \
 134          'for k in %result.params.keys():\n' \
 135          '    pwr = int(str(k)[int(-1):])\n' \
 136          '    if %result.params[k].vary:\n' \
 137          '        if termcount > int(0):\n' \
 138          '            fitstr += \' + \'\n' \
 139          '        fitstr += r\'({%COLOR{red}{\'+rue.latex_rndwitherr(' \
 140                                         '%result.params[k].value,\n' \
 141          '                               %result.params[k].stderr, \n' \
 142          '                                errdig=int(1), \n' \
 143          '                                lowmag=-int(3))+\'}})\'\n' \
 144          '        if pwr == int(1):\n' \
 145          '            fitstr += \'x\'\n' \
 146          '        if pwr > int(1):\n' \
 147          '            fitstr += \'x^\'+str(pwr)\n' \
 148          '        termcount+=int(1)\n' \
 149          '    else:\n' \
 150          '        if %result.params[k].value!=0.0:\n' \
 151          '            if termcount > int(0):\n' \
 152          '                fitstr += \'+\'\n' \
 153          '            fitstr += r\'({%COLOR{blue}{\'+str(' \
 154                                         '%result.params[k].value)+\'}})\'\n' \
 155          '            termcount +=int(1)\n' \
 156          '            if pwr == int(1):\n' \
 157          '                fitstr += \'x\'\n' \
 158          '            if pwr > int(1):\n' \
 159          '                fitstr += \'x^\'+str(pwr)\n' \
 160          'fitstr+=\'$\'\n' \
 161          'captionstr=r\'<p>Use the command <code>%result</code> as the ' \
 162          'last line of a code cell for more details.</p>\'\n' \
 163          'display(Math(fitstr))\n' \
 164          'display(HTML(captionstr))'
 165        return template.replace('%result', str(resultname))
 166
 167    def linmodelresultstr(resultname):
 168        template = '' \
 169       'slopestr = ''\'\'\n' \
 170       'interceptstr = ''\'\'\n' \
 171       'for k in %results.params.keys():\n' \
 172       '    if %results.params[k].vary:\n' \
 173       '        paramstr = r\'({%COLOR{red}{\'+rue.latex_rndwitherr(' \
 174                   '%results.params[k].value,\n' \
 175       '                                       %results.params[k].stderr,\n' \
 176       '                                       errdig=int(1),\n' \
 177       '                                       lowmag=-int(3))+\'}})\'\n' \
 178       '    else:\n' \
 179       '        paramstr = r\'{%COLOR{blue}{\'+str(%results.params[' \
 180                   'k].value,' \
 181                   '\n' \
 182       '                                       )+\'}}\'\n' \
 183       '    if k == \'slope\':\n' \
 184       '        slopestr = paramstr\n' \
 185       '    if k == \'intercept\' and %results.params[k].value != 0:\n' \
 186       '        interceptstr = \' + \' + paramstr\n' \
 187       'fitstr = r\'$fit = \'+slopestr + \'x\' + interceptstr + \'$\'\n' \
 188       'captionstr = r\'<p>Use the command <code>%results</code> as the ' \
 189       'last line of a code cell for more details.</p>\'\n' \
 190       'display(Math(fitstr))\n' \
 191       'display(HTML(captionstr))'
 192        return template.replace('%results', resultname)
 193
 194    def expmodelresultstr(resultname):
 195        template = '' \
 196        'ampstr = ''\'\'\n' \
 197        'decaystr = ''\'\'\n' \
 198        'for k in %results.params.keys():\n' \
 199        '    if %results.params[k].vary:\n' \
 200        '        paramstr = r\'({%COLOR{red}{\'+rue.latex_rndwitherr(' \
 201        '%results.params[k].value,\n' \
 202        '                                 %results.params[k].stderr,\n' \
 203        '                                 errdig=int(1),\n' \
 204        '                                 lowmag=-int(3))+\'}})\'\n' \
 205        '    else:\n' \
 206        '        paramstr = r\'{%COLOR{blue}{\'+str(%results.params[' \
 207                                               'k].value, \n' \
 208        '                                       )+\'}}\'\n' \
 209        '    if k == \'amplitude\':\n' \
 210        '        ampstr = paramstr\n' \
 211        '    if k == \'decay\':\n' \
 212        '        decaystr = paramstr\n' \
 213        'fitstr = r\'$$fit = \'+ampstr+r\'%EXP %LEFT( %FRAC{-x}' \
 214                        '{\'+decaystr+r\'}%RIGHT)$$\'\n' \
 215        'captionstr = r\'<p>Use the command <code>%results</code> as the ' \
 216        'last line of a code cell for more details.</p>\'\n' \
 217        'display(Math(fitstr))\n' \
 218        'display(HTML(captionstr))'
 219        return template.replace('%results',resultname)
 220
 221    def gausmodelresultstr(resultname):
 222        template = '' \
 223        'ampstr = ''\'\'\n' \
 224        'centstr = ''\'\'\n' \
 225        'sigmastr = ''\'\'\n' \
 226        'for k in %results.params.keys():\n' \
 227        '    if %results.params[k].vary:\n' \
 228        '        paramstr = r\'({%COLOR{red}{\'+rue.latex_rndwitherr(' \
 229                                          '%results.params[k].value,\n' \
 230        '                                 %results.params[k].stderr,\n' \
 231        '                                 errdig=int(1),\n' \
 232        '                                 lowmag=-int(3))+\'}})\'\n' \
 233        '    else:\n' \
 234        '        paramstr = r\'{%COLOR{blue}{\'+str(%results.params[' \
 235                                               'k].value, \n' \
 236        '                                       )+\'}}\'\n' \
 237        '    if k == \'amplitude\':\n' \
 238        '        ampstr = paramstr\n' \
 239        '    if k == \'center\':\n' \
 240        '        centstr = paramstr\n' \
 241        '    if k == \'sigma\':\n' \
 242        '        sigmastr = paramstr\n' \
 243        'fitstr = r\'$$fit = %FRAC{\'+ampstr+\'}{' \
 244                   '\'+sigmastr+r\'%SQRT{2%PI}}%EXP %LEFT( %FRAC{' \
 245                   '-%LEFT[x-\'+centstr+r\'%RIGHT]^2}' \
 246                   '{2\'+sigmastr+r\'^2}%RIGHT)$$\'\n' \
 247        'captionstr = r\'<p>Use the command <code>%results</code> as the ' \
 248        'last line of a code cell for more details.</p>\'\n' \
 249        'display(Math(fitstr))\n' \
 250        'display(HTML(captionstr))'
 251        return template.replace('%results',resultname)
 252
 253    def sinmodelresultstr(resultname):
 254        template = '' \
 255       'ampstr = \'\'\n' \
 256       'freqstr = \'\'\n' \
 257       'shiftstr = \'\'\n' \
 258       'for k in %results.params.keys():\n' \
 259       '    if %results.params[k].vary:\n' \
 260       '        if %results.params[k].stderr:\n' \
 261       '            paramstr = r\'({%COLOR{red}{\'+rue.latex_rndwitherr(' \
 262                   '%results.params[k].value,\n' \
 263       '                                       %results.params[k].stderr,\n' \
 264       '                                       errdig=int(1),\n' \
 265       '                                       lowmag=-int(3))+\'}})\'\n' \
 266       '        else:\n' \
 267       '            paramstr = r\'{%COLOR{red}{\'+' \
 268                   'str(%results.params[k].value)+\'}}\'\n' \
 269       '    else:\n' \
 270       '        paramstr = r\'{%COLOR{blue}{\'+str(%results.params[k].value' \
 271                   ')+\'}}\'\n' \
 272       '    if k == \'amplitude\':\n' \
 273       '        ampstr = paramstr\n' \
 274       '    if k == \'frequency\':\n' \
 275       '        freqstr = paramstr\n' \
 276       '    if k == \'shift\' and %results.params[k].value != 0.0:\n' \
 277       '        shiftstr = \' + \' + paramstr\n' \
 278       'fitstr = r\'$fit = \'+ampstr + \'sin[\' + freqstr + \'x\' + shiftstr + \']$\'\n' \
 279       'captionstr = r\'<p>Use the command <code>%results</code> as the ' \
 280       'last line of a code cell for more details.</p>\'\n' \
 281       'display(Math(fitstr))\n' \
 282       'display(HTML(captionstr))'
 283        return template.replace('%results', resultname)
 284
 285    fitresultstrs = {
 286    'LinearModel': linmodelresultstr,
 287    'PolynomialModel': polymodelresultstr,
 288    'ExponentialModel': expmodelresultstr,
 289    'GaussianModel': gausmodelresultstr,
 290    'SineModel': sinmodelresultstr
 291    }
 292
 293    importstr = '# CODE BLOCK generated using fit_pandas_GUI().\n# See '\
 294                'https://jupyterphysscilab.github.io/jupyter_Pandas_GUI.\n' \
 295                '# Integers wrapped in `int()` to avoid having them cast \n' \
 296                '# as other types by interactive preparsers. \n' \
 297                '# Imports (no effect if already imported)\n' \
 298                'import numpy as np\n' \
 299                'import lmfit as lmfit\n' \
 300                'import round_using_error as rue\n' \
 301                'import copy as copy\n' \
 302                'from plotly import graph_objects as go\n' \
 303                'from IPython.display import HTML, Math\n\n'
 304    step1str = ''
 305    step2str = ''
 306    step3str = ''
 307    step4str = ''
 308    step5str = ''
 309    step6str = ''
 310    range_chosen = False
 311    #### Define GUI Elements ####
 312    # Those followed by a * are required.
 313    output = Output()
 314    with output:
 315        display(HTML(
 316            "<h3 id ='pandasfitGUI' style='text-align:center;'>Pandas Fit "
 317            "Composer</h3> <div style='text-align:center;'>"
 318            "<span style='color:green;'>Steps with a * are required.</span> The "
 319            "code that will generate the fit is being "
 320            "built in the textbox or cell immediately below.</div><div "
 321            "style='text-align:center;'>This composer uses a subset of "
 322            "<a href ='https://lmfit.github.io/lmfit-py/'> the lmfit package</a>"
 323            " and <a href ='https://plotly.com/python/line-and-scatter/#'> "
 324            "the plotly scatter plot</a> capabilities.</div>"))
 325
 326    longdesc = {'description_width':'initial'}
 327
 328    # Notices for the Final Check Tab.
 329    makeplot_notices = notice_group(['Need data to fit',
 330                                     'Need a fit model',
 331                                     'Axes must have labels.'],
 332                                    'Notices:','','red')
 333    makeplot_notices.set_active([0,1,2])
 334
 335    # 1. Pick and variables to fit and fit model
 336    #   a. Select Y vs. X (DataFrame, X and Y, which must be from single
 337    #       frame.
 338    # Notices for the Pick Trace(s) tab.
 339    notice_list = [
 340        'Data set (DataFrame) required.',
 341        'X- and Y-values required.',
 342    ]
 343    trace_notices = notice_group(notice_list, 'Notices:','','red')
 344    trace_notices.set_active([0,1])
 345    step1instr = richLabel(value = '<ol><li>Select a DataFrame (Data '
 346                                   'set);</li>'
 347                                   '<li>Select the column containing the X '
 348                                   'values;</li>'
 349                                   '<li>Select the column containing the Y '
 350                                   'values (what is being fit);</li>'
 351                                   '<li>Provide a name for the trace if you do'
 352                                   ' not like the default. This text will be '
 353                                   'used for the legend;</li></ol>'
 354                           )
 355    step1instracc = Accordion(children = [step1instr])
 356    step1instracc.set_title(0,'Instructions')
 357    step1instracc.selected_index = None
 358
 359    # DataFrame selection
 360    tempopts = []
 361    tempopts.append('Choose data set.')
 362    for k in dfs_info:
 363        tempopts.append(k[2])
 364    whichframe = Dropdown(options=tempopts,
 365                                description='DataFrame: ',)
 366
 367    def update_columns(change):
 368        if change['new'] == 'Choose data set.':
 369            Xcoord.disabled = True
 370            Ycoord.disabled = True
 371            trace_notices.activate_notice(0)
 372            trace_notices.activate_notice(1)
 373            trace_notices.activate_notice(2)
 374            add_trace_notices.value = trace_notices.notice_html()
 375            return
 376        df = friendly_to_object[change['new']]
 377        tempcols = df.columns.values
 378        tempopt = ['Choose column for coordinate.']
 379        for k in tempcols:
 380            if show_text_col:
 381                tempopt.append(k)
 382            else:
 383                if df[k].dtype != 'O':
 384                    tempopt.append(k)
 385        Xcoord.options = tempopt
 386        Xcoord.value = tempopt[0]
 387        Ycoord.options = tempopt
 388        Ycoord.value = tempopt[0]
 389        Xcoord.disabled = False
 390        Ycoord.disabled = False
 391        trace_notices.activate_notice(1)
 392        trace_notices.deactivate_notice(0)
 393        add_trace_notices.value =trace_notices.notice_html()
 394        pass
 395    whichframe.observe(update_columns, names='value')
 396
 397    # Data selection
 398    Xcoord = Dropdown(options=['Choose X-coordinate.'],
 399                           description='X: ',
 400                           disabled = True)
 401    Ycoord = Dropdown(options=['Choose Y-coordinate.'],
 402                           description='Y: ',
 403                           disabled = True)
 404    def trace_name_update(change):
 405        if change['new'] != 'Choose column for coordinate.':
 406            trace_name.value = Ycoord.value
 407        if Xcoord.value != 'Choose column for coordinate.' and Ycoord.value \
 408                != 'Choose column for coordinate.':
 409            yerrtype.disabled = False
 410            trace_name.disabled = False
 411            trace_notices.deactivate_notice(1)
 412            add_trace_notices.value = trace_notices.notice_html()
 413        else:
 414            yerrtype.disabled = True
 415            trace_name.disabled = True
 416            trace_notices.activate_notice(1)
 417            add_trace_notices.value = trace_notices.notice_html()
 418        pass
 419    Xcoord.observe(trace_name_update,names='value')
 420    Ycoord.observe(trace_name_update,names='value')
 421
 422    # Trace name
 423    trace_name = Text(placeholder = 'Trace name for legend',
 424                      description = 'Trace name: ',
 425                      disabled = True)
 426
 427    trace_notices.set_active([0,1])
 428    add_trace_notices = richLabel(value = trace_notices.notice_html())
 429    step1tracebox =  VBox(children=[whichframe,Xcoord,Ycoord,trace_name])
 430    step1actionbox =  VBox(children=[add_trace_notices])
 431    step1hbox =  HBox(children=[step1tracebox,step1actionbox])
 432    step1 =  VBox(children=[step1instracc, step1hbox])
 433
 434    # 2. Set data uncertainty
 435    step2instr = richLabel(value = 'If you know the uncertainty in your data '
 436                                   'values (Y-values)you should '
 437                                   'specify it, as the uncertainty impacts '
 438                                   'the final uncertainty in the fit '
 439                                   'parameters. '
 440                                   'If you do not know the uncertainty of '
 441                                   'your data leave the "Error Type" as '
 442                                   '"none". In this case all the data values '
 443                                   'will be equally weighted during the fit. '
 444                                   'Alternatives are: a constant uncertainty '
 445                                   'that is the same for every data point; a '
 446                                   'percentage of each value; data (a '
 447                                   'column) specifying the uncertainty for '
 448                                   'every data point.')
 449
 450    yerrtype = Dropdown(options = ['none','percent','constant','data'],
 451                        description = 'Error Type: ',
 452                        disabled = True)
 453
 454    def error_settings_OK():
 455        check = True
 456        if (yerrtype.value == 'data') and (yerrdata.value == 'Choose error '
 457                                                        'column.'):
 458            check = False
 459        return check
 460
 461    def yerr_change(change):
 462        df = friendly_to_object[whichframe.value]
 463        if change['new'] == 'percent' or change['new'] == 'constant':
 464            yerrvalue.disabled = False
 465            yerrdata.disabled = True
 466        if change['new'] == 'data':
 467            yerrvalue.disabled = True
 468            tempopts = ['Choose error column.']
 469            tempcols = df.columns.values
 470            for k in tempcols:
 471                if df[k].dtype != 'O':
 472                    tempopts.append(k)
 473            yerrdata.options=tempopts
 474            yerrdata.disabled = False
 475        if change['new'] == 'none':
 476            yerrvalue.disabled = True
 477            yerrdata.disabled = True
 478        add_trace_notices.value = trace_notices.notice_html()
 479        pass
 480
 481    yerrtype.observe(yerr_change, names = 'value')
 482
 483    yerrvalue = FloatText(description = '% or constant: ', disabled = True,
 484                          style=longdesc, value=1.0)
 485    yerrdata = Dropdown(options = ['Choose error column.'],
 486                        description = 'Error values: ',
 487                        disabled = True)
 488
 489    def errdata_change(change):
 490        if error_settings_OK():
 491            #trace_notices.deactivate_notice(2)
 492            pass
 493        else:
 494            #trace_notices.activate_notice(2)
 495            pass
 496        add_trace_notices.value = trace_notices.notice_html()
 497        pass
 498
 499    yerrdata.observe(errdata_change, names = 'value')
 500    yerrrow1 =  HBox(children=[yerrtype,yerrvalue])
 501    yerror =  VBox(children=[yerrrow1,yerrdata])
 502    step2instracc = Accordion(children=[step2instr])
 503    step2instracc.selected_index = None
 504    step2 =  VBox(children=[step2instr,yerror])
 505
 506    # 3. Set fit parameters
 507    step3instr = richLabel(value = '<ol><li>Choose the fit type ('
 508                                   'functional form). <span '
 509                                   'style="color:red">Red symbols are '
 510                                   'the fit parameters.</span></li>'
 511                                   '<li>You may use the default settings for '
 512                                   'the '
 513                                   'initial guesses and parameter ranges or '
 514                                   'you may set them.</li>'
 515                                   '<li>To fix a value at the '
 516                                   'initial guess select the "fix" checkbox. '
 517                                   'You must provide an initial guess if you '
 518                                   'fix a parameter.</li></ol>')
 519    step3instracc = Accordion(children = [step3instr])
 520    step3instracc.set_title(0,'Instructions')
 521    step3instracc.selected_index = None
 522    # get selected fit model and update parameters list.
 523    modeldrop = Dropdown(options=fitmodels)
 524    modeleqn = texLabel(value = fitmodeleqns[modeldrop.value])
 525    def getcurrmodel_param(modelname, params_set):
 526        '''
 527        Using the model name return ipywidgets for setting the fit 
 528        parameters and constraints, populated with the default values.
 529        :param string modelname: The string name for the lmfit model.
 530        :param VBox params_set: The VBox containing the HBoxes for parameter
 531            guesses and constraints.
 532        :return VBox: params_set with fields reset and those available visible.
 533        '''
 534        currmodel = getattr(models,modelname)()
 535        currmodel_param = []
 536        labeltext = ''
 537        fix = False
 538        value = 0
 539        min = -sys.float_info.max
 540        max = sys.float_info.max
 541        expr = None  # Not used, maybe for arbitrary functions.
 542        for i in range(0,8):
 543            fix = False
 544            if i < len(currmodel.param_names):
 545                labeltext = str(currmodel.param_names[i])
 546                hints = currmodel.param_hints.get(labeltext,None)
 547                if isinstance(hints,dict):
 548                    fix = not(hints.get('vary',True))
 549                    value = hints.get('value', 0)
 550                    min = hints.get('min', -sys.float_info.max)
 551                    max = hints.get('max', sys.float_info.max)
 552                    expr = hints.get('expr',None)
 553                params_set.children[i].layout.display=''
 554                if modelname == 'ExponentialModel':
 555                    df = friendly_to_object[whichframe.value]
 556                    xvals = df[Xcoord.value]
 557                    yvals = df[Ycoord.value]
 558                    if labeltext == 'amplitude':
 559                        value = np.mean(yvals)
 560                    if labeltext == 'decay':
 561                        value = (np.max(xvals) - np.min(xvals))/3.0
 562                if modelname == 'GaussianModel':
 563                    df = friendly_to_object[whichframe.value]
 564                    xvals = df[Xcoord.value]
 565                    yvals = df[Ycoord.value]
 566                    if labeltext == 'amplitude':
 567                        value = np.max(yvals)
 568                    if labeltext == 'sigma':
 569                        value = 1.0
 570                    if labeltext == 'center':
 571                        maxidx = np.argmax(yvals)
 572                        value = xvals[maxidx]
 573                if modelname == 'SineModel':
 574                    df = friendly_to_object[whichframe.value]
 575                    xvals = df[Xcoord.value]
 576                    yvals = df[Ycoord.value]
 577                    if labeltext == 'amplitude':
 578                        value = np.max(yvals)
 579                    if labeltext == 'frequency':
 580                        # Looking for the most prominent frequency component 
 581                        # to fit.
 582                        temprange = np.max(xvals) - np.min(xvals)
 583                        npts = len(xvals)
 584                        tempfft = np.fft.fft(yvals)[:int(npts/2)]
 585                        maxloc = np.argmax(np.absolute(tempfft))
 586                        value = maxloc*temprange/npts
 587            else:
 588                labeltext = str(i)
 589                params_set.children[i].layout.display='none'
 590            params_set.children[i].children[0].value = labeltext+':'
 591            params_set.children[i].children[1].children[0].value = fix
 592            params_set.children[i].children[1].children[1].value = value
 593            params_set.children[i].children[1].children[2].value = min
 594            params_set.children[i].children[1].children[3].value = max
 595        pass
 596
 597    def make_param_set():
 598        '''
 599        Creates a VBox with 7 parameters each having fields in an HBox:
 600        1. fixcheck (checkbox for fixing the value)
 601        2. valuefield (floatText for setting the value)
 602        3. minfield (floatText for setting the minimum allowed value)
 603        4. maxfield (floatText for setting the maximum allowed value)
 604        By default the all VBox components have their `layout.display=none`.
 605        :return: VBox
 606        '''
 607        import sys
 608        currmodel_param=[]
 609        for i in range (0,8):
 610            fixcheck = Checkbox(value=False,
 611                                description='Fix (hold)',
 612                                disabled=False,
 613                                style=longdesc)
 614            valuefield = FloatText(value=0,
 615                                   description='Value: ',
 616                                   disabled=False,
 617                                   style=longdesc)
 618            minfield = FloatText(value=-sys.float_info.max,
 619                                 description='Min: ',
 620                                 disabled=False,
 621                                 style=longdesc)
 622            maxfield = FloatText(value=sys.float_info.max,
 623                                 description='Max: ',
 624                                 disabled=False,
 625                                 style=longdesc)
 626            paramlabel = Label(value = str(i)+':',style=longdesc)
 627            parambox =  VBox(children=[paramlabel, HBox(children=[fixcheck,valuefield,minfield,
 628                                    maxfield])])
 629            parambox.layout.display = 'none'
 630            currmodel_param.append(parambox)
 631        params_set = VBox(currmodel_param)
 632        return params_set
 633
 634    def modeldrop_change(change):
 635        modeleqn.value=fitmodeleqns[modeldrop.value]
 636        getcurrmodel_param(modeldrop.value,params_set)
 637        pass
 638    modeldrop.observe(modeldrop_change, names = 'value')
 639    params_set = make_param_set()
 640    getcurrmodel_param(modeldrop.value, params_set)
 641    step3 =  VBox(children=[step3instracc, HBox(children=[modeldrop,modeleqn]),params_set])
 642
 643    # 5.Title, Axes, Format ...
 644    step5instr = richLabel(value = '<ul><li><span '
 645                                   'style="font-weight:bold;">You '
 646                                   'must set the axes labels to something '
 647                           'appropriate.</span> For example if the X - values '
 648                           'represent time in seconds "Time (s)" is a good '
 649                           'choice. Likewise, choose an appropriate label '
 650                           'for the Y - axis.</li>'
 651                           '<li>If the Aspect Ratio is set to `auto` the '
 652                           'figure will fill the default output region. '
 653                           'Other choices will allow you to pick the Plot '
 654                                   'Size. `Large` will use about 2/3 of an HD '
 655                                   '(1920X1080) screen.</li></ul>')
 656    plot_title = Text(value = figname,
 657                       description = 'Plot title: ',
 658                      layout = Layout(width='80%'))
 659    X_label = Text(placeholder = 'Provide an X-axis label (usually has units)',
 660                   description = 'X-axis label: ',
 661                   style = longdesc,
 662                   layout=Layout(width='45%'))
 663    Y_label = Text(placeholder = 'Provide a Y-axis label (usually has units)',
 664                   description = 'Y-axis label: ',
 665                   style = longdesc,
 666                   layout=Layout(width='45%'))
 667    def mirror_axes_change(change):
 668        if change['new']:
 669            mirror_ticks.disabled= False
 670        else:
 671            mirror_ticks.disabled= True
 672            mirror_ticks.value = False
 673        pass
 674
 675    mirror_axes = Checkbox(value = False,
 676                           description = 'Display Mirror Axes',
 677                           style = longdesc)
 678    mirror_axes.observe(mirror_axes_change, names = 'value')
 679    mirror_ticks = Checkbox(value = False,
 680                            description = 'Mirror Tick Marks',
 681                            disabled = True)
 682
 683    def aspect_change(change):
 684        if change['new'] != 'auto':
 685            plot_size.disabled=False
 686        else:
 687            plot_size.disabled=True
 688        pass
 689
 690    plot_aspect = Dropdown(options = ['auto', '16:9', '5:3', '7:5', '4:3',
 691                                     '10:8', '1:1'],
 692                      value = 'auto',
 693                      description = 'Aspect Ratio: ',
 694                      style = longdesc)
 695    plot_aspect.observe(aspect_change, names = 'value')
 696    plot_size = Dropdown(options = ['tiny', 'small', 'medium', 'large',
 697                                    'huge'],
 698                         value = 'large',
 699                         description = 'Plot Size: ',
 700                         style = longdesc,
 701                         disabled = True)
 702    plot_template = Dropdown(options=['none','simple_white', 'ggplot2',
 703                                    'seaborn',
 704                                 'plotly', 'plotly_white', 'plotly_dark',
 705                                 'presentation', 'xgridoff', 'ygridoff',
 706                                 'gridon', 'simple_white+presentation',
 707                                      'simple_white+gridon', 
 708                                      'simple_white+presentation+gridon'],
 709                        value='simple_white',
 710                        description = 'Plot Styling: ',
 711                        style = longdesc)
 712    step5hbox1 =  HBox(children=[X_label, Y_label])
 713    step5hbox2 =  HBox(children=[mirror_axes,mirror_ticks, plot_template,
 714                                 plot_aspect, plot_size])
 715    step5 =  VBox(children=[step5instr, plot_title, step5hbox1, step5hbox2])
 716
 717    # 4. Pick Fit Range(s)
 718    step4instr = richLabel(value ='This step is optional. '
 719                                  'If you define no range(s) all data '
 720                                  'points will be used in the fit. <ul>'
 721                                  '<li> Click on points to select the '
 722                                  'beginning and ending of each range of '
 723                                  'data to include in the fit.</li>'
 724                                  '<li> Click again on '
 725                                  'a point to deselect it.</li>'
 726                                  '<li> Nearest neighbor pairs of points '
 727                                  'starting with the lowest point index '
 728                                  'number are used to define each range. If '
 729                                  'you select an odd number of points, '
 730                                  'the last point will be ignored.</li>'
 731                                  '<li> Check the `Extend fitted function '
 732                                  'plot` box if you want to display '
 733                                  'calculations of the fitted function and '
 734                                  'residuals in regions that were not fit '
 735                                  'to.</li></ul>')
 736    extend_fit = Checkbox(value=False,
 737                           description='Extend fitted function plot',
 738                           style=longdesc)
 739    range_plot = None
 740    if JPSLUtils.notebookenv == 'colab':
 741        range_plot = richLabel(value = '<span style="color:blue;">' \
 742                               'Unfortunately, defining a range by clicking ' \
 743                               'on the graph is not yet supported in Google' \
 744                               'Colab.</span>')
 745    else:
 746        range_plot =  go.FigureWidget(layout_template='simple_white')
 747        range_plot_line_color = 'blue'
 748        range_plot_hilight = 'cyan'
 749        range_plot_marker_size = 6
 750        range_plot_hilight_size = 20
 751        ranges=[]
 752        
 753    def update_range_point(trace, points, selector):
 754        # size and color must be done separately as they may not be updated
 755        # in sync.
 756        try:
 757            from collections.abc import Iterable
 758        except ImportError(e):
 759            from collections import Iterable            
 760        if not isinstance(trace['marker']['size'],Iterable):
 761            s = [range_plot_marker_size]*len(trace['x'])
 762        else:
 763            s = list(trace['marker']['size'])
 764        if (not isinstance(trace['marker']['color'],Iterable)) or isinstance(
 765                trace['marker']['color'],str):
 766            c = [range_plot_line_color]*len(trace['x'])
 767        else:
 768            c = list(trace['marker']['color'])
 769        for i in points.point_inds:
 770            if c[i]==range_plot_line_color:
 771                c[i] = range_plot_hilight
 772                s[i] = range_plot_hilight_size
 773            else:
 774                c[i] = range_plot_line_color
 775                s[i] = range_plot_marker_size
 776        with range_plot.batch_update():
 777            trace.marker.color = c
 778            trace.marker.size = s
 779        pass
 780    step4instacc = Accordion(children =[step4instr])
 781    step4instacc.set_title(0,'Instructions (optional step)')
 782    step4instacc.selected_index = None
 783    step4 =  VBox(children=[step4instacc,extend_fit,range_plot])
 784
 785    # 6. Final Check*
 786    step6instr = richLabel(value = 'Things to check before running the fit:' \
 787                                   '<ul><li>Fix any problems listed in ' \
 788                                   '"Notices".</li>' \
 789                                   '<li>Check for any unpaired parentheses, ' \
 790                                   'brackets or braces.</li>' \
 791                                   '<li>Check that all single and double ' \
 792                                   'quotes are paired.</li>' \
 793                                   '<li>If you did any manual editing ' \
 794                                   'double-check for typos.</li>')
 795    step6noticebox = richLabel(value = makeplot_notices.notice_html())
 796
 797    def dofit_click(change):
 798        if JPSLUtils.notebookenv == 'NBClassic':
 799            # Commented out do nothing because of timing issues
 800            #text = '\n# Force save widget states so that graph will still be\n'
 801            #text += '# available when notebook next opened in trusted state.\n'
 802            #text += 'import time\ntime.sleep(5)'
 803            select_containing_cell('pandasfitGUI')
 804            select_cell_immediately_below()
 805            #insert_newline_at_end_of_current_cell(text)
 806            #jscode = 'Jupyter.actions.call("widgets:save-with-widgets");'
 807            #text = 'JPSLUtils.OTJS(\''+jscode+'\')'
 808            #insert_newline_at_end_of_current_cell(text)
 809            # run the cell to build the plot
 810            JPSLUtils.OTJS('Jupyter.notebook.get_selected_cell().execute()')
 811            # remove the GUI cell
 812            select_containing_cell('pandasfitGUI')
 813            delete_selected_cell()
 814        pass
 815
 816    dofitbut_lay = Layout(visibility = "hidden")
 817    if JPSLUtils.notebookenv == 'NBClassic':
 818        dofitbut_lay = Layout(visibility="visible")
 819    dofitbut = Button(description = 'Do Fit',
 820                      disabled = True,
 821                      layout = dofitbut_lay)
 822    dofitbut.on_click(dofit_click)
 823    step6vbox =  VBox(children=[dofitbut,step6noticebox])
 824    step6 =  HBox(children=[step6instr,step6vbox])
 825
 826
 827    steps =  Tab(children=[step1, step2, step3, step4, step5, step6])
 828    steps.set_title(0,'1. Pick Data*')
 829    steps.set_title(1,'2. Data Uncertainty*')
 830    steps.set_title(2,'3. Set up Model*')
 831    steps.set_title(3,'4. Pick Fit Range(s)')
 832    steps.set_title(4, '5. Axes & Format*')
 833    steps.set_title(5, '6. Final Check*')
 834
 835    def tab_changed(change):
 836        nonlocal importstr, step1str, step2str, step3str, step4str, step5str, \
 837            range_chosen
 838        dfname = friendly_to_globalname[whichframe.value]
 839        if change['old'] == 0:
 840            # Update step 1 string
 841            step1str = '# Define data and trace name\n'
 842            step1str += 'Xvals = '+dfname+'[\"'
 843            step1str += str(Xcoord.value)+'\"]\n'
 844            step1str += 'Yvals = ' + dfname +'[\"'
 845            step1str += str(Ycoord.value)+'\"]\n'
 846            step1str += 'tracename = \"'+str(trace_name.value)+'\"\n\n'
 847            pass
 848        if change['old'] == 1:
 849            # TODO need do something in case  tab is changed before a click
 850            # occurs outside a box that was just change. blur things will
 851            # require ipywidgets v8+
 852            # update step 2 string
 853            step2str = '# Define error (uncertainty)\n'
 854            if yerrtype.value == 'none':
 855                step2str += 'Yerr = ' + dfname + '[\"'
 856                step2str += str(Ycoord.value) + '\"]*0.0 + 1.0\n\n'
 857            if yerrtype.value=='constant':
 858                step2str += 'Yerr = ' + dfname +'[\"'
 859                step2str += str(Ycoord.value)+'\"]*0.0 + ' + str(
 860                    yerrvalue.value) + '\n\n'
 861            if yerrtype.value == 'percent':
 862                step2str += 'Yerr = np.fabs('+ dfname +'[\"'
 863                step2str += str(Ycoord.value)+'\"])*0.01*' + str(
 864                    yerrvalue.value) + '\n\n'
 865            if yerrtype.value == 'data':
 866                step2str += 'Yerr = ' + dfname +'[\"'
 867                step2str += str(yerrdata.value)+'\"]\n\n'
 868            pass
 869        if change['old']== 2:
 870            # update step 3 string
 871            step3str = '# Define the fit model, initial guesses, ' \
 872                       'and constraints\n'
 873            step3str += 'fitmod = lmfit.models.'+str(modeldrop.value)+'()\n'
 874            currmodel = getattr(models, str(modeldrop.value))()
 875            for k in params_set.children:
 876                param_name = str(k.children[0].value.split(':')[0])
 877                if param_name in currmodel.param_names:
 878                    step3str += 'fitmod.set_param_hint(\"'+param_name+'\",'
 879                    step3str += ' vary = '+str(not(k.children[1].children[
 880                        0].value))
 881                    temp_val = k.children[1].children[1].value
 882                    def tst_temp_val(temp_val):
 883                        if (temp_val != np.nan) and (temp_val != np.inf) and\
 884                                (temp_val != -np.inf) and (temp_val != \
 885                                -sys.float_info.max) and (temp_val != \
 886                                sys.float_info.max):
 887                            return True
 888                        else:
 889                            return False
 890                    if tst_temp_val(temp_val):
 891                        step3str += ', value = ' + str(temp_val)
 892                    temp_val = k.children[1].children[2].value
 893                    if tst_temp_val(temp_val):
 894                        step3str += ', min = ' + str(temp_val)
 895                    temp_val = k.children[1].children[3].value
 896                    if tst_temp_val(temp_val):
 897                        step3str += ', max = ' + str(temp_val)
 898                    step3str += ')\n'
 899            step3str +='\n'
 900            pass
 901        if change['new']>=4:
 902            ranges = []
 903            if JPSLUtils.notebookenv != 'colab':
 904                # update ranges
 905                range_start = True
 906                new_range = []
 907                if len(range_plot.data)>0:
 908                    for i in range(len(range_plot.data[0].marker.color)):
 909                        if range_plot.data[0].marker.color[i] == range_plot_hilight:
 910                            new_range.append(i)
 911                            if not range_start:
 912                                ranges.append(new_range)
 913                                new_range = []
 914                            range_start = not range_start
 915            # update step 4 string
 916            covscalestr = 'False'
 917            if yerrtype.value == 'none':
 918                covscalestr = 'True'
 919            if len(ranges) > 0:
 920                range_chosen = True
 921                step4str = '# Define fit ranges\n'
 922                step4str += 'Yfiterr = copy.deepcopy(Yerr) # ranges not to ' \
 923                            'fit = np.inf\n'
 924                step4str += 'Xfitdata = copy.deepcopy(Xvals) # ranges where ' \
 925                            'fit not displayed = np.nan\n'
 926                for i in range(len(ranges)):
 927                    if i == 0 and ranges[0][0]>0:
 928                        step4str += 'Yfiterr[int(0):int('+str(ranges[0][0])+ \
 929                                    ')] = np.inf\n'
 930                        step4str += 'Xfitdata[int(0):int('+str(ranges[0][0])+\
 931                                    ')] = np.nan\n'
 932                    if (i + 1) < len(ranges):
 933                        step4str += 'Yfiterr[int('+str(ranges[i][1]+1)+\
 934                                    '):int('+str(ranges[i+1][0])+')] = np.inf\n'
 935                        step4str += 'Xfitdata[int('+str(ranges[i][1]+1)+ \
 936                                    '):int('+str(ranges[i+1][0])+')] = np.nan\n'
 937                    if i+1 == len(ranges):
 938                        step4str += 'Yfiterr[int('+str(ranges[i][1]+1)+\
 939                                    '):int('+str(len(range_plot.data[0].marker.
 940                                                color))+')] = np.inf\n'
 941                        step4str += 'Xfitdata[int('+str(ranges[i][1]+1)+\
 942                                '):int('+str(len(range_plot.data[0].marker.
 943                                            color))+')] = np.nan\n'
 944                step4str += '\n'
 945                step4str += '# Do fit\n'
 946                step4str += str(fitname)+' = fitmod.fit(Yvals, x=Xvals, ' \
 947                    'weights = 1/Yfiterr, scale_covar = '+covscalestr+', ' \
 948                    'nan_policy = \"omit\")\n\n'
 949            else:
 950                range_chosen = False
 951                step4str = '# Do fit\n'
 952                step4str += str(fitname)+' = fitmod.fit(Yvals, x=Xvals, ' \
 953                    'weights = 1/Yerr, scale_covar = '+covscalestr+', ' \
 954                    'nan_policy = \"omit\")\n\n'
 955            step4str += '# Calculate residuals (data - fit) because lmfit\n'
 956            step4str += '#  does not calculate for all points under all ' \
 957                        'conditions\n'
 958            step4str += 'resid = []\n'
 959            step4str += ('# explicit int(0) below avoids collisions with some '
 960                         'preparsers.\n')
 961            step4str += 'for i in range(int(0),len('+str(fitname)+'.data)):\n'
 962            step4str += '    resid.append('+str(fitname)+'.data[' \
 963                                        'i]-'+str(fitname)+'.best_fit[i])\n\n'
 964            pass
 965        if change['old'] == 4:
 966                # update step 5 string
 967                step5str = ''
 968                if range_chosen:
 969                    xstr = 'Xfitdata'
 970                    if not(extend_fit.value):
 971                        step5str += '# Delete residuals in ranges not fit\n'
 972                        step5str += '# and fit values that are not ' \
 973                                    'displayed.\n'
 974                        step5str += 'for i in range(len(resid)):\n'
 975                        step5str += '    if np.isnan(Xfitdata[i]):\n'
 976                        step5str += '        resid[i] = None\n'
 977                        step5str += '        '+str(fitname)+'.best_fit[i] = ' \
 978                                                   'None\n\n'
 979                else:
 980                    xstr = 'Xvals'
 981                errbarstr = ''
 982                if yerrtype.value!='none':
 983                    errbarstr = ', error_y_type=\"data\", error_y_array=Yerr'
 984                xresidstr = xstr
 985                if extend_fit.value:
 986                    xresidstr = 'Xvals'
 987                mirrorstr = ''
 988                if mirror_axes.value:
 989                    mirrorstr = ', mirror = True'
 990                    if mirror_ticks.value:
 991                        mirrorstr = ', mirror = \"ticks\"'
 992                # the plot
 993                step5str += '# Plot Results\n'
 994                step5str += ('# explicit int(..) below avoids collisions with '
 995                             'some preparsers.\n')
 996                step5str += str(figname) + ' = go.FigureWidget(' \
 997                                    'layout_template=\"'+str(
 998                                    plot_template.value)+'\")\n'
 999                step5str += str(figname)+ '.update_layout(title = \"'+ \
1000                                    str(plot_title.value)+'\",'
1001                text = ''
1002                if plot_aspect.value == 'auto':
1003                    text += 'autosize=True)\n'
1004                else:
1005                    if plot_size.value == 'tiny':
1006                        plot_width = 300
1007                    elif plot_size.value == 'small':
1008                        plot_width = 450
1009                    elif plot_size.value == 'medium':
1010                        plot_width = 800
1011                    elif plot_size.value == 'large':
1012                        plot_width = 1200
1013                    elif plot_size.value == 'huge':
1014                        plot_width = 2400
1015                    if plot_aspect.value == '16:9':
1016                        plot_height = int(9 * plot_width / 16)
1017                    elif plot_aspect.value == '5:3':
1018                        plot_height = int(3 * plot_width / 5)
1019                    elif plot_aspect.value == '7:5':
1020                        plot_height = int(5 * plot_width / 7)
1021                    elif plot_aspect.value == '4:3':
1022                        plot_height = int(3 * plot_width / 4)
1023                    elif plot_aspect.value == '10:8':
1024                        plot_height = int(8 * plot_width / 10)
1025                    elif plot_aspect.value == '1:1':
1026                        plot_height = plot_width
1027                    text += 'autosize=False, width = int('
1028                    text += str(plot_width) + '), height=int('
1029                    text += str(plot_height) + '))\n'
1030                step5str += text
1031                step5str += str(figname) + '.set_subplots(rows=int(2), cols=int(1), ' \
1032                                           'row_heights=[0.2,0.8], ' \
1033                                           'shared_xaxes=True)\n'
1034                step5str += 'scat = go.Scatter(y=resid,x='+xresidstr+', ' \
1035                                    'mode=\"markers\",' \
1036                                    'name = \"residuals\"'+errbarstr+')\n'
1037                step5str += str(figname) + '.update_yaxes(title = ' \
1038                                        '\"Residuals\", ' \
1039                            'row=int(1), col=int(1), zeroline=True, zerolinecolor = ' \
1040                            '\"lightgrey\"'+str(mirrorstr)+')\n'
1041                if mirror_axes.value:
1042                    step5str += str(figname) + '.update_xaxes(' \
1043                                           'row=int(1), col=int(1)'+str(mirrorstr)+')\n'
1044                step5str += str(figname) + '.add_trace(scat,col=int(1),row=int(1))\n'
1045                step5str += 'scat = go.Scatter(x=Xvals, y=Yvals, ' \
1046                            'mode=\"markers\", name=tracename'+errbarstr+')\n'
1047                step5str += str(figname) + '.add_trace(scat, col=int(1), ' \
1048                                           'row=int(2))\n'
1049                step5str += str(figname) + '.update_yaxes(title = ' \
1050                                           '\"'+Y_label.value+'\", ' \
1051                                           'row=int(2), col=int(1)'+str(mirrorstr)+')\n'
1052                step5str += str(figname) + '.update_xaxes(title = ' \
1053                                           '\"'+X_label.value+'\", ' \
1054                                           'row=int(2), col=int(1)'+str(mirrorstr)+')\n'
1055                if extend_fit.value:
1056                    step5str += 'scat = go.Scatter(y='+str(
1057                        fitname)+'.best_fit, x=Xvals, mode=\"lines\", '\
1058                                'line_color = \"black\", ' \
1059                                'name=\"extrapolated\",' \
1060                                 'line_dash=\"dash\")\n'
1061                    step5str += str(figname) + '.add_trace(scat, col=int(1), ' \
1062                                               'row=int(2))\n'
1063                step5str += 'scat = go.Scatter(y='+str(fitname)+'.best_fit,' \
1064                                    'x='+xstr+', mode=\"lines\", ' \
1065                                    'name=\"fit\", line_color = ' \
1066                                    '\"black\", line_dash=\"solid\")\n'
1067                step5str += str(figname) + '.add_trace(scat,col=int(1),row=int(2))\n'
1068                step5str += str(figname) + '.show(config = ' \
1069                                            '{\'toImageButtonOptions\': {' \
1070                                            '\'format\': \'svg\'}})\n\n'
1071                pass
1072        if change['new'] == 3 and JPSLUtils.notebookenv != 'colab':
1073            df = friendly_to_object[whichframe.value]
1074            rangex = df[Xcoord.value]
1075            rangey = df[Ycoord.value]
1076            c =[]
1077            s = []
1078            if len(range_plot.data)> 0 and len(range_plot.data[
1079                0].marker.color) == len(range_plot.data[0]['x']):
1080                c = list(range_plot.data[0].marker.color)
1081                s = list(range_plot.data[0].marker.size)
1082            range_plot.data=[]
1083            range_plot.add_scatter(x=rangex,y=rangey, mode = 'markers',
1084                                   line_color = range_plot_line_color,
1085                                   marker_size = range_plot_marker_size)
1086            if len(range_plot.data[0]['x']) == len(c):
1087                with range_plot.batch_update():
1088                    range_plot.data[0].marker.color = c
1089                    range_plot.data[0].marker.size = s
1090            range_plot.data[0].on_click(update_range_point)
1091        if change['new'] ==5:
1092            if X_label.value == '' or Y_label.value == '':
1093                makeplot_notices.activate_notice(2)
1094                dofitbut.disabled = True
1095                dofitbut.button_style = ''
1096            else:
1097                makeplot_notices.deactivate_notice(2)
1098            if Xcoord.value == 'Choose X-coordinate.' or \
1099                    Ycoord.value == 'Choose X-coordinate.':
1100                makeplot_notices.activate_notice(0)
1101                dofitbut.disabled = True
1102                dofitbut.button_style = ''
1103            else:
1104                makeplot_notices.deactivate_notice(0)
1105            if modeldrop.value == '':
1106                makeplot_notices.activate_notice(1)
1107                dofitbut.disabled = True
1108                dofitbut.button_style = ''
1109            else:
1110                makeplot_notices.deactivate_notice(1)
1111            step6noticebox.value = makeplot_notices.notice_html()
1112        if len(makeplot_notices.get_active()) == 0:
1113            dofitbut.disabled = False
1114            dofitbut.button_style = 'success'
1115        # the best fit equation
1116        step6str = '# Display best fit equation\n'
1117        step6str += fitresultstrs[modeldrop.value](fitname)
1118        if JPSLUtils.notebookenv == 'NBClassic':
1119            JPSLUtils.select_containing_cell('pandasfitGUI')
1120            JPSLUtils.replace_text_of_next_cell(importstr + step1str + \
1121                                                step2str + step3str + \
1122                                                step4str + step5str + step6str)
1123        else:
1124            codearea.sniptext.value = importstr + step1str + step2str + \
1125                                      step3str + step4str + step5str + \
1126                                      pseudoLatexToLatex(step6str)
1127        pass
1128
1129    steps.observe(tab_changed, names = 'selected_index')
1130    with output:
1131        display(steps)
1132    if JPSLUtils.notebookenv == 'NBClassic':
1133        display(output)
1134        select_containing_cell('pandasfitGUI')
1135        new_cell_immediately_below()
1136    else:
1137        codearea = build_run_snip_widget('', output)
1138        with output:
1139            display(codearea)
1140        display(output)
1141    pass
def calcresid(result):
 6def calcresid(result):
 7    '''
 8    lmfit has empty values for residuals where the weighting is infinite or not defined.
 9    This calculates all the residuals based on the actual data and fit results.
10
11    :param ModelResult result: An lmfit ModelResult.
12
13    :return np.array residuals: The residuals.
14    '''
15    import numpy as np
16    import lmfit as lmfit
17    resid = []
18    for i in range(0, len(results.data)):
19        resid.append(results.data[i] - results.best_fit[i])
20    return np.array(resid)

lmfit has empty values for residuals where the weighting is infinite or not defined. This calculates all the residuals based on the actual data and fit results.

Parameters
  • ModelResult result: An lmfit ModelResult.
Returns

The residuals.

def fit_pandas_GUI(df_info=None, show_text_col=False, **kwargs):
  22def fit_pandas_GUI(df_info=None, show_text_col = False, **kwargs):
  23    """
  24    If passed no parameters this will look for all the dataframes in the user
  25    namespace and make them available for plotting. Once a
  26    dataframe is chosen only the numerical columns from that dataframe will
  27    be available for inclusion in the plotting expression.
  28
  29    This GUI produces code to use the lmfit package to fit data and the plotly
  30    interactive plotting package to display the results.
  31
  32    If you wish to allow only certain dataframes or have them show up as
  33    user friendly names in the menus provide that information in the first
  34    paramater df_info.
  35
  36    To allow inclusion of text columns pass True for show_text_col.
  37
  38    :param bool show_text_col: (default = False). When True columns
  39    containing text will be shown.
  40
  41    :param list df_info: List of Lists [[object,globalname,
  42    userfriendly]],..]
  43      * object -- pandas.DataFrame
  44      * globalname -- string name of the object in the user global name space.
  45      * userfriendly -- string name to display for user selection.
  46      
  47    :keyword string figname: string used to override default python name for
  48    figure.
  49
  50    :keyword string fitname: string used to override default python name for
  51    fit.
  52    
  53    :keyword bool findframes: default = True. If set to false and dataframes
  54    are passed in dfs_info, will not search for dataframes in the user
  55    namespace.
  56    """
  57    from ipywidgets import Layout, Box, HBox, VBox, GridBox, Tab, \
  58        Accordion, Dropdown, Label, Text, Button, Checkbox, FloatText, \
  59        RadioButtons, BoundedIntText, Output
  60    from ipywidgets import HTML as richLabel
  61    from ipywidgets import HTMLMath as texLabel
  62    from IPython.display import display, HTML
  63    from IPython.display import Javascript as JS
  64    from IPython import get_ipython
  65    from JPSLUtils.utils import new_cell_immediately_below,\
  66        select_cell_immediately_below, move_cursor_in_current_cell, \
  67        insert_text_into_next_cell, insert_text_at_beginning_of_current_cell, \
  68        insert_newline_at_end_of_current_cell, select_containing_cell, \
  69        delete_selected_cell, iconselector, notice_group, \
  70        replace_text_of_current_cell, pseudoLatexToLatex
  71    from lmfit import models
  72
  73    from .utils import find_pandas_dataframe_names, build_run_snip_widget
  74    from IPython import get_ipython
  75    global_dict = get_ipython().user_ns
  76    JPSLUtils = global_dict["JPSLUtils"]
  77    if JPSLUtils.notebookenv != 'colab':
  78        import plotly.graph_objects as go
  79    dfs_info = []
  80    if isinstance(df_info,list):
  81        for k in df_info:
  82            dfs_info.append(k)
  83    findframes = kwargs.pop('findframes',True)
  84    if findframes:
  85        for k in find_pandas_dataframe_names():
  86            dfs_info.append([global_dict[k],k,k])
  87    friendly_to_globalname = {k[2]:k[1] for k in dfs_info}
  88    friendly_to_object = {k[2]:k[0] for k in dfs_info}
  89
  90    fitname = kwargs.pop('fitname',None)
  91    from .utils import find_fit_names
  92    fitlst = find_fit_names()
  93    if fitname in fitlst:
  94        raise UserWarning (str(fitname) + ' already exists. Choose a '
  95                                          'different name for the fit.')
  96    if fitname == None:
  97        fitname = 'Fit_'+str(len(fitlst)+1)
  98
  99    figname = kwargs.pop('figname',None)
 100    from .utils import find_figure_names
 101    figlst = find_figure_names()
 102    if figname in figlst:
 103        raise UserWarning (str(figname) + ' already exists. Choose a '
 104                                          'different name for the figure.')
 105    if figname == None:
 106        figname = str(fitname) + '_Figure'
 107
 108    fitmodels = ['LinearModel','PolynomialModel','ExponentialModel',
 109                 'GaussianModel','SineModel']
 110    fitmodeleqns = {
 111    'LinearModel':r'$fit = \color{red}{a}x+\color{red}{b}$, where $\color{'
 112                  r'red}{a}$ = slope, $\color{red}{b}$ = intercept',
 113    'PolynomialModel': r'$fit = \sum_{n=0}^{\le7}{\color{red}{c_n}x^n} = '
 114                       r'\color{red}{c_0} + \color{red}{c_1}x + \color{red}{'
 115                       r'c_2}x^2 + ...$',
 116    'ExponentialModel': r'$fit = \color{red}{A} \exp \left( \frac{-x} ' \
 117                        r'{\color{red}{\tau}}\right)$, where $\color{red}{A}$ '
 118                        r'= amplitude, $ \color{red}{\tau}$ = decay',
 119    'GaussianModel': r'$fit = \frac{\color{red}{A}}{\color{red}{\sigma} ' \
 120                     r'\sqrt{2 \pi}} \exp \left( \frac{-(x-\color{red}' \
 121                     r'{\mu})^2}{2 \color{red}{\sigma}^2} \right)$, where ' \
 122                     r'$\color{red}{A}$ = amplitude, $\color{red}{\sigma}$ = sigma, '
 123                     r'$\color{red}{\mu}$ = center',
 124        'SineModel': r'$fit = \color{red}{A} \sin \left ( \color{red}{f}x + '
 125                     r'\color{red}{\phi} \right)$, '
 126                     r'where $\color{red}{A}$ = ' \
 127                     r'amplitude, $\color{red}{f}$ = frequency, '\
 128                     r'$\color{red}{\phi}$ = shift'
 129    }
 130
 131    def polymodelresultstr(resultname):
 132        template = '' \
 133          'fitstr = r\'$fit = \'\n' \
 134          'termcount = int(0)\n' \
 135          'for k in %result.params.keys():\n' \
 136          '    pwr = int(str(k)[int(-1):])\n' \
 137          '    if %result.params[k].vary:\n' \
 138          '        if termcount > int(0):\n' \
 139          '            fitstr += \' + \'\n' \
 140          '        fitstr += r\'({%COLOR{red}{\'+rue.latex_rndwitherr(' \
 141                                         '%result.params[k].value,\n' \
 142          '                               %result.params[k].stderr, \n' \
 143          '                                errdig=int(1), \n' \
 144          '                                lowmag=-int(3))+\'}})\'\n' \
 145          '        if pwr == int(1):\n' \
 146          '            fitstr += \'x\'\n' \
 147          '        if pwr > int(1):\n' \
 148          '            fitstr += \'x^\'+str(pwr)\n' \
 149          '        termcount+=int(1)\n' \
 150          '    else:\n' \
 151          '        if %result.params[k].value!=0.0:\n' \
 152          '            if termcount > int(0):\n' \
 153          '                fitstr += \'+\'\n' \
 154          '            fitstr += r\'({%COLOR{blue}{\'+str(' \
 155                                         '%result.params[k].value)+\'}})\'\n' \
 156          '            termcount +=int(1)\n' \
 157          '            if pwr == int(1):\n' \
 158          '                fitstr += \'x\'\n' \
 159          '            if pwr > int(1):\n' \
 160          '                fitstr += \'x^\'+str(pwr)\n' \
 161          'fitstr+=\'$\'\n' \
 162          'captionstr=r\'<p>Use the command <code>%result</code> as the ' \
 163          'last line of a code cell for more details.</p>\'\n' \
 164          'display(Math(fitstr))\n' \
 165          'display(HTML(captionstr))'
 166        return template.replace('%result', str(resultname))
 167
 168    def linmodelresultstr(resultname):
 169        template = '' \
 170       'slopestr = ''\'\'\n' \
 171       'interceptstr = ''\'\'\n' \
 172       'for k in %results.params.keys():\n' \
 173       '    if %results.params[k].vary:\n' \
 174       '        paramstr = r\'({%COLOR{red}{\'+rue.latex_rndwitherr(' \
 175                   '%results.params[k].value,\n' \
 176       '                                       %results.params[k].stderr,\n' \
 177       '                                       errdig=int(1),\n' \
 178       '                                       lowmag=-int(3))+\'}})\'\n' \
 179       '    else:\n' \
 180       '        paramstr = r\'{%COLOR{blue}{\'+str(%results.params[' \
 181                   'k].value,' \
 182                   '\n' \
 183       '                                       )+\'}}\'\n' \
 184       '    if k == \'slope\':\n' \
 185       '        slopestr = paramstr\n' \
 186       '    if k == \'intercept\' and %results.params[k].value != 0:\n' \
 187       '        interceptstr = \' + \' + paramstr\n' \
 188       'fitstr = r\'$fit = \'+slopestr + \'x\' + interceptstr + \'$\'\n' \
 189       'captionstr = r\'<p>Use the command <code>%results</code> as the ' \
 190       'last line of a code cell for more details.</p>\'\n' \
 191       'display(Math(fitstr))\n' \
 192       'display(HTML(captionstr))'
 193        return template.replace('%results', resultname)
 194
 195    def expmodelresultstr(resultname):
 196        template = '' \
 197        'ampstr = ''\'\'\n' \
 198        'decaystr = ''\'\'\n' \
 199        'for k in %results.params.keys():\n' \
 200        '    if %results.params[k].vary:\n' \
 201        '        paramstr = r\'({%COLOR{red}{\'+rue.latex_rndwitherr(' \
 202        '%results.params[k].value,\n' \
 203        '                                 %results.params[k].stderr,\n' \
 204        '                                 errdig=int(1),\n' \
 205        '                                 lowmag=-int(3))+\'}})\'\n' \
 206        '    else:\n' \
 207        '        paramstr = r\'{%COLOR{blue}{\'+str(%results.params[' \
 208                                               'k].value, \n' \
 209        '                                       )+\'}}\'\n' \
 210        '    if k == \'amplitude\':\n' \
 211        '        ampstr = paramstr\n' \
 212        '    if k == \'decay\':\n' \
 213        '        decaystr = paramstr\n' \
 214        'fitstr = r\'$$fit = \'+ampstr+r\'%EXP %LEFT( %FRAC{-x}' \
 215                        '{\'+decaystr+r\'}%RIGHT)$$\'\n' \
 216        'captionstr = r\'<p>Use the command <code>%results</code> as the ' \
 217        'last line of a code cell for more details.</p>\'\n' \
 218        'display(Math(fitstr))\n' \
 219        'display(HTML(captionstr))'
 220        return template.replace('%results',resultname)
 221
 222    def gausmodelresultstr(resultname):
 223        template = '' \
 224        'ampstr = ''\'\'\n' \
 225        'centstr = ''\'\'\n' \
 226        'sigmastr = ''\'\'\n' \
 227        'for k in %results.params.keys():\n' \
 228        '    if %results.params[k].vary:\n' \
 229        '        paramstr = r\'({%COLOR{red}{\'+rue.latex_rndwitherr(' \
 230                                          '%results.params[k].value,\n' \
 231        '                                 %results.params[k].stderr,\n' \
 232        '                                 errdig=int(1),\n' \
 233        '                                 lowmag=-int(3))+\'}})\'\n' \
 234        '    else:\n' \
 235        '        paramstr = r\'{%COLOR{blue}{\'+str(%results.params[' \
 236                                               'k].value, \n' \
 237        '                                       )+\'}}\'\n' \
 238        '    if k == \'amplitude\':\n' \
 239        '        ampstr = paramstr\n' \
 240        '    if k == \'center\':\n' \
 241        '        centstr = paramstr\n' \
 242        '    if k == \'sigma\':\n' \
 243        '        sigmastr = paramstr\n' \
 244        'fitstr = r\'$$fit = %FRAC{\'+ampstr+\'}{' \
 245                   '\'+sigmastr+r\'%SQRT{2%PI}}%EXP %LEFT( %FRAC{' \
 246                   '-%LEFT[x-\'+centstr+r\'%RIGHT]^2}' \
 247                   '{2\'+sigmastr+r\'^2}%RIGHT)$$\'\n' \
 248        'captionstr = r\'<p>Use the command <code>%results</code> as the ' \
 249        'last line of a code cell for more details.</p>\'\n' \
 250        'display(Math(fitstr))\n' \
 251        'display(HTML(captionstr))'
 252        return template.replace('%results',resultname)
 253
 254    def sinmodelresultstr(resultname):
 255        template = '' \
 256       'ampstr = \'\'\n' \
 257       'freqstr = \'\'\n' \
 258       'shiftstr = \'\'\n' \
 259       'for k in %results.params.keys():\n' \
 260       '    if %results.params[k].vary:\n' \
 261       '        if %results.params[k].stderr:\n' \
 262       '            paramstr = r\'({%COLOR{red}{\'+rue.latex_rndwitherr(' \
 263                   '%results.params[k].value,\n' \
 264       '                                       %results.params[k].stderr,\n' \
 265       '                                       errdig=int(1),\n' \
 266       '                                       lowmag=-int(3))+\'}})\'\n' \
 267       '        else:\n' \
 268       '            paramstr = r\'{%COLOR{red}{\'+' \
 269                   'str(%results.params[k].value)+\'}}\'\n' \
 270       '    else:\n' \
 271       '        paramstr = r\'{%COLOR{blue}{\'+str(%results.params[k].value' \
 272                   ')+\'}}\'\n' \
 273       '    if k == \'amplitude\':\n' \
 274       '        ampstr = paramstr\n' \
 275       '    if k == \'frequency\':\n' \
 276       '        freqstr = paramstr\n' \
 277       '    if k == \'shift\' and %results.params[k].value != 0.0:\n' \
 278       '        shiftstr = \' + \' + paramstr\n' \
 279       'fitstr = r\'$fit = \'+ampstr + \'sin[\' + freqstr + \'x\' + shiftstr + \']$\'\n' \
 280       'captionstr = r\'<p>Use the command <code>%results</code> as the ' \
 281       'last line of a code cell for more details.</p>\'\n' \
 282       'display(Math(fitstr))\n' \
 283       'display(HTML(captionstr))'
 284        return template.replace('%results', resultname)
 285
 286    fitresultstrs = {
 287    'LinearModel': linmodelresultstr,
 288    'PolynomialModel': polymodelresultstr,
 289    'ExponentialModel': expmodelresultstr,
 290    'GaussianModel': gausmodelresultstr,
 291    'SineModel': sinmodelresultstr
 292    }
 293
 294    importstr = '# CODE BLOCK generated using fit_pandas_GUI().\n# See '\
 295                'https://jupyterphysscilab.github.io/jupyter_Pandas_GUI.\n' \
 296                '# Integers wrapped in `int()` to avoid having them cast \n' \
 297                '# as other types by interactive preparsers. \n' \
 298                '# Imports (no effect if already imported)\n' \
 299                'import numpy as np\n' \
 300                'import lmfit as lmfit\n' \
 301                'import round_using_error as rue\n' \
 302                'import copy as copy\n' \
 303                'from plotly import graph_objects as go\n' \
 304                'from IPython.display import HTML, Math\n\n'
 305    step1str = ''
 306    step2str = ''
 307    step3str = ''
 308    step4str = ''
 309    step5str = ''
 310    step6str = ''
 311    range_chosen = False
 312    #### Define GUI Elements ####
 313    # Those followed by a * are required.
 314    output = Output()
 315    with output:
 316        display(HTML(
 317            "<h3 id ='pandasfitGUI' style='text-align:center;'>Pandas Fit "
 318            "Composer</h3> <div style='text-align:center;'>"
 319            "<span style='color:green;'>Steps with a * are required.</span> The "
 320            "code that will generate the fit is being "
 321            "built in the textbox or cell immediately below.</div><div "
 322            "style='text-align:center;'>This composer uses a subset of "
 323            "<a href ='https://lmfit.github.io/lmfit-py/'> the lmfit package</a>"
 324            " and <a href ='https://plotly.com/python/line-and-scatter/#'> "
 325            "the plotly scatter plot</a> capabilities.</div>"))
 326
 327    longdesc = {'description_width':'initial'}
 328
 329    # Notices for the Final Check Tab.
 330    makeplot_notices = notice_group(['Need data to fit',
 331                                     'Need a fit model',
 332                                     'Axes must have labels.'],
 333                                    'Notices:','','red')
 334    makeplot_notices.set_active([0,1,2])
 335
 336    # 1. Pick and variables to fit and fit model
 337    #   a. Select Y vs. X (DataFrame, X and Y, which must be from single
 338    #       frame.
 339    # Notices for the Pick Trace(s) tab.
 340    notice_list = [
 341        'Data set (DataFrame) required.',
 342        'X- and Y-values required.',
 343    ]
 344    trace_notices = notice_group(notice_list, 'Notices:','','red')
 345    trace_notices.set_active([0,1])
 346    step1instr = richLabel(value = '<ol><li>Select a DataFrame (Data '
 347                                   'set);</li>'
 348                                   '<li>Select the column containing the X '
 349                                   'values;</li>'
 350                                   '<li>Select the column containing the Y '
 351                                   'values (what is being fit);</li>'
 352                                   '<li>Provide a name for the trace if you do'
 353                                   ' not like the default. This text will be '
 354                                   'used for the legend;</li></ol>'
 355                           )
 356    step1instracc = Accordion(children = [step1instr])
 357    step1instracc.set_title(0,'Instructions')
 358    step1instracc.selected_index = None
 359
 360    # DataFrame selection
 361    tempopts = []
 362    tempopts.append('Choose data set.')
 363    for k in dfs_info:
 364        tempopts.append(k[2])
 365    whichframe = Dropdown(options=tempopts,
 366                                description='DataFrame: ',)
 367
 368    def update_columns(change):
 369        if change['new'] == 'Choose data set.':
 370            Xcoord.disabled = True
 371            Ycoord.disabled = True
 372            trace_notices.activate_notice(0)
 373            trace_notices.activate_notice(1)
 374            trace_notices.activate_notice(2)
 375            add_trace_notices.value = trace_notices.notice_html()
 376            return
 377        df = friendly_to_object[change['new']]
 378        tempcols = df.columns.values
 379        tempopt = ['Choose column for coordinate.']
 380        for k in tempcols:
 381            if show_text_col:
 382                tempopt.append(k)
 383            else:
 384                if df[k].dtype != 'O':
 385                    tempopt.append(k)
 386        Xcoord.options = tempopt
 387        Xcoord.value = tempopt[0]
 388        Ycoord.options = tempopt
 389        Ycoord.value = tempopt[0]
 390        Xcoord.disabled = False
 391        Ycoord.disabled = False
 392        trace_notices.activate_notice(1)
 393        trace_notices.deactivate_notice(0)
 394        add_trace_notices.value =trace_notices.notice_html()
 395        pass
 396    whichframe.observe(update_columns, names='value')
 397
 398    # Data selection
 399    Xcoord = Dropdown(options=['Choose X-coordinate.'],
 400                           description='X: ',
 401                           disabled = True)
 402    Ycoord = Dropdown(options=['Choose Y-coordinate.'],
 403                           description='Y: ',
 404                           disabled = True)
 405    def trace_name_update(change):
 406        if change['new'] != 'Choose column for coordinate.':
 407            trace_name.value = Ycoord.value
 408        if Xcoord.value != 'Choose column for coordinate.' and Ycoord.value \
 409                != 'Choose column for coordinate.':
 410            yerrtype.disabled = False
 411            trace_name.disabled = False
 412            trace_notices.deactivate_notice(1)
 413            add_trace_notices.value = trace_notices.notice_html()
 414        else:
 415            yerrtype.disabled = True
 416            trace_name.disabled = True
 417            trace_notices.activate_notice(1)
 418            add_trace_notices.value = trace_notices.notice_html()
 419        pass
 420    Xcoord.observe(trace_name_update,names='value')
 421    Ycoord.observe(trace_name_update,names='value')
 422
 423    # Trace name
 424    trace_name = Text(placeholder = 'Trace name for legend',
 425                      description = 'Trace name: ',
 426                      disabled = True)
 427
 428    trace_notices.set_active([0,1])
 429    add_trace_notices = richLabel(value = trace_notices.notice_html())
 430    step1tracebox =  VBox(children=[whichframe,Xcoord,Ycoord,trace_name])
 431    step1actionbox =  VBox(children=[add_trace_notices])
 432    step1hbox =  HBox(children=[step1tracebox,step1actionbox])
 433    step1 =  VBox(children=[step1instracc, step1hbox])
 434
 435    # 2. Set data uncertainty
 436    step2instr = richLabel(value = 'If you know the uncertainty in your data '
 437                                   'values (Y-values)you should '
 438                                   'specify it, as the uncertainty impacts '
 439                                   'the final uncertainty in the fit '
 440                                   'parameters. '
 441                                   'If you do not know the uncertainty of '
 442                                   'your data leave the "Error Type" as '
 443                                   '"none". In this case all the data values '
 444                                   'will be equally weighted during the fit. '
 445                                   'Alternatives are: a constant uncertainty '
 446                                   'that is the same for every data point; a '
 447                                   'percentage of each value; data (a '
 448                                   'column) specifying the uncertainty for '
 449                                   'every data point.')
 450
 451    yerrtype = Dropdown(options = ['none','percent','constant','data'],
 452                        description = 'Error Type: ',
 453                        disabled = True)
 454
 455    def error_settings_OK():
 456        check = True
 457        if (yerrtype.value == 'data') and (yerrdata.value == 'Choose error '
 458                                                        'column.'):
 459            check = False
 460        return check
 461
 462    def yerr_change(change):
 463        df = friendly_to_object[whichframe.value]
 464        if change['new'] == 'percent' or change['new'] == 'constant':
 465            yerrvalue.disabled = False
 466            yerrdata.disabled = True
 467        if change['new'] == 'data':
 468            yerrvalue.disabled = True
 469            tempopts = ['Choose error column.']
 470            tempcols = df.columns.values
 471            for k in tempcols:
 472                if df[k].dtype != 'O':
 473                    tempopts.append(k)
 474            yerrdata.options=tempopts
 475            yerrdata.disabled = False
 476        if change['new'] == 'none':
 477            yerrvalue.disabled = True
 478            yerrdata.disabled = True
 479        add_trace_notices.value = trace_notices.notice_html()
 480        pass
 481
 482    yerrtype.observe(yerr_change, names = 'value')
 483
 484    yerrvalue = FloatText(description = '% or constant: ', disabled = True,
 485                          style=longdesc, value=1.0)
 486    yerrdata = Dropdown(options = ['Choose error column.'],
 487                        description = 'Error values: ',
 488                        disabled = True)
 489
 490    def errdata_change(change):
 491        if error_settings_OK():
 492            #trace_notices.deactivate_notice(2)
 493            pass
 494        else:
 495            #trace_notices.activate_notice(2)
 496            pass
 497        add_trace_notices.value = trace_notices.notice_html()
 498        pass
 499
 500    yerrdata.observe(errdata_change, names = 'value')
 501    yerrrow1 =  HBox(children=[yerrtype,yerrvalue])
 502    yerror =  VBox(children=[yerrrow1,yerrdata])
 503    step2instracc = Accordion(children=[step2instr])
 504    step2instracc.selected_index = None
 505    step2 =  VBox(children=[step2instr,yerror])
 506
 507    # 3. Set fit parameters
 508    step3instr = richLabel(value = '<ol><li>Choose the fit type ('
 509                                   'functional form). <span '
 510                                   'style="color:red">Red symbols are '
 511                                   'the fit parameters.</span></li>'
 512                                   '<li>You may use the default settings for '
 513                                   'the '
 514                                   'initial guesses and parameter ranges or '
 515                                   'you may set them.</li>'
 516                                   '<li>To fix a value at the '
 517                                   'initial guess select the "fix" checkbox. '
 518                                   'You must provide an initial guess if you '
 519                                   'fix a parameter.</li></ol>')
 520    step3instracc = Accordion(children = [step3instr])
 521    step3instracc.set_title(0,'Instructions')
 522    step3instracc.selected_index = None
 523    # get selected fit model and update parameters list.
 524    modeldrop = Dropdown(options=fitmodels)
 525    modeleqn = texLabel(value = fitmodeleqns[modeldrop.value])
 526    def getcurrmodel_param(modelname, params_set):
 527        '''
 528        Using the model name return ipywidgets for setting the fit 
 529        parameters and constraints, populated with the default values.
 530        :param string modelname: The string name for the lmfit model.
 531        :param VBox params_set: The VBox containing the HBoxes for parameter
 532            guesses and constraints.
 533        :return VBox: params_set with fields reset and those available visible.
 534        '''
 535        currmodel = getattr(models,modelname)()
 536        currmodel_param = []
 537        labeltext = ''
 538        fix = False
 539        value = 0
 540        min = -sys.float_info.max
 541        max = sys.float_info.max
 542        expr = None  # Not used, maybe for arbitrary functions.
 543        for i in range(0,8):
 544            fix = False
 545            if i < len(currmodel.param_names):
 546                labeltext = str(currmodel.param_names[i])
 547                hints = currmodel.param_hints.get(labeltext,None)
 548                if isinstance(hints,dict):
 549                    fix = not(hints.get('vary',True))
 550                    value = hints.get('value', 0)
 551                    min = hints.get('min', -sys.float_info.max)
 552                    max = hints.get('max', sys.float_info.max)
 553                    expr = hints.get('expr',None)
 554                params_set.children[i].layout.display=''
 555                if modelname == 'ExponentialModel':
 556                    df = friendly_to_object[whichframe.value]
 557                    xvals = df[Xcoord.value]
 558                    yvals = df[Ycoord.value]
 559                    if labeltext == 'amplitude':
 560                        value = np.mean(yvals)
 561                    if labeltext == 'decay':
 562                        value = (np.max(xvals) - np.min(xvals))/3.0
 563                if modelname == 'GaussianModel':
 564                    df = friendly_to_object[whichframe.value]
 565                    xvals = df[Xcoord.value]
 566                    yvals = df[Ycoord.value]
 567                    if labeltext == 'amplitude':
 568                        value = np.max(yvals)
 569                    if labeltext == 'sigma':
 570                        value = 1.0
 571                    if labeltext == 'center':
 572                        maxidx = np.argmax(yvals)
 573                        value = xvals[maxidx]
 574                if modelname == 'SineModel':
 575                    df = friendly_to_object[whichframe.value]
 576                    xvals = df[Xcoord.value]
 577                    yvals = df[Ycoord.value]
 578                    if labeltext == 'amplitude':
 579                        value = np.max(yvals)
 580                    if labeltext == 'frequency':
 581                        # Looking for the most prominent frequency component 
 582                        # to fit.
 583                        temprange = np.max(xvals) - np.min(xvals)
 584                        npts = len(xvals)
 585                        tempfft = np.fft.fft(yvals)[:int(npts/2)]
 586                        maxloc = np.argmax(np.absolute(tempfft))
 587                        value = maxloc*temprange/npts
 588            else:
 589                labeltext = str(i)
 590                params_set.children[i].layout.display='none'
 591            params_set.children[i].children[0].value = labeltext+':'
 592            params_set.children[i].children[1].children[0].value = fix
 593            params_set.children[i].children[1].children[1].value = value
 594            params_set.children[i].children[1].children[2].value = min
 595            params_set.children[i].children[1].children[3].value = max
 596        pass
 597
 598    def make_param_set():
 599        '''
 600        Creates a VBox with 7 parameters each having fields in an HBox:
 601        1. fixcheck (checkbox for fixing the value)
 602        2. valuefield (floatText for setting the value)
 603        3. minfield (floatText for setting the minimum allowed value)
 604        4. maxfield (floatText for setting the maximum allowed value)
 605        By default the all VBox components have their `layout.display=none`.
 606        :return: VBox
 607        '''
 608        import sys
 609        currmodel_param=[]
 610        for i in range (0,8):
 611            fixcheck = Checkbox(value=False,
 612                                description='Fix (hold)',
 613                                disabled=False,
 614                                style=longdesc)
 615            valuefield = FloatText(value=0,
 616                                   description='Value: ',
 617                                   disabled=False,
 618                                   style=longdesc)
 619            minfield = FloatText(value=-sys.float_info.max,
 620                                 description='Min: ',
 621                                 disabled=False,
 622                                 style=longdesc)
 623            maxfield = FloatText(value=sys.float_info.max,
 624                                 description='Max: ',
 625                                 disabled=False,
 626                                 style=longdesc)
 627            paramlabel = Label(value = str(i)+':',style=longdesc)
 628            parambox =  VBox(children=[paramlabel, HBox(children=[fixcheck,valuefield,minfield,
 629                                    maxfield])])
 630            parambox.layout.display = 'none'
 631            currmodel_param.append(parambox)
 632        params_set = VBox(currmodel_param)
 633        return params_set
 634
 635    def modeldrop_change(change):
 636        modeleqn.value=fitmodeleqns[modeldrop.value]
 637        getcurrmodel_param(modeldrop.value,params_set)
 638        pass
 639    modeldrop.observe(modeldrop_change, names = 'value')
 640    params_set = make_param_set()
 641    getcurrmodel_param(modeldrop.value, params_set)
 642    step3 =  VBox(children=[step3instracc, HBox(children=[modeldrop,modeleqn]),params_set])
 643
 644    # 5.Title, Axes, Format ...
 645    step5instr = richLabel(value = '<ul><li><span '
 646                                   'style="font-weight:bold;">You '
 647                                   'must set the axes labels to something '
 648                           'appropriate.</span> For example if the X - values '
 649                           'represent time in seconds "Time (s)" is a good '
 650                           'choice. Likewise, choose an appropriate label '
 651                           'for the Y - axis.</li>'
 652                           '<li>If the Aspect Ratio is set to `auto` the '
 653                           'figure will fill the default output region. '
 654                           'Other choices will allow you to pick the Plot '
 655                                   'Size. `Large` will use about 2/3 of an HD '
 656                                   '(1920X1080) screen.</li></ul>')
 657    plot_title = Text(value = figname,
 658                       description = 'Plot title: ',
 659                      layout = Layout(width='80%'))
 660    X_label = Text(placeholder = 'Provide an X-axis label (usually has units)',
 661                   description = 'X-axis label: ',
 662                   style = longdesc,
 663                   layout=Layout(width='45%'))
 664    Y_label = Text(placeholder = 'Provide a Y-axis label (usually has units)',
 665                   description = 'Y-axis label: ',
 666                   style = longdesc,
 667                   layout=Layout(width='45%'))
 668    def mirror_axes_change(change):
 669        if change['new']:
 670            mirror_ticks.disabled= False
 671        else:
 672            mirror_ticks.disabled= True
 673            mirror_ticks.value = False
 674        pass
 675
 676    mirror_axes = Checkbox(value = False,
 677                           description = 'Display Mirror Axes',
 678                           style = longdesc)
 679    mirror_axes.observe(mirror_axes_change, names = 'value')
 680    mirror_ticks = Checkbox(value = False,
 681                            description = 'Mirror Tick Marks',
 682                            disabled = True)
 683
 684    def aspect_change(change):
 685        if change['new'] != 'auto':
 686            plot_size.disabled=False
 687        else:
 688            plot_size.disabled=True
 689        pass
 690
 691    plot_aspect = Dropdown(options = ['auto', '16:9', '5:3', '7:5', '4:3',
 692                                     '10:8', '1:1'],
 693                      value = 'auto',
 694                      description = 'Aspect Ratio: ',
 695                      style = longdesc)
 696    plot_aspect.observe(aspect_change, names = 'value')
 697    plot_size = Dropdown(options = ['tiny', 'small', 'medium', 'large',
 698                                    'huge'],
 699                         value = 'large',
 700                         description = 'Plot Size: ',
 701                         style = longdesc,
 702                         disabled = True)
 703    plot_template = Dropdown(options=['none','simple_white', 'ggplot2',
 704                                    'seaborn',
 705                                 'plotly', 'plotly_white', 'plotly_dark',
 706                                 'presentation', 'xgridoff', 'ygridoff',
 707                                 'gridon', 'simple_white+presentation',
 708                                      'simple_white+gridon', 
 709                                      'simple_white+presentation+gridon'],
 710                        value='simple_white',
 711                        description = 'Plot Styling: ',
 712                        style = longdesc)
 713    step5hbox1 =  HBox(children=[X_label, Y_label])
 714    step5hbox2 =  HBox(children=[mirror_axes,mirror_ticks, plot_template,
 715                                 plot_aspect, plot_size])
 716    step5 =  VBox(children=[step5instr, plot_title, step5hbox1, step5hbox2])
 717
 718    # 4. Pick Fit Range(s)
 719    step4instr = richLabel(value ='This step is optional. '
 720                                  'If you define no range(s) all data '
 721                                  'points will be used in the fit. <ul>'
 722                                  '<li> Click on points to select the '
 723                                  'beginning and ending of each range of '
 724                                  'data to include in the fit.</li>'
 725                                  '<li> Click again on '
 726                                  'a point to deselect it.</li>'
 727                                  '<li> Nearest neighbor pairs of points '
 728                                  'starting with the lowest point index '
 729                                  'number are used to define each range. If '
 730                                  'you select an odd number of points, '
 731                                  'the last point will be ignored.</li>'
 732                                  '<li> Check the `Extend fitted function '
 733                                  'plot` box if you want to display '
 734                                  'calculations of the fitted function and '
 735                                  'residuals in regions that were not fit '
 736                                  'to.</li></ul>')
 737    extend_fit = Checkbox(value=False,
 738                           description='Extend fitted function plot',
 739                           style=longdesc)
 740    range_plot = None
 741    if JPSLUtils.notebookenv == 'colab':
 742        range_plot = richLabel(value = '<span style="color:blue;">' \
 743                               'Unfortunately, defining a range by clicking ' \
 744                               'on the graph is not yet supported in Google' \
 745                               'Colab.</span>')
 746    else:
 747        range_plot =  go.FigureWidget(layout_template='simple_white')
 748        range_plot_line_color = 'blue'
 749        range_plot_hilight = 'cyan'
 750        range_plot_marker_size = 6
 751        range_plot_hilight_size = 20
 752        ranges=[]
 753        
 754    def update_range_point(trace, points, selector):
 755        # size and color must be done separately as they may not be updated
 756        # in sync.
 757        try:
 758            from collections.abc import Iterable
 759        except ImportError(e):
 760            from collections import Iterable            
 761        if not isinstance(trace['marker']['size'],Iterable):
 762            s = [range_plot_marker_size]*len(trace['x'])
 763        else:
 764            s = list(trace['marker']['size'])
 765        if (not isinstance(trace['marker']['color'],Iterable)) or isinstance(
 766                trace['marker']['color'],str):
 767            c = [range_plot_line_color]*len(trace['x'])
 768        else:
 769            c = list(trace['marker']['color'])
 770        for i in points.point_inds:
 771            if c[i]==range_plot_line_color:
 772                c[i] = range_plot_hilight
 773                s[i] = range_plot_hilight_size
 774            else:
 775                c[i] = range_plot_line_color
 776                s[i] = range_plot_marker_size
 777        with range_plot.batch_update():
 778            trace.marker.color = c
 779            trace.marker.size = s
 780        pass
 781    step4instacc = Accordion(children =[step4instr])
 782    step4instacc.set_title(0,'Instructions (optional step)')
 783    step4instacc.selected_index = None
 784    step4 =  VBox(children=[step4instacc,extend_fit,range_plot])
 785
 786    # 6. Final Check*
 787    step6instr = richLabel(value = 'Things to check before running the fit:' \
 788                                   '<ul><li>Fix any problems listed in ' \
 789                                   '"Notices".</li>' \
 790                                   '<li>Check for any unpaired parentheses, ' \
 791                                   'brackets or braces.</li>' \
 792                                   '<li>Check that all single and double ' \
 793                                   'quotes are paired.</li>' \
 794                                   '<li>If you did any manual editing ' \
 795                                   'double-check for typos.</li>')
 796    step6noticebox = richLabel(value = makeplot_notices.notice_html())
 797
 798    def dofit_click(change):
 799        if JPSLUtils.notebookenv == 'NBClassic':
 800            # Commented out do nothing because of timing issues
 801            #text = '\n# Force save widget states so that graph will still be\n'
 802            #text += '# available when notebook next opened in trusted state.\n'
 803            #text += 'import time\ntime.sleep(5)'
 804            select_containing_cell('pandasfitGUI')
 805            select_cell_immediately_below()
 806            #insert_newline_at_end_of_current_cell(text)
 807            #jscode = 'Jupyter.actions.call("widgets:save-with-widgets");'
 808            #text = 'JPSLUtils.OTJS(\''+jscode+'\')'
 809            #insert_newline_at_end_of_current_cell(text)
 810            # run the cell to build the plot
 811            JPSLUtils.OTJS('Jupyter.notebook.get_selected_cell().execute()')
 812            # remove the GUI cell
 813            select_containing_cell('pandasfitGUI')
 814            delete_selected_cell()
 815        pass
 816
 817    dofitbut_lay = Layout(visibility = "hidden")
 818    if JPSLUtils.notebookenv == 'NBClassic':
 819        dofitbut_lay = Layout(visibility="visible")
 820    dofitbut = Button(description = 'Do Fit',
 821                      disabled = True,
 822                      layout = dofitbut_lay)
 823    dofitbut.on_click(dofit_click)
 824    step6vbox =  VBox(children=[dofitbut,step6noticebox])
 825    step6 =  HBox(children=[step6instr,step6vbox])
 826
 827
 828    steps =  Tab(children=[step1, step2, step3, step4, step5, step6])
 829    steps.set_title(0,'1. Pick Data*')
 830    steps.set_title(1,'2. Data Uncertainty*')
 831    steps.set_title(2,'3. Set up Model*')
 832    steps.set_title(3,'4. Pick Fit Range(s)')
 833    steps.set_title(4, '5. Axes & Format*')
 834    steps.set_title(5, '6. Final Check*')
 835
 836    def tab_changed(change):
 837        nonlocal importstr, step1str, step2str, step3str, step4str, step5str, \
 838            range_chosen
 839        dfname = friendly_to_globalname[whichframe.value]
 840        if change['old'] == 0:
 841            # Update step 1 string
 842            step1str = '# Define data and trace name\n'
 843            step1str += 'Xvals = '+dfname+'[\"'
 844            step1str += str(Xcoord.value)+'\"]\n'
 845            step1str += 'Yvals = ' + dfname +'[\"'
 846            step1str += str(Ycoord.value)+'\"]\n'
 847            step1str += 'tracename = \"'+str(trace_name.value)+'\"\n\n'
 848            pass
 849        if change['old'] == 1:
 850            # TODO need do something in case  tab is changed before a click
 851            # occurs outside a box that was just change. blur things will
 852            # require ipywidgets v8+
 853            # update step 2 string
 854            step2str = '# Define error (uncertainty)\n'
 855            if yerrtype.value == 'none':
 856                step2str += 'Yerr = ' + dfname + '[\"'
 857                step2str += str(Ycoord.value) + '\"]*0.0 + 1.0\n\n'
 858            if yerrtype.value=='constant':
 859                step2str += 'Yerr = ' + dfname +'[\"'
 860                step2str += str(Ycoord.value)+'\"]*0.0 + ' + str(
 861                    yerrvalue.value) + '\n\n'
 862            if yerrtype.value == 'percent':
 863                step2str += 'Yerr = np.fabs('+ dfname +'[\"'
 864                step2str += str(Ycoord.value)+'\"])*0.01*' + str(
 865                    yerrvalue.value) + '\n\n'
 866            if yerrtype.value == 'data':
 867                step2str += 'Yerr = ' + dfname +'[\"'
 868                step2str += str(yerrdata.value)+'\"]\n\n'
 869            pass
 870        if change['old']== 2:
 871            # update step 3 string
 872            step3str = '# Define the fit model, initial guesses, ' \
 873                       'and constraints\n'
 874            step3str += 'fitmod = lmfit.models.'+str(modeldrop.value)+'()\n'
 875            currmodel = getattr(models, str(modeldrop.value))()
 876            for k in params_set.children:
 877                param_name = str(k.children[0].value.split(':')[0])
 878                if param_name in currmodel.param_names:
 879                    step3str += 'fitmod.set_param_hint(\"'+param_name+'\",'
 880                    step3str += ' vary = '+str(not(k.children[1].children[
 881                        0].value))
 882                    temp_val = k.children[1].children[1].value
 883                    def tst_temp_val(temp_val):
 884                        if (temp_val != np.nan) and (temp_val != np.inf) and\
 885                                (temp_val != -np.inf) and (temp_val != \
 886                                -sys.float_info.max) and (temp_val != \
 887                                sys.float_info.max):
 888                            return True
 889                        else:
 890                            return False
 891                    if tst_temp_val(temp_val):
 892                        step3str += ', value = ' + str(temp_val)
 893                    temp_val = k.children[1].children[2].value
 894                    if tst_temp_val(temp_val):
 895                        step3str += ', min = ' + str(temp_val)
 896                    temp_val = k.children[1].children[3].value
 897                    if tst_temp_val(temp_val):
 898                        step3str += ', max = ' + str(temp_val)
 899                    step3str += ')\n'
 900            step3str +='\n'
 901            pass
 902        if change['new']>=4:
 903            ranges = []
 904            if JPSLUtils.notebookenv != 'colab':
 905                # update ranges
 906                range_start = True
 907                new_range = []
 908                if len(range_plot.data)>0:
 909                    for i in range(len(range_plot.data[0].marker.color)):
 910                        if range_plot.data[0].marker.color[i] == range_plot_hilight:
 911                            new_range.append(i)
 912                            if not range_start:
 913                                ranges.append(new_range)
 914                                new_range = []
 915                            range_start = not range_start
 916            # update step 4 string
 917            covscalestr = 'False'
 918            if yerrtype.value == 'none':
 919                covscalestr = 'True'
 920            if len(ranges) > 0:
 921                range_chosen = True
 922                step4str = '# Define fit ranges\n'
 923                step4str += 'Yfiterr = copy.deepcopy(Yerr) # ranges not to ' \
 924                            'fit = np.inf\n'
 925                step4str += 'Xfitdata = copy.deepcopy(Xvals) # ranges where ' \
 926                            'fit not displayed = np.nan\n'
 927                for i in range(len(ranges)):
 928                    if i == 0 and ranges[0][0]>0:
 929                        step4str += 'Yfiterr[int(0):int('+str(ranges[0][0])+ \
 930                                    ')] = np.inf\n'
 931                        step4str += 'Xfitdata[int(0):int('+str(ranges[0][0])+\
 932                                    ')] = np.nan\n'
 933                    if (i + 1) < len(ranges):
 934                        step4str += 'Yfiterr[int('+str(ranges[i][1]+1)+\
 935                                    '):int('+str(ranges[i+1][0])+')] = np.inf\n'
 936                        step4str += 'Xfitdata[int('+str(ranges[i][1]+1)+ \
 937                                    '):int('+str(ranges[i+1][0])+')] = np.nan\n'
 938                    if i+1 == len(ranges):
 939                        step4str += 'Yfiterr[int('+str(ranges[i][1]+1)+\
 940                                    '):int('+str(len(range_plot.data[0].marker.
 941                                                color))+')] = np.inf\n'
 942                        step4str += 'Xfitdata[int('+str(ranges[i][1]+1)+\
 943                                '):int('+str(len(range_plot.data[0].marker.
 944                                            color))+')] = np.nan\n'
 945                step4str += '\n'
 946                step4str += '# Do fit\n'
 947                step4str += str(fitname)+' = fitmod.fit(Yvals, x=Xvals, ' \
 948                    'weights = 1/Yfiterr, scale_covar = '+covscalestr+', ' \
 949                    'nan_policy = \"omit\")\n\n'
 950            else:
 951                range_chosen = False
 952                step4str = '# Do fit\n'
 953                step4str += str(fitname)+' = fitmod.fit(Yvals, x=Xvals, ' \
 954                    'weights = 1/Yerr, scale_covar = '+covscalestr+', ' \
 955                    'nan_policy = \"omit\")\n\n'
 956            step4str += '# Calculate residuals (data - fit) because lmfit\n'
 957            step4str += '#  does not calculate for all points under all ' \
 958                        'conditions\n'
 959            step4str += 'resid = []\n'
 960            step4str += ('# explicit int(0) below avoids collisions with some '
 961                         'preparsers.\n')
 962            step4str += 'for i in range(int(0),len('+str(fitname)+'.data)):\n'
 963            step4str += '    resid.append('+str(fitname)+'.data[' \
 964                                        'i]-'+str(fitname)+'.best_fit[i])\n\n'
 965            pass
 966        if change['old'] == 4:
 967                # update step 5 string
 968                step5str = ''
 969                if range_chosen:
 970                    xstr = 'Xfitdata'
 971                    if not(extend_fit.value):
 972                        step5str += '# Delete residuals in ranges not fit\n'
 973                        step5str += '# and fit values that are not ' \
 974                                    'displayed.\n'
 975                        step5str += 'for i in range(len(resid)):\n'
 976                        step5str += '    if np.isnan(Xfitdata[i]):\n'
 977                        step5str += '        resid[i] = None\n'
 978                        step5str += '        '+str(fitname)+'.best_fit[i] = ' \
 979                                                   'None\n\n'
 980                else:
 981                    xstr = 'Xvals'
 982                errbarstr = ''
 983                if yerrtype.value!='none':
 984                    errbarstr = ', error_y_type=\"data\", error_y_array=Yerr'
 985                xresidstr = xstr
 986                if extend_fit.value:
 987                    xresidstr = 'Xvals'
 988                mirrorstr = ''
 989                if mirror_axes.value:
 990                    mirrorstr = ', mirror = True'
 991                    if mirror_ticks.value:
 992                        mirrorstr = ', mirror = \"ticks\"'
 993                # the plot
 994                step5str += '# Plot Results\n'
 995                step5str += ('# explicit int(..) below avoids collisions with '
 996                             'some preparsers.\n')
 997                step5str += str(figname) + ' = go.FigureWidget(' \
 998                                    'layout_template=\"'+str(
 999                                    plot_template.value)+'\")\n'
1000                step5str += str(figname)+ '.update_layout(title = \"'+ \
1001                                    str(plot_title.value)+'\",'
1002                text = ''
1003                if plot_aspect.value == 'auto':
1004                    text += 'autosize=True)\n'
1005                else:
1006                    if plot_size.value == 'tiny':
1007                        plot_width = 300
1008                    elif plot_size.value == 'small':
1009                        plot_width = 450
1010                    elif plot_size.value == 'medium':
1011                        plot_width = 800
1012                    elif plot_size.value == 'large':
1013                        plot_width = 1200
1014                    elif plot_size.value == 'huge':
1015                        plot_width = 2400
1016                    if plot_aspect.value == '16:9':
1017                        plot_height = int(9 * plot_width / 16)
1018                    elif plot_aspect.value == '5:3':
1019                        plot_height = int(3 * plot_width / 5)
1020                    elif plot_aspect.value == '7:5':
1021                        plot_height = int(5 * plot_width / 7)
1022                    elif plot_aspect.value == '4:3':
1023                        plot_height = int(3 * plot_width / 4)
1024                    elif plot_aspect.value == '10:8':
1025                        plot_height = int(8 * plot_width / 10)
1026                    elif plot_aspect.value == '1:1':
1027                        plot_height = plot_width
1028                    text += 'autosize=False, width = int('
1029                    text += str(plot_width) + '), height=int('
1030                    text += str(plot_height) + '))\n'
1031                step5str += text
1032                step5str += str(figname) + '.set_subplots(rows=int(2), cols=int(1), ' \
1033                                           'row_heights=[0.2,0.8], ' \
1034                                           'shared_xaxes=True)\n'
1035                step5str += 'scat = go.Scatter(y=resid,x='+xresidstr+', ' \
1036                                    'mode=\"markers\",' \
1037                                    'name = \"residuals\"'+errbarstr+')\n'
1038                step5str += str(figname) + '.update_yaxes(title = ' \
1039                                        '\"Residuals\", ' \
1040                            'row=int(1), col=int(1), zeroline=True, zerolinecolor = ' \
1041                            '\"lightgrey\"'+str(mirrorstr)+')\n'
1042                if mirror_axes.value:
1043                    step5str += str(figname) + '.update_xaxes(' \
1044                                           'row=int(1), col=int(1)'+str(mirrorstr)+')\n'
1045                step5str += str(figname) + '.add_trace(scat,col=int(1),row=int(1))\n'
1046                step5str += 'scat = go.Scatter(x=Xvals, y=Yvals, ' \
1047                            'mode=\"markers\", name=tracename'+errbarstr+')\n'
1048                step5str += str(figname) + '.add_trace(scat, col=int(1), ' \
1049                                           'row=int(2))\n'
1050                step5str += str(figname) + '.update_yaxes(title = ' \
1051                                           '\"'+Y_label.value+'\", ' \
1052                                           'row=int(2), col=int(1)'+str(mirrorstr)+')\n'
1053                step5str += str(figname) + '.update_xaxes(title = ' \
1054                                           '\"'+X_label.value+'\", ' \
1055                                           'row=int(2), col=int(1)'+str(mirrorstr)+')\n'
1056                if extend_fit.value:
1057                    step5str += 'scat = go.Scatter(y='+str(
1058                        fitname)+'.best_fit, x=Xvals, mode=\"lines\", '\
1059                                'line_color = \"black\", ' \
1060                                'name=\"extrapolated\",' \
1061                                 'line_dash=\"dash\")\n'
1062                    step5str += str(figname) + '.add_trace(scat, col=int(1), ' \
1063                                               'row=int(2))\n'
1064                step5str += 'scat = go.Scatter(y='+str(fitname)+'.best_fit,' \
1065                                    'x='+xstr+', mode=\"lines\", ' \
1066                                    'name=\"fit\", line_color = ' \
1067                                    '\"black\", line_dash=\"solid\")\n'
1068                step5str += str(figname) + '.add_trace(scat,col=int(1),row=int(2))\n'
1069                step5str += str(figname) + '.show(config = ' \
1070                                            '{\'toImageButtonOptions\': {' \
1071                                            '\'format\': \'svg\'}})\n\n'
1072                pass
1073        if change['new'] == 3 and JPSLUtils.notebookenv != 'colab':
1074            df = friendly_to_object[whichframe.value]
1075            rangex = df[Xcoord.value]
1076            rangey = df[Ycoord.value]
1077            c =[]
1078            s = []
1079            if len(range_plot.data)> 0 and len(range_plot.data[
1080                0].marker.color) == len(range_plot.data[0]['x']):
1081                c = list(range_plot.data[0].marker.color)
1082                s = list(range_plot.data[0].marker.size)
1083            range_plot.data=[]
1084            range_plot.add_scatter(x=rangex,y=rangey, mode = 'markers',
1085                                   line_color = range_plot_line_color,
1086                                   marker_size = range_plot_marker_size)
1087            if len(range_plot.data[0]['x']) == len(c):
1088                with range_plot.batch_update():
1089                    range_plot.data[0].marker.color = c
1090                    range_plot.data[0].marker.size = s
1091            range_plot.data[0].on_click(update_range_point)
1092        if change['new'] ==5:
1093            if X_label.value == '' or Y_label.value == '':
1094                makeplot_notices.activate_notice(2)
1095                dofitbut.disabled = True
1096                dofitbut.button_style = ''
1097            else:
1098                makeplot_notices.deactivate_notice(2)
1099            if Xcoord.value == 'Choose X-coordinate.' or \
1100                    Ycoord.value == 'Choose X-coordinate.':
1101                makeplot_notices.activate_notice(0)
1102                dofitbut.disabled = True
1103                dofitbut.button_style = ''
1104            else:
1105                makeplot_notices.deactivate_notice(0)
1106            if modeldrop.value == '':
1107                makeplot_notices.activate_notice(1)
1108                dofitbut.disabled = True
1109                dofitbut.button_style = ''
1110            else:
1111                makeplot_notices.deactivate_notice(1)
1112            step6noticebox.value = makeplot_notices.notice_html()
1113        if len(makeplot_notices.get_active()) == 0:
1114            dofitbut.disabled = False
1115            dofitbut.button_style = 'success'
1116        # the best fit equation
1117        step6str = '# Display best fit equation\n'
1118        step6str += fitresultstrs[modeldrop.value](fitname)
1119        if JPSLUtils.notebookenv == 'NBClassic':
1120            JPSLUtils.select_containing_cell('pandasfitGUI')
1121            JPSLUtils.replace_text_of_next_cell(importstr + step1str + \
1122                                                step2str + step3str + \
1123                                                step4str + step5str + step6str)
1124        else:
1125            codearea.sniptext.value = importstr + step1str + step2str + \
1126                                      step3str + step4str + step5str + \
1127                                      pseudoLatexToLatex(step6str)
1128        pass
1129
1130    steps.observe(tab_changed, names = 'selected_index')
1131    with output:
1132        display(steps)
1133    if JPSLUtils.notebookenv == 'NBClassic':
1134        display(output)
1135        select_containing_cell('pandasfitGUI')
1136        new_cell_immediately_below()
1137    else:
1138        codearea = build_run_snip_widget('', output)
1139        with output:
1140            display(codearea)
1141        display(output)
1142    pass

If passed no parameters this will look for all the dataframes in the user namespace and make them available for plotting. Once a dataframe is chosen only the numerical columns from that dataframe will be available for inclusion in the plotting expression.

This GUI produces code to use the lmfit package to fit data and the plotly interactive plotting package to display the results.

If you wish to allow only certain dataframes or have them show up as user friendly names in the menus provide that information in the first paramater df_info.

To allow inclusion of text columns pass True for show_text_col.

Parameters
  • bool show_text_col: (default = False). When True columns containing text will be shown.

  • list df_info: List of Lists [[object,globalname, userfriendly]],..]

    • object -- pandas.DataFrame
    • globalname -- string name of the object in the user global name space.
    • userfriendly -- string name to display for user selection.

:keyword string figname: string used to override default python name for figure.

:keyword string fitname: string used to override default python name for fit.

:keyword bool findframes: default = True. If set to false and dataframes are passed in dfs_info, will not search for dataframes in the user namespace.