from turtle import color
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
import uncertainties
import uncertainties.unumpy as unp
import sympy
import matplotlib.pylab as pylab
import itertools
# local imports
from smpl import interpolate
from smpl import io
from smpl import util
from smpl import wrap
from smpl import doc
from smpl import fit as ffit
def set_plot_style():
# fig_size = (8, 6)
# fig_legendsize = 14
# fig_labelsize = 12 # ‘xx-small’, ‘x-small’, ‘small’, ‘medium’, ‘large’, ‘x-large’, ‘xx-large’.
params = {'legend.fontsize': 'x-large',
'figure.figsize': (8, 6),
'axes.labelsize': 'x-large',
'axes.titlesize': 'x-large',
'xtick.labelsize': 'x-large',
'ytick.labelsize': 'x-large'}
pylab.rcParams.update(params)
matplotlib.rcParams.update(params)
# matplotlib.rcParams.update({'font.size': fig_labelsize})
# colors = dict(mcolors.BASE_COLORS, **mcolors.CSS4_COLORS)
unv = unp.nominal_values
usd = unp.std_devs
default = {
# 'params' :[None ,"Initial fit parameters",
'xlabel': ["", "X axis label", ],
'ylabel': ["", "Y axis label", ],
'label': [None, "Legend name of plotted ``data``", ],
'fmt': ['.', "Format for plotting fit function", ],
'units': [None, "Units of the fit parameters as strings. Displayed in the Legend", ],
'save': [None, " File to save the plot", ],
'lpos': [0, "Legend position", ],
'tight': [True, "tight_layout", ],
# 'frange' :[None ,"Limit the fit to given range. First integer is the lowest and second the highest index.",],
'prange': [None, "Limit the plot of the fit to given range", ],
'sigmas': [0, "Color the array of given ``sigma`` times uncertainty. Only works if the fit function is coded with ``unp``", ],
'data_sigmas': [1, "Color the array of given ``sigma`` times uncertainty. Only works if the data has uncertainties", ],
'init': [False, "Initialize a new plot"],
'ss': [True, "save, add legends and grid to the plot", ],
'also_data': [True, " also plot the data"],
'also_fit': [True, "also plot the fit", ],
'logy': [False, "logarithmic x axis", ],
'logx': [False, "logarithmic y axis", ],
'function_color': [None, "Color of the function plot", ],
'data_color': [None, "Color of the data plot", ],
'fit_color': [None, "Color of the fit plot", ],
'fit_fmt': ["-", "Format of the fit plot", ],
'residue': [False, "Display difference between fit and data in a second plot", ],
'residue_err': [True, "Differences between fit and data will have errorbars", ],
'show': [False, "Call plt.show()", ],
'size': [None, "Size of the plot as a tuple (x,y). Only has an effect if ``init`` is True", ],
'number_format': [io.gf(4), "Format to display numbers.", ],
# , 'selector' :[ None ,"Function that takes ``x`` and ``y`` as parameters and returns an array mask in order to limit the data points for fitting. Alternatively a mask for selecting elements from datax and datay.",],
# , 'fixed_params' :[ True ,"Enable fixing parameters by choosing the same-named variables from ``kwargs``.",],
# , 'sortbyx' :[ True , "Enable sorting the x and y data so that x is sorted.",],
'interpolate': [False, "Enable interpolation of the data."],
'interpolate_fmt': ["-", "Either format string or linestyle tuple.", ],
'extrapolate': [True, "Enable extrapolation of whole data if fit range is limited by ``frange`` or ``fselector``.", ],
'extrapolate_min': [None, "Lower extrapolation bound", ],
'extrapolate_max': [None, "Higher extrapolation bound", ],
'extrapolate_fmt': ["--", "Format of the extrapolation line", ],
'extrapolate_hatch': [r"||", "Extrapolation shape/hatch for filled area in case of ``sigmas``>0. See https://matplotlib.org/stable/gallery/shapes_and_collections/hatch_style_reference.html", ],
'bbox_to_anchor': [None, "Position in a tuple (x,y),Shift position of the legend out of the main pane. ", ],
'ncol': [None, "Columns in the legend if used with ``bbox_to_anchor``.", ],
'steps': [1000, "resolution of the plotted function", ],
'fitinline': [False, "No newlines for each fit parameter", ],
'grid': [True, "Enable grid for the plot", ],
'hist': [False, "Enable histogram plot", ],
'stairs': [False, "Enable stair plot", ],
'capsize': [5, "size of cap on error bar plot"],
'axes': [None, "set current axis"],
'linestyle': [None, "linestyle, only active if `fmt`=None"],
'xspace': [np.linspace, "xspace gets called with xspace(xmin,xmax,steps) in :func:`function` to get the points of the function that will be drawn."],
'only_uncertainties': [False, "Only plot uncertainties for interpolation or fit", ],
}
[docs]@doc.append_doc(ffit.fit_kwargs)
@doc.append_str("\t")
@doc.append_str(doc.table(default, init=False))
@doc.append_str(doc.table({"plot_kwargs ": ["default", "description"]}, bottom=False))
def plot_kwargs(kwargs):
"""Set default plot_kwargs if not set.
"""
kwargs = ffit.fit_kwargs(kwargs)
for k, v in default.items():
if not k in kwargs:
kwargs[k] = v[0]
return kwargs
# @append_doc(default_kwargs)
[docs]def auto(datax, datay, funcs=None, **kwargs):
"""
Automatically loop over functions and fit the best one.
Parameters
----------
funcs : function array
functions to consider as fit. Default all ``smpl.functions``.
**kwargs : optional
see :func:`plot_kwargs`.
Returns
-------
The best fit function and it's parameters. Also a lambda function where the parameters are already applied.
"""
best_f, best_ff, lambda_f = ffit.auto(datax, datay, funcs, **kwargs)
if not best_f is None:
fit(datax, datay, best_f, **kwargs)
return best_f, best_ff, lambda_f
# @append_doc(default_kwargs)
[docs]def fit(datax, datay, function, **kwargs):
"""
Fit and plot function to datax and datay.
Parameters
----------
datax : array_like
X data either as ``unp.uarray`` or ``np.array`` or ``list``
datay : array_like
Y data either as ``unp.uarray`` or ``np.array`` or ``list``
function : func
Fit function with parameters: ``x``, ``params``
**kwargs : optional
see :func:`plot_kwargs`.
Fit parameters can be fixed via ``kwargs`` eg. ``a=5``.
Returns
-------
array_like
Optimized fit parameters of ``function`` to ``datax`` and ``datay``
Examples
--------
.. plot::
:include-source:
>>> from smpl import functions as f
>>> from smpl import plot
>>> param = plot.fit([0,1,2],[0,1,2],f.line)
>>> plot.unv(param).round()[0]
1.0
"""
kwargs = plot_kwargs(kwargs)
x = None
y = None
rfit = None
ifit = None
fig = None
fig = init_plot(kwargs)
ll = None
if kwargs['also_data']:
ll = plt_data(datax, datay, **kwargs).get_color()
if kwargs['interpolate']:
ifit, _, x, y = plt_interpolate(datax, datay, icolor=ll, **kwargs)
if kwargs['also_fit']:
rfit, kwargs['fit_color'], _, _ = plt_fit(
datax, datay, function, **kwargs)
if kwargs['ss']:
kwargs['oldshow'] = kwargs['show']
kwargs['show'] = kwargs['show'] and not kwargs['residue']
save_plot(**kwargs)
kwargs['show'] = kwargs['oldshow']
if kwargs['residue'] and fig is not None:
plt_residue(datax, datay, function, rfit, fig, **kwargs)
if not kwargs["also_fit"] and kwargs["interpolate"]:
return (ifit, x, y)
# return ifit
return rfit
# @append_doc(default_kwargs)
[docs]def data(datax, datay, function=None, **kwargs):
"""
Plot datay against datax via :func:`fit`
Parameters
----------
datax : array_like
X data either as ``unp.uarray`` or ``np.array`` or ``list``
datay : array_like
Y data either as ``unp.uarray`` or ``np.array`` or ``list``
function : func,optional
Fit function with parameters: ``x``, ``params``
**kwargs : optional
see :func:`plot_kwargs`.
Returns
-------
array_like
Optimized fit parameters of ``function`` to ``datax`` and ``datay``
"""
if not 'also_fit' in kwargs:
kwargs['also_fit'] = False
kwargs = plot_kwargs(kwargs)
if kwargs['label'] == None and kwargs['lpos'] == 0:
kwargs['lpos'] = -1
return fit(datax, datay, function, **kwargs)
def _function(func, xfit, **kwargs):
kargs = {}
if util.has('fmt', kwargs):
kargs["fmt"] = kwargs["fmt"]
if util.has('label', kwargs) and kwargs['label'] != "":
kargs['label'] = kwargs['label']
if util.has('function_color', kwargs) and kwargs['function_color'] != "":
kargs['color'] = kwargs['function_color']
if util.has('sigmas', kwargs) and kwargs['sigmas'] != "":
kargs['sigmas'] = kwargs['sigmas']
__function(func, xfit, **kargs)
def plt_plt(x, y, fmt, color, label, linestyle):
if linestyle is None and fmt is not None:
return plt.plot(x, y, fmt, label=label, color=color)
elif linestyle is not None and fmt is None:
return plt.plot(x, y, label=label, color=color, linestyle=linestyle)
elif linestyle is None and fmt is None:
return plt.plot(x, y, label=label, color=color)
# TODO needs axis specification?
def __function(gfunc, xlinspace, fmt="-", label=None, color=None, hatch=None, sigmas=0., linestyle=None,only_uncertainties=False):
func = gfunc
x = xlinspace
l = label
y = func(x)
if isinstance(y[0], uncertainties.UFloat):
if sigmas > 0:
if not only_uncertainties:
ll, = plt_plt(x, unv(y), fmt, label=None,
color=color, linestyle=linestyle)
plt.fill_between(x, unv(y)-sigmas*usd(y), unv(
y)+sigmas*usd(y), alpha=0.4, label=l, color=ll.get_color(), hatch=hatch)
else:
ll, = plt_plt([], [], fmt, label=None,
color=color, linestyle=linestyle)
plt.fill_between(x, unv(y)-sigmas*usd(y), unv(
y)+sigmas*usd(y), alpha=0.4, label=l, color=ll.get_color(), hatch=hatch)
else:
ll, = plt_plt(x, unv(y), fmt, label=l,
color=color, linestyle=linestyle)
else:
ll, = plt_plt(x, y, fmt, label=l,
color=color, linestyle=linestyle)
return ll
[docs]def function(func, *args, **kwargs):
"""
Plot function ``func`` between ``xmin`` and ``xmax``
Parameters
----------
func : function
Function to be plotted between ``xmin`` and ``xmax``, only taking `array_like` ``x`` as parameter
*args : optional
arguments for ``func``
**kwargs : optional
see :func:`plot_kwargs`.
"""
if not util.has("xmin", kwargs) or not util.has("xmin", kwargs):
raise Exception("xmin or xmax missing.")
# if not util.has('lpos', kwargs) and not util.has('label', kwargs):
# kwargs['lpos'] = -1
if not util.has('fmt', kwargs):
kwargs['fmt'] = "-"
if not "label" in kwargs:
kwargs = plot_kwargs(kwargs)
kwargs['label'] = get_fnc_legend(func, args, **kwargs)
else:
kwargs = plot_kwargs(kwargs)
xlin = kwargs["xspace"](kwargs['xmin'], kwargs['xmax'], kwargs['steps'])
init_plot(kwargs)
# kwargs['lpos'] = 0
#_plot(xfit, func(xfit, *args), **kwargs)
_function(wrap.get_lambda_argd(
func, kwargs['xvar'], *args), xlin, **kwargs)
if kwargs['ss']:
save_plot(**kwargs)
# xaxis="",yaxis="",fit_color=None,save = None,residue_err=True,show=False):
def plt_residue(datax, datay, gfunction, rfit, fig, **kwargs):
function = wrap.get_lambda(gfunction, kwargs['xvar'])
fig.add_axes((.1, .1, .8, .2))
kwargs['yaxis'] = "$\\Delta$" + kwargs['yaxis']
kwargs['data_color'] = kwargs['fit_color']
if kwargs['residue_err']:
plt_data(datax, datay-function(datax, *rfit), **kwargs)
else:
plt_data(unv(datax), unv(datay-function(datax, *rfit)), **kwargs)
kwargs['lpos'] = -1
save_plot(**kwargs)
def data_split(datax, datay, **kwargs):
return ffit.data_split(datax, datay, **kwargs)
def _fit(datax, datay, function, **kwargs):
"""
Returns a fit like :func:`fit` but does no plotting.
"""
return ffit.fit(datax, datay, function, **kwargs)
def plt_data(datax, datay, **kwargs):
"""
Plot datay vs datax
"""
x, y, xerr, yerr = data_split(datax, datay, **kwargs)
if xerr is not None:
xerr = xerr * kwargs['data_sigmas']
if yerr is not None:
yerr = yerr * kwargs['data_sigmas']
ll = None
if xerr is None and yerr is None:
if kwargs['fmt'] is None:
if kwargs['linestyle'] is None:
ll, = plt.plot(
x, y, label=kwargs['label'], color=kwargs['data_color'])
else:
ll, = plt.plot(
x, y, label=kwargs['label'], color=kwargs['data_color'], linestyle=kwargs['linestyle'])
elif kwargs['fmt'] == "step":
ll, = plt.step(x, y, where='mid',
label=kwargs['label'], color=kwargs['data_color'])
elif kwargs['fmt'] == "hist":
ll, = plt.step(x, y, where='mid',
label=kwargs['label'], color=kwargs['data_color'])
plt.fill_between(x, y, step="mid", color=ll.get_color())
else:
ll, = plt.plot(x, y, kwargs['fmt'], label=kwargs['label'],
color=kwargs['data_color'])
else:
if kwargs['fmt'] is None:
if kwargs['linestyle'] is None:
ll, _, _, = plt.errorbar(x, y, yerr=yerr, xerr=xerr, fmt=" ", capsize=kwargs["capsize"],
label=kwargs['label'], color=kwargs['data_color'])
else:
ll, _, _, = plt.errorbar(x, y, yerr=yerr, xerr=xerr, fmt=" ", capsize=kwargs["capsize"],
label=kwargs['label'], color=kwargs['data_color'], linestyle=kwargs['linestyle'])
elif kwargs['fmt'] == "step":
ll, = plt.step(x, y, where='mid',
color=kwargs['data_color'])
if xerr is not None:
for ix, xv in enumerate(x):
dx = (xerr[ix])
tx = [xv-dx, xv+dx]
plt.fill_between(tx, y[ix]-yerr[ix], y[ix]+yerr[ix],
label=kwargs['label']if ix == 1 else None, alpha=0.2, step='pre', color=ll.get_color())
else:
plt.fill_between(x, y-yerr, y+yerr,
label=kwargs['label'], alpha=0.2, step='mid', color=ll.get_color())
elif kwargs['fmt'] == "hist":
ll, _, _, = plt.errorbar(x, y, yerr=yerr, xerr=xerr, fmt=" ", capsize=kwargs["capsize"],
color="black")
plt.fill_between(x, y, step="mid",
label=kwargs['label'], color=ll.get_color())
else:
ll, _, _, = plt.errorbar(x, y, yerr=yerr, xerr=xerr, fmt=kwargs['fmt'], capsize=kwargs["capsize"],
label=kwargs['label'], color=kwargs['data_color'])
return ll
def get_fnc_legend(function, rfit, **kwargs):
l = wrap.get_latex(function)
vnames = wrap.get_varnames(function, kwargs['xvar'])
for i in range(1, len(vnames)):
l = l + ("\n" if not kwargs["fitinline"] or i == 1 else " ")
l = l + "$" + sympy.latex(sympy.symbols(str(vnames[i]))) + "$="
if kwargs['units'] is not None and usd(rfit[i-1]) > 0:
l = l + "("
if 'number_format' in kwargs:
l = l + kwargs['number_format'].format(rfit[i-1])
else:
l = l + "%s" % (rfit[i-1])
if kwargs['units'] is not None and usd(rfit[i-1]) > 0:
l = l + ")"
if kwargs['units'] is not None:
l = l + " " + kwargs['units'][i-1]
return l
def plt_fit_or_interpolate(datax, datay, fitted, l=None, c=None, f=None, ls=None, **kwargs):
if kwargs['prange'] is None:
x, _, _, _ = ffit.fit_split(datax, datay, **kwargs)
xfit = kwargs['xspace'](
np.min(unv(x)), np.max(unv(x)), kwargs['steps'])
else:
xfit = kwargs['xspace'](kwargs['prange'][0],
kwargs['prange'][1], kwargs['steps'])
ll = __function(fitted, xfit, kwargs['fit_fmt'] if f is not None and ls is None else f, label=l,
color=kwargs['fit_color'] if c is None else c, sigmas=kwargs['sigmas'], linestyle=ls,only_uncertainties=kwargs['only_uncertainties'])
if (kwargs['frange'] is not None or kwargs['fselector'] is not None) and util.true('extrapolate', kwargs) or util.has("extrapolate_max", kwargs) or util.has("extrapolate_min", kwargs):
xxfit = kwargs['xspace'](util.get("extrapolate_min", kwargs, np.min(
unv(datax))), util.get("extrapolate_max", kwargs, np.max(unv(datax))), kwargs['steps'])
__function(fitted, kwargs['xspace'](np.min(xxfit), np.min(xfit), kwargs['steps']), util.get("extrapolate_fmt", kwargs, "--"),
color=ll.get_color(), hatch=util.get("extrapolate_hatch", kwargs, r"||"), sigmas=kwargs['sigmas'])
__function(fitted, kwargs['xspace'](np.max(xfit), np.max(xxfit), kwargs['steps']), util.get("extrapolate_fmt", kwargs, "--"),
color=ll.get_color(), hatch=util.get("extrapolate_hatch", kwargs, r"||"), sigmas=kwargs['sigmas'])
return ll.get_color(), xfit, fitted(xfit)
def plt_interpolate(datax, datay, icolor=None, **kwargs):
"""
Interpolate and Plot that Interpolation.
"""
inter = interpolate.interpolate(datax, datay, **kwargs)
kargs = {}
if isinstance(kwargs["interpolate_fmt"], tuple):
kargs["ls"] = kwargs["interpolate_fmt"]
else:
kargs["f"] = kwargs["interpolate_fmt"]
# l = None so that no label
return (inter, *plt_fit_or_interpolate(datax, datay, inter, l=None, c=icolor, **kargs, **kwargs))
def plt_fit(datax, datay, gfunction, **kwargs):
"""
Fit and Plot that Fit.
"""
func = wrap.get_lambda(gfunction, kwargs['xvar'])
rfit = _fit(datax, datay, gfunction, **kwargs)
def fitted(x): return func(x, *rfit)
l = get_fnc_legend(gfunction, rfit, **kwargs)
return (rfit, *plt_fit_or_interpolate(datax, datay, fitted, l, **kwargs))
def init_plot(kwargs):
fig = None
if util.has("axes", kwargs) and kwargs["axes"] is not None:
plt.sca(kwargs["axes"])
fig = kwargs["axes"].get_figure()
if kwargs['init'] or util.true("residue", kwargs):
if kwargs['size'] is None:
fig = plt.figure()
else:
fig = plt.figure(figsize=kwargs['size'])
if kwargs['residue']:
fig.add_axes((.1, .3, .8, .6))
if util.has("xlabel", kwargs) and kwargs['xlabel'] != "":
plt.xlabel(kwargs['xlabel'])
if util.has("ylabel", kwargs) and kwargs['ylabel'] != "":
plt.ylabel(kwargs['ylabel'])
if util.has("xaxis", kwargs) and kwargs['xaxis'] != "":
plt.xlabel(kwargs['xaxis'])
if util.has("yaxis", kwargs) and kwargs['yaxis'] != "":
plt.ylabel(kwargs['yaxis'])
if util.has("next_color", kwargs) and not kwargs['next_color']:
it1, it2 = itertools.tee(iter(plt.gca()._get_lines.prop_cycler))
plt.gca()._get_lines.prop_cycler = it2
tmp_color = next(it1)['color']
if kwargs['data_color'] is None:
kwargs['data_color'] = tmp_color
if kwargs['fit_color'] is None:
kwargs['fit_color'] = tmp_color
if kwargs['function_color'] is None:
kwargs['function_color'] = tmp_color
return fig
def save_plot(**kwargs):
"""
save plot
"""
if 'logy' in kwargs and kwargs['logy']:
plt.gca().set_yscale('log')
if 'logx' in kwargs and kwargs['logx']:
plt.gca().set_xscale('log')
if 'tight' in kwargs and kwargs['tight']:
plt.tight_layout()
if 'lpos' in kwargs and kwargs['lpos'] >= 0:
if(util.has('bbox_to_anchor', kwargs)):
if(util.has('ncol', kwargs)):
plt.legend(loc=kwargs['lpos'], bbox_to_anchor=kwargs['bbox_to_anchor'],
ncol=kwargs['ncol'], borderaxespad=0)
else:
plt.legend(loc=kwargs['lpos'],
bbox_to_anchor=kwargs['bbox_to_anchor'])
else:
plt.legend(loc=kwargs['lpos'])
# plt.gca().set_xlim([kwargs['xmin'],kwargs['xmax']])
# plt.gca().set_ylim([kwargs['ymin'],kwargs['ymax']])
if 'save' in kwargs and not kwargs['save'] == None:
io.mkdirs(kwargs['save'])
plt.savefig(kwargs['save'] + ".pdf")
plt.grid(b=kwargs["grid"])
if 'show' in kwargs and kwargs['show']:
show(**kwargs)
def show(**kwargs):
kwargs = plot_kwargs(kwargs)
plt.grid(b=kwargs["grid"])
plt.show()
if __name__ == "__main__":
import doctest
doctest.testmod()