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'))
class Fit_result(collections.abc.Sequence):
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.
Fit_result()
29    def __init__(self):
30        self.fit_parameters = None
def gamma_method(self)
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
def least_squares(x, y, func, priors=None, silent=False, **kwargs)
 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 for prior==None and when no method 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).
def total_least_squares(x, y, func, silent=False, **kwargs)
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

def fit_lin(x, y, **kwargs)
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.
def qqplot(x, o_y, func, p)
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.

def residual_plot(x, y, func, fit_res)
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

def error_band(x, func, beta)
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.

def ks_test(objects=None)
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).