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 11from scipy.stats import chi2 12import iminuit 13from autograd import jacobian 14from autograd import elementwise_grad as egrad 15from .obs import Obs, derived_observable, covariance, cov_Obs 16 17 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): 38 """Apply the gamma method to all fit parameters""" 39 [o.gamma_method() 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())]) 59 60 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. 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_inv = np.linalg.pinv(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 deriv_x = -hess_inv @ jac_jac_x[:n_parms + m, n_parms + m:] 279 280 def odr_chisquare_compact_y(d): 281 model = func(d[:n_parms], d[n_parms:n_parms + m].reshape(x_shape)) 282 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) 283 return chisq 284 285 jac_jac_y = jacobian(jacobian(odr_chisquare_compact_y))(np.concatenate((fitp, out.xplus.ravel(), y_f))) 286 287 deriv_y = -hess_inv @ jac_jac_y[:n_parms + m, n_parms + m:] 288 289 result = [] 290 for i in range(n_parms): 291 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]))) 292 293 output.fit_parameters = result 294 295 output.odr_chisquare = odr_chisquare(np.concatenate((out.beta, out.xplus.ravel()))) 296 output.dof = x.shape[-1] - n_parms 297 output.p_value = 1 - chi2.cdf(output.odr_chisquare, output.dof) 298 299 return output 300 301 302def _prior_fit(x, y, func, priors, silent=False, **kwargs): 303 output = Fit_result() 304 305 output.fit_function = func 306 307 x = np.asarray(x) 308 309 if not callable(func): 310 raise TypeError('func has to be a function.') 311 312 for i in range(100): 313 try: 314 func(np.arange(i), 0) 315 except Exception: 316 pass 317 else: 318 break 319 320 n_parms = i 321 322 if n_parms != len(priors): 323 raise Exception('Priors does not have the correct length.') 324 325 def extract_val_and_dval(string): 326 split_string = string.split('(') 327 if '.' in split_string[0] and '.' not in split_string[1][:-1]: 328 factor = 10 ** -len(split_string[0].partition('.')[2]) 329 else: 330 factor = 1 331 return float(split_string[0]), float(split_string[1][:-1]) * factor 332 333 loc_priors = [] 334 for i_n, i_prior in enumerate(priors): 335 if isinstance(i_prior, Obs): 336 loc_priors.append(i_prior) 337 else: 338 loc_val, loc_dval = extract_val_and_dval(i_prior) 339 loc_priors.append(cov_Obs(loc_val, loc_dval ** 2, '#prior' + str(i_n) + f"_{np.random.randint(2147483647):010d}")) 340 341 output.priors = loc_priors 342 343 if not silent: 344 print('Fit with', n_parms, 'parameter' + 's' * (n_parms > 1)) 345 346 y_f = [o.value for o in y] 347 dy_f = [o.dvalue for o in y] 348 349 if np.any(np.asarray(dy_f) <= 0.0): 350 raise Exception('No y errors available, run the gamma method first.') 351 352 p_f = [o.value for o in loc_priors] 353 dp_f = [o.dvalue for o in loc_priors] 354 355 if np.any(np.asarray(dp_f) <= 0.0): 356 raise Exception('No prior errors available, run the gamma method first.') 357 358 if 'initial_guess' in kwargs: 359 x0 = kwargs.get('initial_guess') 360 if len(x0) != n_parms: 361 raise Exception('Initial guess does not have the correct length.') 362 else: 363 x0 = p_f 364 365 def chisqfunc(p): 366 model = func(p, x) 367 chisq = anp.sum(((y_f - model) / dy_f) ** 2) + anp.sum(((p_f - p) / dp_f) ** 2) 368 return chisq 369 370 if not silent: 371 print('Method: migrad') 372 373 m = iminuit.Minuit(chisqfunc, x0) 374 m.errordef = 1 375 m.print_level = 0 376 if 'tol' in kwargs: 377 m.tol = kwargs.get('tol') 378 else: 379 m.tol = 1e-4 380 m.migrad() 381 params = np.asarray(m.values) 382 383 output.chisquare_by_dof = m.fval / len(x) 384 385 output.method = 'migrad' 386 387 if not silent: 388 print('chisquare/d.o.f.:', output.chisquare_by_dof) 389 390 if not m.fmin.is_valid: 391 raise Exception('The minimization procedure did not converge.') 392 393 hess_inv = np.linalg.pinv(jacobian(jacobian(chisqfunc))(params)) 394 395 def chisqfunc_compact(d): 396 model = func(d[:n_parms], x) 397 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) 398 return chisq 399 400 jac_jac = jacobian(jacobian(chisqfunc_compact))(np.concatenate((params, y_f, p_f))) 401 402 deriv = -hess_inv @ jac_jac[:n_parms, n_parms:] 403 404 result = [] 405 for i in range(n_parms): 406 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]))) 407 408 output.fit_parameters = result 409 output.chisquare = chisqfunc(np.asarray(params)) 410 411 if kwargs.get('resplot') is True: 412 residual_plot(x, y, func, result) 413 414 if kwargs.get('qqplot') is True: 415 qqplot(x, y, func, result) 416 417 return output 418 419 420def _standard_fit(x, y, func, silent=False, **kwargs): 421 422 output = Fit_result() 423 424 output.fit_function = func 425 426 x = np.asarray(x) 427 428 if x.shape[-1] != len(y): 429 raise Exception('x and y input have to have the same length') 430 431 if len(x.shape) > 2: 432 raise Exception('Unknown format for x values') 433 434 if not callable(func): 435 raise TypeError('func has to be a function.') 436 437 for i in range(25): 438 try: 439 func(np.arange(i), x.T[0]) 440 except Exception: 441 pass 442 else: 443 break 444 445 n_parms = i 446 447 if not silent: 448 print('Fit with', n_parms, 'parameter' + 's' * (n_parms > 1)) 449 450 y_f = [o.value for o in y] 451 dy_f = [o.dvalue for o in y] 452 453 if np.any(np.asarray(dy_f) <= 0.0): 454 raise Exception('No y errors available, run the gamma method first.') 455 456 if 'initial_guess' in kwargs: 457 x0 = kwargs.get('initial_guess') 458 if len(x0) != n_parms: 459 raise Exception('Initial guess does not have the correct length: %d vs. %d' % (len(x0), n_parms)) 460 else: 461 x0 = [0.1] * n_parms 462 463 if kwargs.get('correlated_fit') is True: 464 corr = covariance(y, correlation=True, **kwargs) 465 covdiag = np.diag(1 / np.asarray(dy_f)) 466 condn = np.linalg.cond(corr) 467 if condn > 0.1 / np.finfo(float).eps: 468 raise Exception(f"Cannot invert correlation matrix as its condition number exceeds machine precision ({condn:1.2e})") 469 if condn > 1 / np.sqrt(np.finfo(float).eps): 470 warnings.warn("Correlation matrix may be ill-conditioned, condition number: {%1.2e}" % (condn), RuntimeWarning) 471 chol = np.linalg.cholesky(corr) 472 chol_inv = np.linalg.inv(chol) 473 chol_inv = np.dot(chol_inv, covdiag) 474 475 def chisqfunc(p): 476 model = func(p, x) 477 chisq = anp.sum(anp.dot(chol_inv, (y_f - model)) ** 2) 478 return chisq 479 else: 480 def chisqfunc(p): 481 model = func(p, x) 482 chisq = anp.sum(((y_f - model) / dy_f) ** 2) 483 return chisq 484 485 output.method = kwargs.get('method', 'Levenberg-Marquardt') 486 if not silent: 487 print('Method:', output.method) 488 489 if output.method != 'Levenberg-Marquardt': 490 if output.method == 'migrad': 491 fit_result = iminuit.minimize(chisqfunc, x0, tol=1e-4) # Stopping criterion 0.002 * tol * errordef 492 output.iterations = fit_result.nfev 493 else: 494 fit_result = scipy.optimize.minimize(chisqfunc, x0, method=kwargs.get('method'), tol=1e-12) 495 output.iterations = fit_result.nit 496 497 chisquare = fit_result.fun 498 499 else: 500 if kwargs.get('correlated_fit') is True: 501 def chisqfunc_residuals(p): 502 model = func(p, x) 503 chisq = anp.dot(chol_inv, (y_f - model)) 504 return chisq 505 506 else: 507 def chisqfunc_residuals(p): 508 model = func(p, x) 509 chisq = ((y_f - model) / dy_f) 510 return chisq 511 512 fit_result = scipy.optimize.least_squares(chisqfunc_residuals, x0, method='lm', ftol=1e-15, gtol=1e-15, xtol=1e-15) 513 514 chisquare = np.sum(fit_result.fun ** 2) 515 516 output.iterations = fit_result.nfev 517 518 if not fit_result.success: 519 raise Exception('The minimization procedure did not converge.') 520 521 if x.shape[-1] - n_parms > 0: 522 output.chisquare_by_dof = chisquare / (x.shape[-1] - n_parms) 523 else: 524 output.chisquare_by_dof = float('nan') 525 526 output.message = fit_result.message 527 if not silent: 528 print(fit_result.message) 529 print('chisquare/d.o.f.:', output.chisquare_by_dof) 530 531 if kwargs.get('expected_chisquare') is True: 532 if kwargs.get('correlated_fit') is not True: 533 W = np.diag(1 / np.asarray(dy_f)) 534 cov = covariance(y) 535 A = W @ jacobian(func)(fit_result.x, x) 536 P_phi = A @ np.linalg.pinv(A.T @ A) @ A.T 537 expected_chisquare = np.trace((np.identity(x.shape[-1]) - P_phi) @ W @ cov @ W) 538 output.chisquare_by_expected_chisquare = chisquare / expected_chisquare 539 if not silent: 540 print('chisquare/expected_chisquare:', 541 output.chisquare_by_expected_chisquare) 542 543 fitp = fit_result.x 544 try: 545 hess_inv = np.linalg.pinv(jacobian(jacobian(chisqfunc))(fitp)) 546 except TypeError: 547 raise Exception("It is required to use autograd.numpy instead of numpy within fit functions, see the documentation for details.") from None 548 549 if kwargs.get('correlated_fit') is True: 550 def chisqfunc_compact(d): 551 model = func(d[:n_parms], x) 552 chisq = anp.sum(anp.dot(chol_inv, (d[n_parms:] - model)) ** 2) 553 return chisq 554 555 else: 556 def chisqfunc_compact(d): 557 model = func(d[:n_parms], x) 558 chisq = anp.sum(((d[n_parms:] - model) / dy_f) ** 2) 559 return chisq 560 561 jac_jac = jacobian(jacobian(chisqfunc_compact))(np.concatenate((fitp, y_f))) 562 563 deriv = -hess_inv @ jac_jac[:n_parms, n_parms:] 564 565 result = [] 566 for i in range(n_parms): 567 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]))) 568 569 output.fit_parameters = result 570 571 output.chisquare = chisqfunc(fit_result.x) 572 output.dof = x.shape[-1] - n_parms 573 output.p_value = 1 - chi2.cdf(output.chisquare, output.dof) 574 575 if kwargs.get('resplot') is True: 576 residual_plot(x, y, func, result) 577 578 if kwargs.get('qqplot') is True: 579 qqplot(x, y, func, result) 580 581 return output 582 583 584def fit_lin(x, y, **kwargs): 585 """Performs a linear fit to y = n + m * x and returns two Obs n, m. 586 587 Parameters 588 ---------- 589 x : list 590 Can either be a list of floats in which case no xerror is assumed, or 591 a list of Obs, where the dvalues of the Obs are used as xerror for the fit. 592 y : list 593 List of Obs, the dvalues of the Obs are used as yerror for the fit. 594 """ 595 596 def f(a, x): 597 y = a[0] + a[1] * x 598 return y 599 600 if all(isinstance(n, Obs) for n in x): 601 out = total_least_squares(x, y, f, **kwargs) 602 return out.fit_parameters 603 elif all(isinstance(n, float) or isinstance(n, int) for n in x) or isinstance(x, np.ndarray): 604 out = least_squares(x, y, f, **kwargs) 605 return out.fit_parameters 606 else: 607 raise Exception('Unsupported types for x') 608 609 610def qqplot(x, o_y, func, p): 611 """Generates a quantile-quantile plot of the fit result which can be used to 612 check if the residuals of the fit are gaussian distributed. 613 """ 614 615 residuals = [] 616 for i_x, i_y in zip(x, o_y): 617 residuals.append((i_y - func(p, i_x)) / i_y.dvalue) 618 residuals = sorted(residuals) 619 my_y = [o.value for o in residuals] 620 probplot = scipy.stats.probplot(my_y) 621 my_x = probplot[0][0] 622 plt.figure(figsize=(8, 8 / 1.618)) 623 plt.errorbar(my_x, my_y, fmt='o') 624 fit_start = my_x[0] 625 fit_stop = my_x[-1] 626 samples = np.arange(fit_start, fit_stop, 0.01) 627 plt.plot(samples, samples, 'k--', zorder=11, label='Standard normal distribution') 628 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='-') 629 630 plt.xlabel('Theoretical quantiles') 631 plt.ylabel('Ordered Values') 632 plt.legend() 633 plt.draw() 634 635 636def residual_plot(x, y, func, fit_res): 637 """ Generates a plot which compares the fit to the data and displays the corresponding residuals""" 638 sorted_x = sorted(x) 639 xstart = sorted_x[0] - 0.5 * (sorted_x[1] - sorted_x[0]) 640 xstop = sorted_x[-1] + 0.5 * (sorted_x[-1] - sorted_x[-2]) 641 x_samples = np.arange(xstart, xstop + 0.01, 0.01) 642 643 plt.figure(figsize=(8, 8 / 1.618)) 644 gs = gridspec.GridSpec(2, 1, height_ratios=[3, 1], wspace=0.0, hspace=0.0) 645 ax0 = plt.subplot(gs[0]) 646 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') 647 ax0.plot(x_samples, func([o.value for o in fit_res], x_samples), label='Fit', zorder=10, ls='-', ms=0) 648 ax0.set_xticklabels([]) 649 ax0.set_xlim([xstart, xstop]) 650 ax0.set_xticklabels([]) 651 ax0.legend() 652 653 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]) 654 ax1 = plt.subplot(gs[1]) 655 ax1.plot(x, residuals, 'ko', ls='none', markersize=5) 656 ax1.tick_params(direction='out') 657 ax1.tick_params(axis="x", bottom=True, top=True, labelbottom=True) 658 ax1.axhline(y=0.0, ls='--', color='k', marker=" ") 659 ax1.fill_between(x_samples, -1.0, 1.0, alpha=0.1, facecolor='k') 660 ax1.set_xlim([xstart, xstop]) 661 ax1.set_ylabel('Residuals') 662 plt.subplots_adjust(wspace=None, hspace=None) 663 plt.draw() 664 665 666def error_band(x, func, beta): 667 """Returns the error band for an array of sample values x, for given fit function func with optimized parameters beta.""" 668 cov = covariance(beta) 669 if np.any(np.abs(cov - cov.T) > 1000 * np.finfo(np.float64).eps): 670 warnings.warn("Covariance matrix is not symmetric within floating point precision", RuntimeWarning) 671 672 deriv = [] 673 for i, item in enumerate(x): 674 deriv.append(np.array(egrad(func)([o.value for o in beta], item))) 675 676 err = [] 677 for i, item in enumerate(x): 678 err.append(np.sqrt(deriv[i] @ cov @ deriv[i])) 679 err = np.array(err) 680 681 return err 682 683 684def ks_test(objects=None): 685 """Performs a Kolmogorov–Smirnov test for the p-values of all fit object. 686 687 Parameters 688 ---------- 689 objects : list 690 List of fit results to include in the analysis (optional). 691 """ 692 693 if objects is None: 694 obs_list = [] 695 for obj in gc.get_objects(): 696 if isinstance(obj, Fit_result): 697 obs_list.append(obj) 698 else: 699 obs_list = objects 700 701 p_values = [o.p_value for o in obs_list] 702 703 bins = len(p_values) 704 x = np.arange(0, 1.001, 0.001) 705 plt.plot(x, x, 'k', zorder=1) 706 plt.xlim(0, 1) 707 plt.ylim(0, 1) 708 plt.xlabel('p-value') 709 plt.ylabel('Cumulative probability') 710 plt.title(str(bins) + ' p-values') 711 712 n = np.arange(1, bins + 1) / np.float64(bins) 713 Xs = np.sort(p_values) 714 plt.step(Xs, n) 715 diffs = n - Xs 716 loc_max_diff = np.argmax(np.abs(diffs)) 717 loc = Xs[loc_max_diff] 718 plt.annotate('', xy=(loc, loc), xytext=(loc, loc + diffs[loc_max_diff]), arrowprops=dict(arrowstyle='<->', shrinkA=0, shrinkB=0)) 719 plt.draw() 720 721 print(scipy.stats.kstest(p_values, 'uniform'))
19class Fit_result(Sequence): 20 """Represents fit results. 21 22 Attributes 23 ---------- 24 fit_parameters : list 25 results for the individual fit parameters, 26 also accessible via indices. 27 """ 28 29 def __init__(self): 30 self.fit_parameters = None 31 32 def __getitem__(self, idx): 33 return self.fit_parameters[idx] 34 35 def __len__(self): 36 return len(self.fit_parameters) 37 38 def gamma_method(self): 39 """Apply the gamma method to all fit parameters""" 40 [o.gamma_method() for o in self.fit_parameters] 41 42 def __str__(self): 43 my_str = 'Goodness of fit:\n' 44 if hasattr(self, 'chisquare_by_dof'): 45 my_str += '\u03C7\u00b2/d.o.f. = ' + f'{self.chisquare_by_dof:2.6f}' + '\n' 46 elif hasattr(self, 'residual_variance'): 47 my_str += 'residual variance = ' + f'{self.residual_variance:2.6f}' + '\n' 48 if hasattr(self, 'chisquare_by_expected_chisquare'): 49 my_str += '\u03C7\u00b2/\u03C7\u00b2exp = ' + f'{self.chisquare_by_expected_chisquare:2.6f}' + '\n' 50 if hasattr(self, 'p_value'): 51 my_str += 'p-value = ' + f'{self.p_value:2.4f}' + '\n' 52 my_str += 'Fit parameters:\n' 53 for i_par, par in enumerate(self.fit_parameters): 54 my_str += str(i_par) + '\t' + ' ' * int(par >= 0) + str(par).rjust(int(par < 0.0)) + '\n' 55 return my_str 56 57 def __repr__(self): 58 m = max(map(len, list(self.__dict__.keys()))) + 1 59 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.
38 def gamma_method(self): 39 """Apply the gamma method to all fit parameters""" 40 [o.gamma_method() for o in self.fit_parameters]
Apply the gamma method to all fit parameters
Inherited Members
- collections.abc.Sequence
- index
- count
62def least_squares(x, y, func, priors=None, silent=False, **kwargs): 63 r'''Performs a non-linear fit to y = func(x). 64 65 Parameters 66 ---------- 67 x : list 68 list of floats. 69 y : list 70 list of Obs. 71 func : object 72 fit function, has to be of the form 73 74 ```python 75 import autograd.numpy as anp 76 77 def func(a, x): 78 return a[0] + a[1] * x + a[2] * anp.sinh(x) 79 ``` 80 81 For multiple x values func can be of the form 82 83 ```python 84 def func(a, x): 85 (x1, x2) = x 86 return a[0] * x1 ** 2 + a[1] * x2 87 ``` 88 89 It is important that all numpy functions refer to autograd.numpy, otherwise the differentiation 90 will not work. 91 priors : list, optional 92 priors has to be a list with an entry for every parameter in the fit. The entries can either be 93 Obs (e.g. results from a previous fit) or strings containing a value and an error formatted like 94 0.548(23), 500(40) or 0.5(0.4) 95 silent : bool, optional 96 If true all output to the console is omitted (default False). 97 initial_guess : list 98 can provide an initial guess for the input parameters. Relevant for 99 non-linear fits with many parameters. 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.
- 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_inv = np.linalg.pinv(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 deriv_x = -hess_inv @ jac_jac_x[:n_parms + m, n_parms + m:] 280 281 def odr_chisquare_compact_y(d): 282 model = func(d[:n_parms], d[n_parms:n_parms + m].reshape(x_shape)) 283 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) 284 return chisq 285 286 jac_jac_y = jacobian(jacobian(odr_chisquare_compact_y))(np.concatenate((fitp, out.xplus.ravel(), y_f))) 287 288 deriv_y = -hess_inv @ jac_jac_y[:n_parms + m, n_parms + m:] 289 290 result = [] 291 for i in range(n_parms): 292 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]))) 293 294 output.fit_parameters = result 295 296 output.odr_chisquare = odr_chisquare(np.concatenate((out.beta, out.xplus.ravel()))) 297 output.dof = x.shape[-1] - n_parms 298 output.p_value = 1 - chi2.cdf(output.odr_chisquare, output.dof) 299 300 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
585def fit_lin(x, y, **kwargs): 586 """Performs a linear fit to y = n + m * x and returns two Obs n, m. 587 588 Parameters 589 ---------- 590 x : list 591 Can either be a list of floats in which case no xerror is assumed, or 592 a list of Obs, where the dvalues of the Obs are used as xerror for the fit. 593 y : list 594 List of Obs, the dvalues of the Obs are used as yerror for the fit. 595 """ 596 597 def f(a, x): 598 y = a[0] + a[1] * x 599 return y 600 601 if all(isinstance(n, Obs) for n in x): 602 out = total_least_squares(x, y, f, **kwargs) 603 return out.fit_parameters 604 elif all(isinstance(n, float) or isinstance(n, int) for n in x) or isinstance(x, np.ndarray): 605 out = least_squares(x, y, f, **kwargs) 606 return out.fit_parameters 607 else: 608 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.
611def qqplot(x, o_y, func, p): 612 """Generates a quantile-quantile plot of the fit result which can be used to 613 check if the residuals of the fit are gaussian distributed. 614 """ 615 616 residuals = [] 617 for i_x, i_y in zip(x, o_y): 618 residuals.append((i_y - func(p, i_x)) / i_y.dvalue) 619 residuals = sorted(residuals) 620 my_y = [o.value for o in residuals] 621 probplot = scipy.stats.probplot(my_y) 622 my_x = probplot[0][0] 623 plt.figure(figsize=(8, 8 / 1.618)) 624 plt.errorbar(my_x, my_y, fmt='o') 625 fit_start = my_x[0] 626 fit_stop = my_x[-1] 627 samples = np.arange(fit_start, fit_stop, 0.01) 628 plt.plot(samples, samples, 'k--', zorder=11, label='Standard normal distribution') 629 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='-') 630 631 plt.xlabel('Theoretical quantiles') 632 plt.ylabel('Ordered Values') 633 plt.legend() 634 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.
637def residual_plot(x, y, func, fit_res): 638 """ Generates a plot which compares the fit to the data and displays the corresponding residuals""" 639 sorted_x = sorted(x) 640 xstart = sorted_x[0] - 0.5 * (sorted_x[1] - sorted_x[0]) 641 xstop = sorted_x[-1] + 0.5 * (sorted_x[-1] - sorted_x[-2]) 642 x_samples = np.arange(xstart, xstop + 0.01, 0.01) 643 644 plt.figure(figsize=(8, 8 / 1.618)) 645 gs = gridspec.GridSpec(2, 1, height_ratios=[3, 1], wspace=0.0, hspace=0.0) 646 ax0 = plt.subplot(gs[0]) 647 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') 648 ax0.plot(x_samples, func([o.value for o in fit_res], x_samples), label='Fit', zorder=10, ls='-', ms=0) 649 ax0.set_xticklabels([]) 650 ax0.set_xlim([xstart, xstop]) 651 ax0.set_xticklabels([]) 652 ax0.legend() 653 654 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]) 655 ax1 = plt.subplot(gs[1]) 656 ax1.plot(x, residuals, 'ko', ls='none', markersize=5) 657 ax1.tick_params(direction='out') 658 ax1.tick_params(axis="x", bottom=True, top=True, labelbottom=True) 659 ax1.axhline(y=0.0, ls='--', color='k', marker=" ") 660 ax1.fill_between(x_samples, -1.0, 1.0, alpha=0.1, facecolor='k') 661 ax1.set_xlim([xstart, xstop]) 662 ax1.set_ylabel('Residuals') 663 plt.subplots_adjust(wspace=None, hspace=None) 664 plt.draw()
Generates a plot which compares the fit to the data and displays the corresponding residuals
667def error_band(x, func, beta): 668 """Returns the error band for an array of sample values x, for given fit function func with optimized parameters beta.""" 669 cov = covariance(beta) 670 if np.any(np.abs(cov - cov.T) > 1000 * np.finfo(np.float64).eps): 671 warnings.warn("Covariance matrix is not symmetric within floating point precision", RuntimeWarning) 672 673 deriv = [] 674 for i, item in enumerate(x): 675 deriv.append(np.array(egrad(func)([o.value for o in beta], item))) 676 677 err = [] 678 for i, item in enumerate(x): 679 err.append(np.sqrt(deriv[i] @ cov @ deriv[i])) 680 err = np.array(err) 681 682 return err
Returns the error band for an array of sample values x, for given fit function func with optimized parameters beta.
685def ks_test(objects=None): 686 """Performs a Kolmogorov–Smirnov test for the p-values of all fit object. 687 688 Parameters 689 ---------- 690 objects : list 691 List of fit results to include in the analysis (optional). 692 """ 693 694 if objects is None: 695 obs_list = [] 696 for obj in gc.get_objects(): 697 if isinstance(obj, Fit_result): 698 obs_list.append(obj) 699 else: 700 obs_list = objects 701 702 p_values = [o.p_value for o in obs_list] 703 704 bins = len(p_values) 705 x = np.arange(0, 1.001, 0.001) 706 plt.plot(x, x, 'k', zorder=1) 707 plt.xlim(0, 1) 708 plt.ylim(0, 1) 709 plt.xlabel('p-value') 710 plt.ylabel('Cumulative probability') 711 plt.title(str(bins) + ' p-values') 712 713 n = np.arange(1, bins + 1) / np.float64(bins) 714 Xs = np.sort(p_values) 715 plt.step(Xs, n) 716 diffs = n - Xs 717 loc_max_diff = np.argmax(np.abs(diffs)) 718 loc = Xs[loc_max_diff] 719 plt.annotate('', xy=(loc, loc), xytext=(loc, loc + diffs[loc_max_diff]), arrowprops=dict(arrowstyle='<->', shrinkA=0, shrinkB=0)) 720 plt.draw() 721 722 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).