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
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.
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.