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