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