pyerrors.fits
1import gc 2from collections.abc import Sequence 3import warnings 4import numpy as np 5import autograd.numpy as anp 6import scipy.optimize 7import scipy.stats 8import matplotlib.pyplot as plt 9from matplotlib import gridspec 10from scipy.odr import ODR, Model, RealData 11import iminuit 12from autograd import jacobian as auto_jacobian 13from autograd import hessian as auto_hessian 14from autograd import elementwise_grad as egrad 15from numdifftools import Jacobian as num_jacobian 16from numdifftools import Hessian as num_hessian 17from .obs import Obs, derived_observable, covariance, cov_Obs 18 19 20class Fit_result(Sequence): 21 """Represents fit results. 22 23 Attributes 24 ---------- 25 fit_parameters : list 26 results for the individual fit parameters, 27 also accessible via indices. 28 """ 29 30 def __init__(self): 31 self.fit_parameters = None 32 33 def __getitem__(self, idx): 34 return self.fit_parameters[idx] 35 36 def __len__(self): 37 return len(self.fit_parameters) 38 39 def gamma_method(self, **kwargs): 40 """Apply the gamma method to all fit parameters""" 41 [o.gamma_method(**kwargs) for o in self.fit_parameters] 42 43 def __str__(self): 44 my_str = 'Goodness of fit:\n' 45 if hasattr(self, 'chisquare_by_dof'): 46 my_str += '\u03C7\u00b2/d.o.f. = ' + f'{self.chisquare_by_dof:2.6f}' + '\n' 47 elif hasattr(self, 'residual_variance'): 48 my_str += 'residual variance = ' + f'{self.residual_variance:2.6f}' + '\n' 49 if hasattr(self, 'chisquare_by_expected_chisquare'): 50 my_str += '\u03C7\u00b2/\u03C7\u00b2exp = ' + f'{self.chisquare_by_expected_chisquare:2.6f}' + '\n' 51 if hasattr(self, 'p_value'): 52 my_str += 'p-value = ' + f'{self.p_value:2.4f}' + '\n' 53 my_str += 'Fit parameters:\n' 54 for i_par, par in enumerate(self.fit_parameters): 55 my_str += str(i_par) + '\t' + ' ' * int(par >= 0) + str(par).rjust(int(par < 0.0)) + '\n' 56 return my_str 57 58 def __repr__(self): 59 m = max(map(len, list(self.__dict__.keys()))) + 1 60 return '\n'.join([key.rjust(m) + ': ' + repr(value) for key, value in sorted(self.__dict__.items())]) 61 62 63def least_squares(x, y, func, priors=None, silent=False, **kwargs): 64 r'''Performs a non-linear fit to y = func(x). 65 66 Parameters 67 ---------- 68 x : list 69 list of floats. 70 y : list 71 list of Obs. 72 func : object 73 fit function, has to be of the form 74 75 ```python 76 import autograd.numpy as anp 77 78 def func(a, x): 79 return a[0] + a[1] * x + a[2] * anp.sinh(x) 80 ``` 81 82 For multiple x values func can be of the form 83 84 ```python 85 def func(a, x): 86 (x1, x2) = x 87 return a[0] * x1 ** 2 + a[1] * x2 88 ``` 89 90 It is important that all numpy functions refer to autograd.numpy, otherwise the differentiation 91 will not work. 92 priors : list, optional 93 priors has to be a list with an entry for every parameter in the fit. The entries can either be 94 Obs (e.g. results from a previous fit) or strings containing a value and an error formatted like 95 0.548(23), 500(40) or 0.5(0.4) 96 silent : bool, optional 97 If true all output to the console is omitted (default False). 98 initial_guess : list 99 can provide an initial guess for the input parameters. Relevant for 100 non-linear fits with many parameters. In case of correlated fits the guess is used to perform 101 an uncorrelated fit which then serves as guess for the correlated fit. 102 method : str, optional 103 can be used to choose an alternative method for the minimization of chisquare. 104 The possible methods are the ones which can be used for scipy.optimize.minimize and 105 migrad of iminuit. If no method is specified, Levenberg-Marquard is used. 106 Reliable alternatives are migrad, Powell and Nelder-Mead. 107 correlated_fit : bool 108 If True, use the full inverse covariance matrix in the definition of the chisquare cost function. 109 For details about how the covariance matrix is estimated see `pyerrors.obs.covariance`. 110 In practice the correlation matrix is Cholesky decomposed and inverted (instead of the covariance matrix). 111 This procedure should be numerically more stable as the correlation matrix is typically better conditioned (Jacobi preconditioning). 112 At the moment this option only works for `prior==None` and when no `method` is given. 113 expected_chisquare : bool 114 If True estimates the expected chisquare which is 115 corrected by effects caused by correlated input data (default False). 116 resplot : bool 117 If True, a plot which displays fit, data and residuals is generated (default False). 118 qqplot : bool 119 If True, a quantile-quantile plot of the fit result is generated (default False). 120 num_grad : bool 121 Use numerical differentation instead of automatic differentiation to perform the error propagation (default False). 122 ''' 123 if priors is not None: 124 return _prior_fit(x, y, func, priors, silent=silent, **kwargs) 125 else: 126 return _standard_fit(x, y, func, silent=silent, **kwargs) 127 128 129def total_least_squares(x, y, func, silent=False, **kwargs): 130 r'''Performs a non-linear fit to y = func(x) and returns a list of Obs corresponding to the fit parameters. 131 132 Parameters 133 ---------- 134 x : list 135 list of Obs, or a tuple of lists of Obs 136 y : list 137 list of Obs. The dvalues of the Obs are used as x- and yerror for the fit. 138 func : object 139 func has to be of the form 140 141 ```python 142 import autograd.numpy as anp 143 144 def func(a, x): 145 return a[0] + a[1] * x + a[2] * anp.sinh(x) 146 ``` 147 148 For multiple x values func can be of the form 149 150 ```python 151 def func(a, x): 152 (x1, x2) = x 153 return a[0] * x1 ** 2 + a[1] * x2 154 ``` 155 156 It is important that all numpy functions refer to autograd.numpy, otherwise the differentiation 157 will not work. 158 silent : bool, optional 159 If true all output to the console is omitted (default False). 160 initial_guess : list 161 can provide an initial guess for the input parameters. Relevant for non-linear 162 fits with many parameters. 163 expected_chisquare : bool 164 If true prints the expected chisquare which is 165 corrected by effects caused by correlated input data. 166 This can take a while as the full correlation matrix 167 has to be calculated (default False). 168 num_grad : bool 169 Use numerical differentation instead of automatic differentiation to perform the error propagation (default False). 170 171 Notes 172 ----- 173 Based on the orthogonal distance regression module of scipy 174 ''' 175 176 output = Fit_result() 177 178 output.fit_function = func 179 180 x = np.array(x) 181 182 x_shape = x.shape 183 184 if kwargs.get('num_grad') is True: 185 jacobian = num_jacobian 186 hessian = num_hessian 187 else: 188 jacobian = auto_jacobian 189 hessian = auto_hessian 190 191 if not callable(func): 192 raise TypeError('func has to be a function.') 193 194 for i in range(42): 195 try: 196 func(np.arange(i), x.T[0]) 197 except TypeError: 198 continue 199 except IndexError: 200 continue 201 else: 202 break 203 else: 204 raise RuntimeError("Fit function is not valid.") 205 206 n_parms = i 207 if not silent: 208 print('Fit with', n_parms, 'parameter' + 's' * (n_parms > 1)) 209 210 x_f = np.vectorize(lambda o: o.value)(x) 211 dx_f = np.vectorize(lambda o: o.dvalue)(x) 212 y_f = np.array([o.value for o in y]) 213 dy_f = np.array([o.dvalue for o in y]) 214 215 if np.any(np.asarray(dx_f) <= 0.0): 216 raise Exception('No x errors available, run the gamma method first.') 217 218 if np.any(np.asarray(dy_f) <= 0.0): 219 raise Exception('No y errors available, run the gamma method first.') 220 221 if 'initial_guess' in kwargs: 222 x0 = kwargs.get('initial_guess') 223 if len(x0) != n_parms: 224 raise Exception('Initial guess does not have the correct length: %d vs. %d' % (len(x0), n_parms)) 225 else: 226 x0 = [1] * n_parms 227 228 data = RealData(x_f, y_f, sx=dx_f, sy=dy_f) 229 model = Model(func) 230 odr = ODR(data, model, x0, partol=np.finfo(np.float64).eps) 231 odr.set_job(fit_type=0, deriv=1) 232 out = odr.run() 233 234 output.residual_variance = out.res_var 235 236 output.method = 'ODR' 237 238 output.message = out.stopreason 239 240 output.xplus = out.xplus 241 242 if not silent: 243 print('Method: ODR') 244 print(*out.stopreason) 245 print('Residual variance:', output.residual_variance) 246 247 if out.info > 3: 248 raise Exception('The minimization procedure did not converge.') 249 250 m = x_f.size 251 252 def odr_chisquare(p): 253 model = func(p[:n_parms], p[n_parms:].reshape(x_shape)) 254 chisq = anp.sum(((y_f - model) / dy_f) ** 2) + anp.sum(((x_f - p[n_parms:].reshape(x_shape)) / dx_f) ** 2) 255 return chisq 256 257 if kwargs.get('expected_chisquare') is True: 258 W = np.diag(1 / np.asarray(np.concatenate((dy_f.ravel(), dx_f.ravel())))) 259 260 if kwargs.get('covariance') is not None: 261 cov = kwargs.get('covariance') 262 else: 263 cov = covariance(np.concatenate((y, x.ravel()))) 264 265 number_of_x_parameters = int(m / x_f.shape[-1]) 266 267 old_jac = jacobian(func)(out.beta, out.xplus) 268 fused_row1 = np.concatenate((old_jac, np.concatenate((number_of_x_parameters * [np.zeros(old_jac.shape)]), axis=0))) 269 fused_row2 = np.concatenate((jacobian(lambda x, y: func(y, x))(out.xplus, out.beta).reshape(x_f.shape[-1], x_f.shape[-1] * number_of_x_parameters), np.identity(number_of_x_parameters * old_jac.shape[0]))) 270 new_jac = np.concatenate((fused_row1, fused_row2), axis=1) 271 272 A = W @ new_jac 273 P_phi = A @ np.linalg.pinv(A.T @ A) @ A.T 274 expected_chisquare = np.trace((np.identity(P_phi.shape[0]) - P_phi) @ W @ cov @ W) 275 if expected_chisquare <= 0.0: 276 warnings.warn("Negative expected_chisquare.", RuntimeWarning) 277 expected_chisquare = np.abs(expected_chisquare) 278 output.chisquare_by_expected_chisquare = odr_chisquare(np.concatenate((out.beta, out.xplus.ravel()))) / expected_chisquare 279 if not silent: 280 print('chisquare/expected_chisquare:', 281 output.chisquare_by_expected_chisquare) 282 283 fitp = out.beta 284 try: 285 hess = hessian(odr_chisquare)(np.concatenate((fitp, out.xplus.ravel()))) 286 except TypeError: 287 raise Exception("It is required to use autograd.numpy instead of numpy within fit functions, see the documentation for details.") from None 288 289 def odr_chisquare_compact_x(d): 290 model = func(d[:n_parms], d[n_parms:n_parms + m].reshape(x_shape)) 291 chisq = anp.sum(((y_f - model) / dy_f) ** 2) + anp.sum(((d[n_parms + m:].reshape(x_shape) - d[n_parms:n_parms + m].reshape(x_shape)) / dx_f) ** 2) 292 return chisq 293 294 jac_jac_x = hessian(odr_chisquare_compact_x)(np.concatenate((fitp, out.xplus.ravel(), x_f.ravel()))) 295 296 # Compute hess^{-1} @ jac_jac_x[:n_parms + m, n_parms + m:] using LAPACK dgesv 297 try: 298 deriv_x = -scipy.linalg.solve(hess, jac_jac_x[:n_parms + m, n_parms + m:]) 299 except np.linalg.LinAlgError: 300 raise Exception("Cannot invert hessian matrix.") 301 302 def odr_chisquare_compact_y(d): 303 model = func(d[:n_parms], d[n_parms:n_parms + m].reshape(x_shape)) 304 chisq = anp.sum(((d[n_parms + m:] - model) / dy_f) ** 2) + anp.sum(((x_f - d[n_parms:n_parms + m].reshape(x_shape)) / dx_f) ** 2) 305 return chisq 306 307 jac_jac_y = hessian(odr_chisquare_compact_y)(np.concatenate((fitp, out.xplus.ravel(), y_f))) 308 309 # Compute hess^{-1} @ jac_jac_y[:n_parms + m, n_parms + m:] using LAPACK dgesv 310 try: 311 deriv_y = -scipy.linalg.solve(hess, jac_jac_y[:n_parms + m, n_parms + m:]) 312 except np.linalg.LinAlgError: 313 raise Exception("Cannot invert hessian matrix.") 314 315 result = [] 316 for i in range(n_parms): 317 result.append(derived_observable(lambda my_var, **kwargs: (my_var[0] + np.finfo(np.float64).eps) / (x.ravel()[0].value + np.finfo(np.float64).eps) * out.beta[i], list(x.ravel()) + list(y), man_grad=list(deriv_x[i]) + list(deriv_y[i]))) 318 319 output.fit_parameters = result 320 321 output.odr_chisquare = odr_chisquare(np.concatenate((out.beta, out.xplus.ravel()))) 322 output.dof = x.shape[-1] - n_parms 323 output.p_value = 1 - scipy.stats.chi2.cdf(output.odr_chisquare, output.dof) 324 325 return output 326 327 328def _prior_fit(x, y, func, priors, silent=False, **kwargs): 329 output = Fit_result() 330 331 output.fit_function = func 332 333 x = np.asarray(x) 334 335 if kwargs.get('num_grad') is True: 336 hessian = num_hessian 337 else: 338 hessian = auto_hessian 339 340 if not callable(func): 341 raise TypeError('func has to be a function.') 342 343 for i in range(100): 344 try: 345 func(np.arange(i), 0) 346 except TypeError: 347 continue 348 except IndexError: 349 continue 350 else: 351 break 352 else: 353 raise RuntimeError("Fit function is not valid.") 354 355 n_parms = i 356 357 if n_parms != len(priors): 358 raise Exception('Priors does not have the correct length.') 359 360 def extract_val_and_dval(string): 361 split_string = string.split('(') 362 if '.' in split_string[0] and '.' not in split_string[1][:-1]: 363 factor = 10 ** -len(split_string[0].partition('.')[2]) 364 else: 365 factor = 1 366 return float(split_string[0]), float(split_string[1][:-1]) * factor 367 368 loc_priors = [] 369 for i_n, i_prior in enumerate(priors): 370 if isinstance(i_prior, Obs): 371 loc_priors.append(i_prior) 372 else: 373 loc_val, loc_dval = extract_val_and_dval(i_prior) 374 loc_priors.append(cov_Obs(loc_val, loc_dval ** 2, '#prior' + str(i_n) + f"_{np.random.randint(2147483647):010d}")) 375 376 output.priors = loc_priors 377 378 if not silent: 379 print('Fit with', n_parms, 'parameter' + 's' * (n_parms > 1)) 380 381 y_f = [o.value for o in y] 382 dy_f = [o.dvalue for o in y] 383 384 if np.any(np.asarray(dy_f) <= 0.0): 385 raise Exception('No y errors available, run the gamma method first.') 386 387 p_f = [o.value for o in loc_priors] 388 dp_f = [o.dvalue for o in loc_priors] 389 390 if np.any(np.asarray(dp_f) <= 0.0): 391 raise Exception('No prior errors available, run the gamma method first.') 392 393 if 'initial_guess' in kwargs: 394 x0 = kwargs.get('initial_guess') 395 if len(x0) != n_parms: 396 raise Exception('Initial guess does not have the correct length.') 397 else: 398 x0 = p_f 399 400 def chisqfunc(p): 401 model = func(p, x) 402 chisq = anp.sum(((y_f - model) / dy_f) ** 2) + anp.sum(((p_f - p) / dp_f) ** 2) 403 return chisq 404 405 if not silent: 406 print('Method: migrad') 407 408 m = iminuit.Minuit(chisqfunc, x0) 409 m.errordef = 1 410 m.print_level = 0 411 if 'tol' in kwargs: 412 m.tol = kwargs.get('tol') 413 else: 414 m.tol = 1e-4 415 m.migrad() 416 params = np.asarray(m.values) 417 418 output.chisquare_by_dof = m.fval / len(x) 419 420 output.method = 'migrad' 421 422 if not silent: 423 print('chisquare/d.o.f.:', output.chisquare_by_dof) 424 425 if not m.fmin.is_valid: 426 raise Exception('The minimization procedure did not converge.') 427 428 hess = hessian(chisqfunc)(params) 429 hess_inv = np.linalg.pinv(hess) 430 431 def chisqfunc_compact(d): 432 model = func(d[:n_parms], x) 433 chisq = anp.sum(((d[n_parms: n_parms + len(x)] - model) / dy_f) ** 2) + anp.sum(((d[n_parms + len(x):] - d[:n_parms]) / dp_f) ** 2) 434 return chisq 435 436 jac_jac = hessian(chisqfunc_compact)(np.concatenate((params, y_f, p_f))) 437 438 deriv = -hess_inv @ jac_jac[:n_parms, n_parms:] 439 440 result = [] 441 for i in range(n_parms): 442 result.append(derived_observable(lambda x, **kwargs: (x[0] + np.finfo(np.float64).eps) / (y[0].value + np.finfo(np.float64).eps) * params[i], list(y) + list(loc_priors), man_grad=list(deriv[i]))) 443 444 output.fit_parameters = result 445 output.chisquare = chisqfunc(np.asarray(params)) 446 447 if kwargs.get('resplot') is True: 448 residual_plot(x, y, func, result) 449 450 if kwargs.get('qqplot') is True: 451 qqplot(x, y, func, result) 452 453 return output 454 455 456def _standard_fit(x, y, func, silent=False, **kwargs): 457 458 output = Fit_result() 459 460 output.fit_function = func 461 462 x = np.asarray(x) 463 464 if kwargs.get('num_grad') is True: 465 jacobian = num_jacobian 466 hessian = num_hessian 467 else: 468 jacobian = auto_jacobian 469 hessian = auto_hessian 470 471 if x.shape[-1] != len(y): 472 raise Exception('x and y input have to have the same length') 473 474 if len(x.shape) > 2: 475 raise Exception('Unknown format for x values') 476 477 if not callable(func): 478 raise TypeError('func has to be a function.') 479 480 for i in range(42): 481 try: 482 func(np.arange(i), x.T[0]) 483 except TypeError: 484 continue 485 except IndexError: 486 continue 487 else: 488 break 489 else: 490 raise RuntimeError("Fit function is not valid.") 491 492 n_parms = i 493 494 if not silent: 495 print('Fit with', n_parms, 'parameter' + 's' * (n_parms > 1)) 496 497 y_f = [o.value for o in y] 498 dy_f = [o.dvalue for o in y] 499 500 if np.any(np.asarray(dy_f) <= 0.0): 501 raise Exception('No y errors available, run the gamma method first.') 502 503 if 'initial_guess' in kwargs: 504 x0 = kwargs.get('initial_guess') 505 if len(x0) != n_parms: 506 raise Exception('Initial guess does not have the correct length: %d vs. %d' % (len(x0), n_parms)) 507 else: 508 x0 = [0.1] * n_parms 509 510 if kwargs.get('correlated_fit') is True: 511 corr = covariance(y, correlation=True, **kwargs) 512 covdiag = np.diag(1 / np.asarray(dy_f)) 513 condn = np.linalg.cond(corr) 514 if condn > 0.1 / np.finfo(float).eps: 515 raise Exception(f"Cannot invert correlation matrix as its condition number exceeds machine precision ({condn:1.2e})") 516 if condn > 1e13: 517 warnings.warn("Correlation matrix may be ill-conditioned, condition number: {%1.2e}" % (condn), RuntimeWarning) 518 chol = np.linalg.cholesky(corr) 519 chol_inv = scipy.linalg.solve_triangular(chol, covdiag, lower=True) 520 521 def chisqfunc_corr(p): 522 model = func(p, x) 523 chisq = anp.sum(anp.dot(chol_inv, (y_f - model)) ** 2) 524 return chisq 525 526 def chisqfunc(p): 527 model = func(p, x) 528 chisq = anp.sum(((y_f - model) / dy_f) ** 2) 529 return chisq 530 531 output.method = kwargs.get('method', 'Levenberg-Marquardt') 532 if not silent: 533 print('Method:', output.method) 534 535 if output.method != 'Levenberg-Marquardt': 536 if output.method == 'migrad': 537 fit_result = iminuit.minimize(chisqfunc, x0, tol=1e-4) # Stopping criterion 0.002 * tol * errordef 538 if kwargs.get('correlated_fit') is True: 539 fit_result = iminuit.minimize(chisqfunc_corr, fit_result.x, tol=1e-4) # Stopping criterion 0.002 * tol * errordef 540 output.iterations = fit_result.nfev 541 else: 542 fit_result = scipy.optimize.minimize(chisqfunc, x0, method=kwargs.get('method'), tol=1e-12) 543 if kwargs.get('correlated_fit') is True: 544 fit_result = scipy.optimize.minimize(chisqfunc_corr, fit_result.x, method=kwargs.get('method'), tol=1e-12) 545 output.iterations = fit_result.nit 546 547 chisquare = fit_result.fun 548 549 else: 550 if kwargs.get('correlated_fit') is True: 551 def chisqfunc_residuals_corr(p): 552 model = func(p, x) 553 chisq = anp.dot(chol_inv, (y_f - model)) 554 return chisq 555 556 def chisqfunc_residuals(p): 557 model = func(p, x) 558 chisq = ((y_f - model) / dy_f) 559 return chisq 560 561 fit_result = scipy.optimize.least_squares(chisqfunc_residuals, x0, method='lm', ftol=1e-15, gtol=1e-15, xtol=1e-15) 562 if kwargs.get('correlated_fit') is True: 563 fit_result = scipy.optimize.least_squares(chisqfunc_residuals_corr, fit_result.x, method='lm', ftol=1e-15, gtol=1e-15, xtol=1e-15) 564 565 chisquare = np.sum(fit_result.fun ** 2) 566 if kwargs.get('correlated_fit') is True: 567 assert np.isclose(chisquare, chisqfunc_corr(fit_result.x), atol=1e-14) 568 else: 569 assert np.isclose(chisquare, chisqfunc(fit_result.x), atol=1e-14) 570 571 output.iterations = fit_result.nfev 572 573 if not fit_result.success: 574 raise Exception('The minimization procedure did not converge.') 575 576 if x.shape[-1] - n_parms > 0: 577 output.chisquare_by_dof = chisquare / (x.shape[-1] - n_parms) 578 else: 579 output.chisquare_by_dof = float('nan') 580 581 output.message = fit_result.message 582 if not silent: 583 print(fit_result.message) 584 print('chisquare/d.o.f.:', output.chisquare_by_dof) 585 586 if kwargs.get('expected_chisquare') is True: 587 if kwargs.get('correlated_fit') is not True: 588 W = np.diag(1 / np.asarray(dy_f)) 589 cov = covariance(y) 590 A = W @ jacobian(func)(fit_result.x, x) 591 P_phi = A @ np.linalg.pinv(A.T @ A) @ A.T 592 expected_chisquare = np.trace((np.identity(x.shape[-1]) - P_phi) @ W @ cov @ W) 593 output.chisquare_by_expected_chisquare = chisquare / expected_chisquare 594 if not silent: 595 print('chisquare/expected_chisquare:', 596 output.chisquare_by_expected_chisquare) 597 598 fitp = fit_result.x 599 try: 600 if kwargs.get('correlated_fit') is True: 601 hess = hessian(chisqfunc_corr)(fitp) 602 else: 603 hess = hessian(chisqfunc)(fitp) 604 except TypeError: 605 raise Exception("It is required to use autograd.numpy instead of numpy within fit functions, see the documentation for details.") from None 606 607 if kwargs.get('correlated_fit') is True: 608 def chisqfunc_compact(d): 609 model = func(d[:n_parms], x) 610 chisq = anp.sum(anp.dot(chol_inv, (d[n_parms:] - model)) ** 2) 611 return chisq 612 613 else: 614 def chisqfunc_compact(d): 615 model = func(d[:n_parms], x) 616 chisq = anp.sum(((d[n_parms:] - model) / dy_f) ** 2) 617 return chisq 618 619 jac_jac = hessian(chisqfunc_compact)(np.concatenate((fitp, y_f))) 620 621 # Compute hess^{-1} @ jac_jac[:n_parms, n_parms:] using LAPACK dgesv 622 try: 623 deriv = -scipy.linalg.solve(hess, jac_jac[:n_parms, n_parms:]) 624 except np.linalg.LinAlgError: 625 raise Exception("Cannot invert hessian matrix.") 626 627 result = [] 628 for i in range(n_parms): 629 result.append(derived_observable(lambda x, **kwargs: (x[0] + np.finfo(np.float64).eps) / (y[0].value + np.finfo(np.float64).eps) * fit_result.x[i], list(y), man_grad=list(deriv[i]))) 630 631 output.fit_parameters = result 632 633 output.chisquare = chisquare 634 output.dof = x.shape[-1] - n_parms 635 output.p_value = 1 - scipy.stats.chi2.cdf(output.chisquare, output.dof) 636 637 if kwargs.get('resplot') is True: 638 residual_plot(x, y, func, result) 639 640 if kwargs.get('qqplot') is True: 641 qqplot(x, y, func, result) 642 643 return output 644 645 646def fit_lin(x, y, **kwargs): 647 """Performs a linear fit to y = n + m * x and returns two Obs n, m. 648 649 Parameters 650 ---------- 651 x : list 652 Can either be a list of floats in which case no xerror is assumed, or 653 a list of Obs, where the dvalues of the Obs are used as xerror for the fit. 654 y : list 655 List of Obs, the dvalues of the Obs are used as yerror for the fit. 656 """ 657 658 def f(a, x): 659 y = a[0] + a[1] * x 660 return y 661 662 if all(isinstance(n, Obs) for n in x): 663 out = total_least_squares(x, y, f, **kwargs) 664 return out.fit_parameters 665 elif all(isinstance(n, float) or isinstance(n, int) for n in x) or isinstance(x, np.ndarray): 666 out = least_squares(x, y, f, **kwargs) 667 return out.fit_parameters 668 else: 669 raise Exception('Unsupported types for x') 670 671 672def qqplot(x, o_y, func, p): 673 """Generates a quantile-quantile plot of the fit result which can be used to 674 check if the residuals of the fit are gaussian distributed. 675 """ 676 677 residuals = [] 678 for i_x, i_y in zip(x, o_y): 679 residuals.append((i_y - func(p, i_x)) / i_y.dvalue) 680 residuals = sorted(residuals) 681 my_y = [o.value for o in residuals] 682 probplot = scipy.stats.probplot(my_y) 683 my_x = probplot[0][0] 684 plt.figure(figsize=(8, 8 / 1.618)) 685 plt.errorbar(my_x, my_y, fmt='o') 686 fit_start = my_x[0] 687 fit_stop = my_x[-1] 688 samples = np.arange(fit_start, fit_stop, 0.01) 689 plt.plot(samples, samples, 'k--', zorder=11, label='Standard normal distribution') 690 plt.plot(samples, probplot[1][0] * samples + probplot[1][1], zorder=10, label='Least squares fit, r=' + str(np.around(probplot[1][2], 3)), marker='', ls='-') 691 692 plt.xlabel('Theoretical quantiles') 693 plt.ylabel('Ordered Values') 694 plt.legend() 695 plt.draw() 696 697 698def residual_plot(x, y, func, fit_res): 699 """ Generates a plot which compares the fit to the data and displays the corresponding residuals""" 700 sorted_x = sorted(x) 701 xstart = sorted_x[0] - 0.5 * (sorted_x[1] - sorted_x[0]) 702 xstop = sorted_x[-1] + 0.5 * (sorted_x[-1] - sorted_x[-2]) 703 x_samples = np.arange(xstart, xstop + 0.01, 0.01) 704 705 plt.figure(figsize=(8, 8 / 1.618)) 706 gs = gridspec.GridSpec(2, 1, height_ratios=[3, 1], wspace=0.0, hspace=0.0) 707 ax0 = plt.subplot(gs[0]) 708 ax0.errorbar(x, [o.value for o in y], yerr=[o.dvalue for o in y], ls='none', fmt='o', capsize=3, markersize=5, label='Data') 709 ax0.plot(x_samples, func([o.value for o in fit_res], x_samples), label='Fit', zorder=10, ls='-', ms=0) 710 ax0.set_xticklabels([]) 711 ax0.set_xlim([xstart, xstop]) 712 ax0.set_xticklabels([]) 713 ax0.legend() 714 715 residuals = (np.asarray([o.value for o in y]) - func([o.value for o in fit_res], x)) / np.asarray([o.dvalue for o in y]) 716 ax1 = plt.subplot(gs[1]) 717 ax1.plot(x, residuals, 'ko', ls='none', markersize=5) 718 ax1.tick_params(direction='out') 719 ax1.tick_params(axis="x", bottom=True, top=True, labelbottom=True) 720 ax1.axhline(y=0.0, ls='--', color='k', marker=" ") 721 ax1.fill_between(x_samples, -1.0, 1.0, alpha=0.1, facecolor='k') 722 ax1.set_xlim([xstart, xstop]) 723 ax1.set_ylabel('Residuals') 724 plt.subplots_adjust(wspace=None, hspace=None) 725 plt.draw() 726 727 728def error_band(x, func, beta): 729 """Returns the error band for an array of sample values x, for given fit function func with optimized parameters beta.""" 730 cov = covariance(beta) 731 if np.any(np.abs(cov - cov.T) > 1000 * np.finfo(np.float64).eps): 732 warnings.warn("Covariance matrix is not symmetric within floating point precision", RuntimeWarning) 733 734 deriv = [] 735 for i, item in enumerate(x): 736 deriv.append(np.array(egrad(func)([o.value for o in beta], item))) 737 738 err = [] 739 for i, item in enumerate(x): 740 err.append(np.sqrt(deriv[i] @ cov @ deriv[i])) 741 err = np.array(err) 742 743 return err 744 745 746def ks_test(objects=None): 747 """Performs a Kolmogorov–Smirnov test for the p-values of all fit object. 748 749 Parameters 750 ---------- 751 objects : list 752 List of fit results to include in the analysis (optional). 753 """ 754 755 if objects is None: 756 obs_list = [] 757 for obj in gc.get_objects(): 758 if isinstance(obj, Fit_result): 759 obs_list.append(obj) 760 else: 761 obs_list = objects 762 763 p_values = [o.p_value for o in obs_list] 764 765 bins = len(p_values) 766 x = np.arange(0, 1.001, 0.001) 767 plt.plot(x, x, 'k', zorder=1) 768 plt.xlim(0, 1) 769 plt.ylim(0, 1) 770 plt.xlabel('p-value') 771 plt.ylabel('Cumulative probability') 772 plt.title(str(bins) + ' p-values') 773 774 n = np.arange(1, bins + 1) / np.float64(bins) 775 Xs = np.sort(p_values) 776 plt.step(Xs, n) 777 diffs = n - Xs 778 loc_max_diff = np.argmax(np.abs(diffs)) 779 loc = Xs[loc_max_diff] 780 plt.annotate('', xy=(loc, loc), xytext=(loc, loc + diffs[loc_max_diff]), arrowprops=dict(arrowstyle='<->', shrinkA=0, shrinkB=0)) 781 plt.draw() 782 783 print(scipy.stats.kstest(p_values, 'uniform'))
21class Fit_result(Sequence): 22 """Represents fit results. 23 24 Attributes 25 ---------- 26 fit_parameters : list 27 results for the individual fit parameters, 28 also accessible via indices. 29 """ 30 31 def __init__(self): 32 self.fit_parameters = None 33 34 def __getitem__(self, idx): 35 return self.fit_parameters[idx] 36 37 def __len__(self): 38 return len(self.fit_parameters) 39 40 def gamma_method(self, **kwargs): 41 """Apply the gamma method to all fit parameters""" 42 [o.gamma_method(**kwargs) for o in self.fit_parameters] 43 44 def __str__(self): 45 my_str = 'Goodness of fit:\n' 46 if hasattr(self, 'chisquare_by_dof'): 47 my_str += '\u03C7\u00b2/d.o.f. = ' + f'{self.chisquare_by_dof:2.6f}' + '\n' 48 elif hasattr(self, 'residual_variance'): 49 my_str += 'residual variance = ' + f'{self.residual_variance:2.6f}' + '\n' 50 if hasattr(self, 'chisquare_by_expected_chisquare'): 51 my_str += '\u03C7\u00b2/\u03C7\u00b2exp = ' + f'{self.chisquare_by_expected_chisquare:2.6f}' + '\n' 52 if hasattr(self, 'p_value'): 53 my_str += 'p-value = ' + f'{self.p_value:2.4f}' + '\n' 54 my_str += 'Fit parameters:\n' 55 for i_par, par in enumerate(self.fit_parameters): 56 my_str += str(i_par) + '\t' + ' ' * int(par >= 0) + str(par).rjust(int(par < 0.0)) + '\n' 57 return my_str 58 59 def __repr__(self): 60 m = max(map(len, list(self.__dict__.keys()))) + 1 61 return '\n'.join([key.rjust(m) + ': ' + repr(value) for key, value in sorted(self.__dict__.items())])
Represents fit results.
Attributes
- fit_parameters (list): results for the individual fit parameters, also accessible via indices.
40 def gamma_method(self, **kwargs): 41 """Apply the gamma method to all fit parameters""" 42 [o.gamma_method(**kwargs) for o in self.fit_parameters]
Apply the gamma method to all fit parameters
Inherited Members
- collections.abc.Sequence
- index
- count
64def least_squares(x, y, func, priors=None, silent=False, **kwargs): 65 r'''Performs a non-linear fit to y = func(x). 66 67 Parameters 68 ---------- 69 x : list 70 list of floats. 71 y : list 72 list of Obs. 73 func : object 74 fit function, has to be of the form 75 76 ```python 77 import autograd.numpy as anp 78 79 def func(a, x): 80 return a[0] + a[1] * x + a[2] * anp.sinh(x) 81 ``` 82 83 For multiple x values func can be of the form 84 85 ```python 86 def func(a, x): 87 (x1, x2) = x 88 return a[0] * x1 ** 2 + a[1] * x2 89 ``` 90 91 It is important that all numpy functions refer to autograd.numpy, otherwise the differentiation 92 will not work. 93 priors : list, optional 94 priors has to be a list with an entry for every parameter in the fit. The entries can either be 95 Obs (e.g. results from a previous fit) or strings containing a value and an error formatted like 96 0.548(23), 500(40) or 0.5(0.4) 97 silent : bool, optional 98 If true all output to the console is omitted (default False). 99 initial_guess : list 100 can provide an initial guess for the input parameters. Relevant for 101 non-linear fits with many parameters. In case of correlated fits the guess is used to perform 102 an uncorrelated fit which then serves as guess for the correlated fit. 103 method : str, optional 104 can be used to choose an alternative method for the minimization of chisquare. 105 The possible methods are the ones which can be used for scipy.optimize.minimize and 106 migrad of iminuit. If no method is specified, Levenberg-Marquard is used. 107 Reliable alternatives are migrad, Powell and Nelder-Mead. 108 correlated_fit : bool 109 If True, use the full inverse covariance matrix in the definition of the chisquare cost function. 110 For details about how the covariance matrix is estimated see `pyerrors.obs.covariance`. 111 In practice the correlation matrix is Cholesky decomposed and inverted (instead of the covariance matrix). 112 This procedure should be numerically more stable as the correlation matrix is typically better conditioned (Jacobi preconditioning). 113 At the moment this option only works for `prior==None` and when no `method` is given. 114 expected_chisquare : bool 115 If True estimates the expected chisquare which is 116 corrected by effects caused by correlated input data (default False). 117 resplot : bool 118 If True, a plot which displays fit, data and residuals is generated (default False). 119 qqplot : bool 120 If True, a quantile-quantile plot of the fit result is generated (default False). 121 num_grad : bool 122 Use numerical differentation instead of automatic differentiation to perform the error propagation (default False). 123 ''' 124 if priors is not None: 125 return _prior_fit(x, y, func, priors, silent=silent, **kwargs) 126 else: 127 return _standard_fit(x, y, func, silent=silent, **kwargs)
Performs a non-linear fit to y = func(x).
Parameters
- x (list): list of floats.
- y (list): list of Obs.
func (object): fit function, has to be of the form
import autograd.numpy as anp def func(a, x): return a[0] + a[1] * x + a[2] * anp.sinh(x)
For multiple x values func can be of the form
def func(a, x): (x1, x2) = x return a[0] * x1 ** 2 + a[1] * x2
It is important that all numpy functions refer to autograd.numpy, otherwise the differentiation will not work.
- priors (list, optional): priors has to be a list with an entry for every parameter in the fit. The entries can either be Obs (e.g. results from a previous fit) or strings containing a value and an error formatted like 0.548(23), 500(40) or 0.5(0.4)
- silent (bool, optional): If true all output to the console is omitted (default False).
- initial_guess (list): can provide an initial guess for the input parameters. Relevant for non-linear fits with many parameters. In case of correlated fits the guess is used to perform an uncorrelated fit which then serves as guess for the correlated fit.
- method (str, optional): can be used to choose an alternative method for the minimization of chisquare. The possible methods are the ones which can be used for scipy.optimize.minimize and migrad of iminuit. If no method is specified, Levenberg-Marquard is used. Reliable alternatives are migrad, Powell and Nelder-Mead.
- correlated_fit (bool):
If True, use the full inverse covariance matrix in the definition of the chisquare cost function.
For details about how the covariance matrix is estimated see
pyerrors.obs.covariance
. In practice the correlation matrix is Cholesky decomposed and inverted (instead of the covariance matrix). This procedure should be numerically more stable as the correlation matrix is typically better conditioned (Jacobi preconditioning). At the moment this option only works forprior==None
and when nomethod
is given. - expected_chisquare (bool): If True estimates the expected chisquare which is corrected by effects caused by correlated input data (default False).
- resplot (bool): If True, a plot which displays fit, data and residuals is generated (default False).
- qqplot (bool): If True, a quantile-quantile plot of the fit result is generated (default False).
- num_grad (bool): Use numerical differentation instead of automatic differentiation to perform the error propagation (default False).
130def total_least_squares(x, y, func, silent=False, **kwargs): 131 r'''Performs a non-linear fit to y = func(x) and returns a list of Obs corresponding to the fit parameters. 132 133 Parameters 134 ---------- 135 x : list 136 list of Obs, or a tuple of lists of Obs 137 y : list 138 list of Obs. The dvalues of the Obs are used as x- and yerror for the fit. 139 func : object 140 func has to be of the form 141 142 ```python 143 import autograd.numpy as anp 144 145 def func(a, x): 146 return a[0] + a[1] * x + a[2] * anp.sinh(x) 147 ``` 148 149 For multiple x values func can be of the form 150 151 ```python 152 def func(a, x): 153 (x1, x2) = x 154 return a[0] * x1 ** 2 + a[1] * x2 155 ``` 156 157 It is important that all numpy functions refer to autograd.numpy, otherwise the differentiation 158 will not work. 159 silent : bool, optional 160 If true all output to the console is omitted (default False). 161 initial_guess : list 162 can provide an initial guess for the input parameters. Relevant for non-linear 163 fits with many parameters. 164 expected_chisquare : bool 165 If true prints the expected chisquare which is 166 corrected by effects caused by correlated input data. 167 This can take a while as the full correlation matrix 168 has to be calculated (default False). 169 num_grad : bool 170 Use numerical differentation instead of automatic differentiation to perform the error propagation (default False). 171 172 Notes 173 ----- 174 Based on the orthogonal distance regression module of scipy 175 ''' 176 177 output = Fit_result() 178 179 output.fit_function = func 180 181 x = np.array(x) 182 183 x_shape = x.shape 184 185 if kwargs.get('num_grad') is True: 186 jacobian = num_jacobian 187 hessian = num_hessian 188 else: 189 jacobian = auto_jacobian 190 hessian = auto_hessian 191 192 if not callable(func): 193 raise TypeError('func has to be a function.') 194 195 for i in range(42): 196 try: 197 func(np.arange(i), x.T[0]) 198 except TypeError: 199 continue 200 except IndexError: 201 continue 202 else: 203 break 204 else: 205 raise RuntimeError("Fit function is not valid.") 206 207 n_parms = i 208 if not silent: 209 print('Fit with', n_parms, 'parameter' + 's' * (n_parms > 1)) 210 211 x_f = np.vectorize(lambda o: o.value)(x) 212 dx_f = np.vectorize(lambda o: o.dvalue)(x) 213 y_f = np.array([o.value for o in y]) 214 dy_f = np.array([o.dvalue for o in y]) 215 216 if np.any(np.asarray(dx_f) <= 0.0): 217 raise Exception('No x errors available, run the gamma method first.') 218 219 if np.any(np.asarray(dy_f) <= 0.0): 220 raise Exception('No y errors available, run the gamma method first.') 221 222 if 'initial_guess' in kwargs: 223 x0 = kwargs.get('initial_guess') 224 if len(x0) != n_parms: 225 raise Exception('Initial guess does not have the correct length: %d vs. %d' % (len(x0), n_parms)) 226 else: 227 x0 = [1] * n_parms 228 229 data = RealData(x_f, y_f, sx=dx_f, sy=dy_f) 230 model = Model(func) 231 odr = ODR(data, model, x0, partol=np.finfo(np.float64).eps) 232 odr.set_job(fit_type=0, deriv=1) 233 out = odr.run() 234 235 output.residual_variance = out.res_var 236 237 output.method = 'ODR' 238 239 output.message = out.stopreason 240 241 output.xplus = out.xplus 242 243 if not silent: 244 print('Method: ODR') 245 print(*out.stopreason) 246 print('Residual variance:', output.residual_variance) 247 248 if out.info > 3: 249 raise Exception('The minimization procedure did not converge.') 250 251 m = x_f.size 252 253 def odr_chisquare(p): 254 model = func(p[:n_parms], p[n_parms:].reshape(x_shape)) 255 chisq = anp.sum(((y_f - model) / dy_f) ** 2) + anp.sum(((x_f - p[n_parms:].reshape(x_shape)) / dx_f) ** 2) 256 return chisq 257 258 if kwargs.get('expected_chisquare') is True: 259 W = np.diag(1 / np.asarray(np.concatenate((dy_f.ravel(), dx_f.ravel())))) 260 261 if kwargs.get('covariance') is not None: 262 cov = kwargs.get('covariance') 263 else: 264 cov = covariance(np.concatenate((y, x.ravel()))) 265 266 number_of_x_parameters = int(m / x_f.shape[-1]) 267 268 old_jac = jacobian(func)(out.beta, out.xplus) 269 fused_row1 = np.concatenate((old_jac, np.concatenate((number_of_x_parameters * [np.zeros(old_jac.shape)]), axis=0))) 270 fused_row2 = np.concatenate((jacobian(lambda x, y: func(y, x))(out.xplus, out.beta).reshape(x_f.shape[-1], x_f.shape[-1] * number_of_x_parameters), np.identity(number_of_x_parameters * old_jac.shape[0]))) 271 new_jac = np.concatenate((fused_row1, fused_row2), axis=1) 272 273 A = W @ new_jac 274 P_phi = A @ np.linalg.pinv(A.T @ A) @ A.T 275 expected_chisquare = np.trace((np.identity(P_phi.shape[0]) - P_phi) @ W @ cov @ W) 276 if expected_chisquare <= 0.0: 277 warnings.warn("Negative expected_chisquare.", RuntimeWarning) 278 expected_chisquare = np.abs(expected_chisquare) 279 output.chisquare_by_expected_chisquare = odr_chisquare(np.concatenate((out.beta, out.xplus.ravel()))) / expected_chisquare 280 if not silent: 281 print('chisquare/expected_chisquare:', 282 output.chisquare_by_expected_chisquare) 283 284 fitp = out.beta 285 try: 286 hess = hessian(odr_chisquare)(np.concatenate((fitp, out.xplus.ravel()))) 287 except TypeError: 288 raise Exception("It is required to use autograd.numpy instead of numpy within fit functions, see the documentation for details.") from None 289 290 def odr_chisquare_compact_x(d): 291 model = func(d[:n_parms], d[n_parms:n_parms + m].reshape(x_shape)) 292 chisq = anp.sum(((y_f - model) / dy_f) ** 2) + anp.sum(((d[n_parms + m:].reshape(x_shape) - d[n_parms:n_parms + m].reshape(x_shape)) / dx_f) ** 2) 293 return chisq 294 295 jac_jac_x = hessian(odr_chisquare_compact_x)(np.concatenate((fitp, out.xplus.ravel(), x_f.ravel()))) 296 297 # Compute hess^{-1} @ jac_jac_x[:n_parms + m, n_parms + m:] using LAPACK dgesv 298 try: 299 deriv_x = -scipy.linalg.solve(hess, jac_jac_x[:n_parms + m, n_parms + m:]) 300 except np.linalg.LinAlgError: 301 raise Exception("Cannot invert hessian matrix.") 302 303 def odr_chisquare_compact_y(d): 304 model = func(d[:n_parms], d[n_parms:n_parms + m].reshape(x_shape)) 305 chisq = anp.sum(((d[n_parms + m:] - model) / dy_f) ** 2) + anp.sum(((x_f - d[n_parms:n_parms + m].reshape(x_shape)) / dx_f) ** 2) 306 return chisq 307 308 jac_jac_y = hessian(odr_chisquare_compact_y)(np.concatenate((fitp, out.xplus.ravel(), y_f))) 309 310 # Compute hess^{-1} @ jac_jac_y[:n_parms + m, n_parms + m:] using LAPACK dgesv 311 try: 312 deriv_y = -scipy.linalg.solve(hess, jac_jac_y[:n_parms + m, n_parms + m:]) 313 except np.linalg.LinAlgError: 314 raise Exception("Cannot invert hessian matrix.") 315 316 result = [] 317 for i in range(n_parms): 318 result.append(derived_observable(lambda my_var, **kwargs: (my_var[0] + np.finfo(np.float64).eps) / (x.ravel()[0].value + np.finfo(np.float64).eps) * out.beta[i], list(x.ravel()) + list(y), man_grad=list(deriv_x[i]) + list(deriv_y[i]))) 319 320 output.fit_parameters = result 321 322 output.odr_chisquare = odr_chisquare(np.concatenate((out.beta, out.xplus.ravel()))) 323 output.dof = x.shape[-1] - n_parms 324 output.p_value = 1 - scipy.stats.chi2.cdf(output.odr_chisquare, output.dof) 325 326 return output
Performs a non-linear fit to y = func(x) and returns a list of Obs corresponding to the fit parameters.
Parameters
- x (list): list of Obs, or a tuple of lists of Obs
- y (list): list of Obs. The dvalues of the Obs are used as x- and yerror for the fit.
func (object): func has to be of the form
import autograd.numpy as anp def func(a, x): return a[0] + a[1] * x + a[2] * anp.sinh(x)
For multiple x values func can be of the form
def func(a, x): (x1, x2) = x return a[0] * x1 ** 2 + a[1] * x2
It is important that all numpy functions refer to autograd.numpy, otherwise the differentiation will not work.
- silent (bool, optional): If true all output to the console is omitted (default False).
- initial_guess (list): can provide an initial guess for the input parameters. Relevant for non-linear fits with many parameters.
- expected_chisquare (bool): If true prints the expected chisquare which is corrected by effects caused by correlated input data. This can take a while as the full correlation matrix has to be calculated (default False).
- num_grad (bool): Use numerical differentation instead of automatic differentiation to perform the error propagation (default False).
Notes
Based on the orthogonal distance regression module of scipy
647def fit_lin(x, y, **kwargs): 648 """Performs a linear fit to y = n + m * x and returns two Obs n, m. 649 650 Parameters 651 ---------- 652 x : list 653 Can either be a list of floats in which case no xerror is assumed, or 654 a list of Obs, where the dvalues of the Obs are used as xerror for the fit. 655 y : list 656 List of Obs, the dvalues of the Obs are used as yerror for the fit. 657 """ 658 659 def f(a, x): 660 y = a[0] + a[1] * x 661 return y 662 663 if all(isinstance(n, Obs) for n in x): 664 out = total_least_squares(x, y, f, **kwargs) 665 return out.fit_parameters 666 elif all(isinstance(n, float) or isinstance(n, int) for n in x) or isinstance(x, np.ndarray): 667 out = least_squares(x, y, f, **kwargs) 668 return out.fit_parameters 669 else: 670 raise Exception('Unsupported types for x')
Performs a linear fit to y = n + m * x and returns two Obs n, m.
Parameters
- x (list): Can either be a list of floats in which case no xerror is assumed, or a list of Obs, where the dvalues of the Obs are used as xerror for the fit.
- y (list): List of Obs, the dvalues of the Obs are used as yerror for the fit.
673def qqplot(x, o_y, func, p): 674 """Generates a quantile-quantile plot of the fit result which can be used to 675 check if the residuals of the fit are gaussian distributed. 676 """ 677 678 residuals = [] 679 for i_x, i_y in zip(x, o_y): 680 residuals.append((i_y - func(p, i_x)) / i_y.dvalue) 681 residuals = sorted(residuals) 682 my_y = [o.value for o in residuals] 683 probplot = scipy.stats.probplot(my_y) 684 my_x = probplot[0][0] 685 plt.figure(figsize=(8, 8 / 1.618)) 686 plt.errorbar(my_x, my_y, fmt='o') 687 fit_start = my_x[0] 688 fit_stop = my_x[-1] 689 samples = np.arange(fit_start, fit_stop, 0.01) 690 plt.plot(samples, samples, 'k--', zorder=11, label='Standard normal distribution') 691 plt.plot(samples, probplot[1][0] * samples + probplot[1][1], zorder=10, label='Least squares fit, r=' + str(np.around(probplot[1][2], 3)), marker='', ls='-') 692 693 plt.xlabel('Theoretical quantiles') 694 plt.ylabel('Ordered Values') 695 plt.legend() 696 plt.draw()
Generates a quantile-quantile plot of the fit result which can be used to check if the residuals of the fit are gaussian distributed.
699def residual_plot(x, y, func, fit_res): 700 """ Generates a plot which compares the fit to the data and displays the corresponding residuals""" 701 sorted_x = sorted(x) 702 xstart = sorted_x[0] - 0.5 * (sorted_x[1] - sorted_x[0]) 703 xstop = sorted_x[-1] + 0.5 * (sorted_x[-1] - sorted_x[-2]) 704 x_samples = np.arange(xstart, xstop + 0.01, 0.01) 705 706 plt.figure(figsize=(8, 8 / 1.618)) 707 gs = gridspec.GridSpec(2, 1, height_ratios=[3, 1], wspace=0.0, hspace=0.0) 708 ax0 = plt.subplot(gs[0]) 709 ax0.errorbar(x, [o.value for o in y], yerr=[o.dvalue for o in y], ls='none', fmt='o', capsize=3, markersize=5, label='Data') 710 ax0.plot(x_samples, func([o.value for o in fit_res], x_samples), label='Fit', zorder=10, ls='-', ms=0) 711 ax0.set_xticklabels([]) 712 ax0.set_xlim([xstart, xstop]) 713 ax0.set_xticklabels([]) 714 ax0.legend() 715 716 residuals = (np.asarray([o.value for o in y]) - func([o.value for o in fit_res], x)) / np.asarray([o.dvalue for o in y]) 717 ax1 = plt.subplot(gs[1]) 718 ax1.plot(x, residuals, 'ko', ls='none', markersize=5) 719 ax1.tick_params(direction='out') 720 ax1.tick_params(axis="x", bottom=True, top=True, labelbottom=True) 721 ax1.axhline(y=0.0, ls='--', color='k', marker=" ") 722 ax1.fill_between(x_samples, -1.0, 1.0, alpha=0.1, facecolor='k') 723 ax1.set_xlim([xstart, xstop]) 724 ax1.set_ylabel('Residuals') 725 plt.subplots_adjust(wspace=None, hspace=None) 726 plt.draw()
Generates a plot which compares the fit to the data and displays the corresponding residuals
729def error_band(x, func, beta): 730 """Returns the error band for an array of sample values x, for given fit function func with optimized parameters beta.""" 731 cov = covariance(beta) 732 if np.any(np.abs(cov - cov.T) > 1000 * np.finfo(np.float64).eps): 733 warnings.warn("Covariance matrix is not symmetric within floating point precision", RuntimeWarning) 734 735 deriv = [] 736 for i, item in enumerate(x): 737 deriv.append(np.array(egrad(func)([o.value for o in beta], item))) 738 739 err = [] 740 for i, item in enumerate(x): 741 err.append(np.sqrt(deriv[i] @ cov @ deriv[i])) 742 err = np.array(err) 743 744 return err
Returns the error band for an array of sample values x, for given fit function func with optimized parameters beta.
747def ks_test(objects=None): 748 """Performs a Kolmogorov–Smirnov test for the p-values of all fit object. 749 750 Parameters 751 ---------- 752 objects : list 753 List of fit results to include in the analysis (optional). 754 """ 755 756 if objects is None: 757 obs_list = [] 758 for obj in gc.get_objects(): 759 if isinstance(obj, Fit_result): 760 obs_list.append(obj) 761 else: 762 obs_list = objects 763 764 p_values = [o.p_value for o in obs_list] 765 766 bins = len(p_values) 767 x = np.arange(0, 1.001, 0.001) 768 plt.plot(x, x, 'k', zorder=1) 769 plt.xlim(0, 1) 770 plt.ylim(0, 1) 771 plt.xlabel('p-value') 772 plt.ylabel('Cumulative probability') 773 plt.title(str(bins) + ' p-values') 774 775 n = np.arange(1, bins + 1) / np.float64(bins) 776 Xs = np.sort(p_values) 777 plt.step(Xs, n) 778 diffs = n - Xs 779 loc_max_diff = np.argmax(np.abs(diffs)) 780 loc = Xs[loc_max_diff] 781 plt.annotate('', xy=(loc, loc), xytext=(loc, loc + diffs[loc_max_diff]), arrowprops=dict(arrowstyle='<->', shrinkA=0, shrinkB=0)) 782 plt.draw() 783 784 print(scipy.stats.kstest(p_values, 'uniform'))
Performs a Kolmogorov–Smirnov test for the p-values of all fit object.
Parameters
- objects (list): List of fit results to include in the analysis (optional).