import numpy as np
from matplotlib import pyplot as plt
from matplotlib import gridspec, transforms
from desilike import plotting
from desilike.plotting import *
from desilike.parameter import is_parameter_sequence
from . import diagnostics, utils
def _make_list(obj, length=None, default=None):
"""
Return list from ``obj``.
Parameters
----------
obj : object, tuple, list, array
If tuple, list or array, cast to list.
Else return list of ``obj`` with length ``length``.
length : int, default=None
Length of list to return.
Returns
-------
toret : list
"""
if obj is None:
obj = default
if is_parameter_sequence(obj):
obj = list(obj)
if length is not None:
obj += [default] * (length - len(obj))
else:
obj = [obj]
if length is not None:
obj += [default] * (length - len(obj))
return obj
def _get_default_chain_params(chains, params=None, **kwargs):
from desilike.parameter import ParameterCollection
chains = _make_list(chains)
if params is not None:
params = _make_list(params)
list_params = ParameterCollection()
for param in params:
for chain in chains[::-1]:
list_params += chain.params(name=[str(param)])
return list_params
list_params = [chain.params(**kwargs) for chain in chains]
return ParameterCollection([params for params in list_params[0] if all(params in lparams for lparams in list_params[1:])])
def _get_default_profiles_params(profiles, params=None, of='bestfit', **kwargs):
from desilike.parameter import ParameterCollection
profiles = _make_list(profiles)
if not profiles:
return ParameterCollection()
of = _make_list(of)
if params is not None:
params = _make_list(params)
list_params = ParameterCollection()
list_params = ParameterCollection()
for param in params:
for profile in profiles[::-1]:
for off in of:
tmp = profile.get(off, None)
if tmp is not None:
list_params += tmp.params(name=[str(param)])
return list_params
list_params = []
for profile in profiles:
lparams = []
for off in of:
tmp = profile.get(off, None)
if tmp is not None:
lparams += tmp.params(**kwargs)
list_params.append(lparams)
return ParameterCollection([params for params in list_params[0] if all(params in lparams for lparams in list_params[1:])])
[docs]
@plotting.plotter
def plot_trace(chains, params=None, figsize=None, colors=None, labelsize=None, kw_plot=None, fig=None):
"""
Make trace plot as a function of steps, with a panel for each parameter.
Parameters
----------
chains : list, default=None
List of (or single) :class:`Chain` instance(s).
params : list, ParameterCollection, default=None
Parameters to plot trace for.
Defaults to varied and not derived parameters.
figsize : float, tuple, default=None
Figure size.
colors : str, list
List of (or single) color(s) for chains.
labelsize : int, default=None
Label sizes.
kw_plot : dict, default=None
Optional arguments for :meth:`matplotlib.axes.Axes.plot`.
Defaults to ``{'alpha': 0.2}``.
fn : str, Path, default=None
Optionally, path where to save figure.
If not provided, figure is not saved.
kw_save : dict, default=None
Optionally, arguments for :meth:`matplotlib.figure.Figure.savefig`.
show : bool, default=False
If ``True``, show figure.
fig : matplotlib.figure.Figure, default=None
Optionally, a figure with at least as many axes as ``params``.
Returns
-------
fig : matplotlib.figure.Figure
"""
chains = _make_list(chains)
params = _get_default_chain_params(chains, params=params, varied=True, derived=False)
nparams = len(params)
colors = _make_list(colors, length=len(chains), default=None)
kw_plot = kw_plot or {'alpha': 0.2}
steps = 1 + np.arange(max(chain.size for chain in chains))
figsize = figsize or (8, 1.5 * nparams)
if fig is None:
fig, lax = plt.subplots(nparams, sharex=True, sharey=False, figsize=figsize, squeeze=False)
lax = lax.ravel()
else:
lax = fig.axes
for ax, param in zip(lax, params):
ax.grid(True)
ax.set_ylabel(chains[0][param].param.latex(inline=True), fontsize=labelsize)
ax.set_xlim(steps[0], steps[-1])
for ichain, chain in enumerate(chains):
tmp = chain[param].ravel()
ax.plot(steps[:len(tmp)], tmp, color=colors[ichain], **kw_plot)
lax[-1].set_xlabel('step', fontsize=labelsize)
return fig
[docs]
@plotting.plotter
def plot_gelman_rubin(chains, params=None, multivariate=False, threshold=None, slices=None, offset=0, labelsize=None, fig=None, **kwargs):
"""
Plot Gelman-Rubin statistics as a function of steps.
Parameters
----------
chains : list, default=None
List of (or single) :class:`Chain` instance(s).
params : list, ParameterCollection, default=None
Parameters to plot Gelman-Rubin statistics for.
Defaults to varied and not derived parameters.
multivariate : bool, default=False
If ``True``, add line for maximum of eigen value of Gelman-Rubin matrix.
See :func:`diagnostics.gelman_rubin`.
threshold : float, default=None
If not ``None``, plot horizontal line at this value.
slices : list, array
List of increasing number of steps to include in calculation of Gelman-Rubin statistics.
Defaults to ``np.arange(100, nsteps, 500)``, where ``nsteps`` is the minimum size of input ``chains``:
Gelman-Rubin statistics is then plotted for chain slices (0, 100), (0, 600), ...
offset : float, default=0
Offset to apply to the Gelman-Rubin statistics, typically 0 or -1.
labelsize : int, default=None
Label sizes.
fig : matplotlib.figure.Figure, default=None
Optionally, a figure with at least 1 axis.
**kwargs : dict
Optional arguments for :func:`diagnostics.gelman_rubin` ('nsplits', 'check_valid').
fn : str, Path, default=None
Optionally, path where to save figure.
If not provided, figure is not saved.
kw_save : dict, default=None
Optionally, arguments for :meth:`matplotlib.figure.Figure.savefig`.
show : bool, default=False
If ``True``, show figure.
Returns
-------
fig : matplotlib.figure.Figure
"""
chains = _make_list(chains)
params = _get_default_chain_params(chains, params=params, varied=True, derived=False)
if slices is None:
nsteps = min(chain.size for chain in chains)
slices = np.arange(100, nsteps, 500)
gr_multi = []
gr = {param: [] for param in params}
for end in slices:
chains_sliced = [chain.ravel()[:end] for chain in chains]
if multivariate: gr_multi.append(diagnostics.gelman_rubin(chains_sliced, params, method='eigen', **kwargs).max())
for param in gr: gr[param].append(diagnostics.gelman_rubin(chains_sliced, param, method='diag', **kwargs))
gr_multi = np.asarray(gr_multi)
for param in gr: gr[param] = np.asarray(gr[param])
if fig is None:
fig, ax = plt.subplots()
else:
ax = fig.axes[0]
ax.grid(True)
ax.set_xlabel('step', fontsize=labelsize)
ylabel = r'$\hat{{R}} {} {}$'.format('-' if (offset < 0) else '+', abs(offset)) if offset != 0 else r'$\hat{{R}}$'
ax.set_ylabel(ylabel, fontsize=labelsize)
if multivariate: ax.plot(slices, gr_multi + offset, label='multi', linestyle='-', linewidth=1, color='k')
for param in params:
ax.plot(slices, gr[param] + offset, label=chains[0][param].param.latex(inline=True), linestyle='--', linewidth=1)
if threshold is not None: ax.axhline(y=threshold, xmin=0., xmax=1., linestyle='--', linewidth=1, color='k')
ax.legend()
return fig
[docs]
@plotting.plotter
def plot_geweke(chains, params=None, threshold=None, slices=None, labelsize=None, fig=None, **kwargs):
"""
Plot Geweke statistics.
Parameters
----------
chains : list, default=None
List of (or single) :class:`Chain` instance(s).
params : list, ParameterCollection, default=None
Parameters to plot Geweke statistics for.
Defaults to varied and not derived parameters.
threshold : float, default=None
If not ``None``, plot horizontal line at this value.
slices : list, array
List of increasing number of steps to include in calculation of Geweke statistics.
Defaults to ``np.arange(100, nsteps, 500)``, where ``nsteps`` is the minimum size of input ``chains``:
Geweke statistics is then plotted for chain slices (0, 100), (0, 600), ...
labelsize : int, default=None
Label sizes.
fig : matplotlib.figure.Figure, default=None
Optionally, a figure with at least 1 axis.
**kwargs : dict
Optional arguments for :func:`diagnostics.geweke` ('first', 'last').
fn : str, Path, default=None
Optionally, path where to save figure.
If not provided, figure is not saved.
kw_save : dict, default=None
Optionally, arguments for :meth:`matplotlib.figure.Figure.savefig`.
show : bool, default=False
If ``True``, show figure.
Returns
-------
fig : matplotlib.figure.Figure
"""
params = _get_default_chain_params(chains, params=params, varied=True, derived=False)
if slices is None:
nsteps = min(chain.size for chain in chains)
slices = np.arange(100, nsteps, 500)
geweke = {param: [] for param in params}
for end in slices:
chains_sliced = [chain.ravel()[:end] for chain in chains]
for param in geweke: geweke[param].append(diagnostics.geweke(chains_sliced, param, **kwargs))
for param in geweke: geweke[param] = np.asarray(geweke[param]).mean(axis=-1)
if fig is None:
fig, ax = plt.subplots()
else:
ax = fig.axes[0]
ax.grid(True)
ax.set_xlabel('step', fontsize=labelsize)
ax.set_ylabel(r'geweke', fontsize=labelsize)
for param in params:
ax.plot(slices, geweke[param], label=chains[0][param].param.latex(inline=True), linestyle='-', linewidth=1)
if threshold is not None: ax.axhline(y=threshold, xmin=0., xmax=1., linestyle='--', linewidth=1, color='k')
ax.legend()
return fig
[docs]
@plotting.plotter
def plot_autocorrelation_time(chains, params=None, threshold=50, slices=None, labelsize=None, fig=None):
r"""
Plot integrated autocorrelation time.
Parameters
----------
chains : list, default=None
List of (or single) :class:`Chain` instance(s).
params : list, ParameterCollection, default=None
Parameters to plot autocorrelation time for.
Defaults to varied and not derived parameters.
threshold : float, default=50
If not ``None``, plot :math:`y = x/\mathrm{threshold}` line.
Integrated autocorrelation time estimation can be considered reliable when falling under this line.
slices : list, array
List of increasing number of steps to include in calculation of autocorrelation time.
Defaults to ``np.arange(100, nsteps, 500)``, where ``nsteps`` is the minimum size of input ``chains``:
Autocorrelation time is then plotted for chain slices (0, 100), (0, 600), ...
labelsize : int, default=None
Label sizes.
fig : matplotlib.figure.Figure, default=None
Optionally, a figure with at least 1 axis.
fn : str, Path, default=None
Optionally, path where to save figure.
If not provided, figure is not saved.
kw_save : dict, default=None
Optionally, arguments for :meth:`matplotlib.figure.Figure.savefig`.
show : bool, default=False
If ``True``, show figure.
Returns
-------
fig : matplotlib.figure.Figure
"""
chains = _make_list(chains)
params = _get_default_chain_params(chains, params=params, varied=True, derived=False)
if slices is None:
nsteps = min(chain.size for chain in chains)
slices = np.arange(100, nsteps, 500)
autocorr = {param: [] for param in params}
for end in slices:
chains_sliced = [chain.ravel()[:end] for chain in chains]
for param in autocorr:
tmp = diagnostics.integrated_autocorrelation_time(chains_sliced, param)
autocorr[param].append(tmp)
for param in autocorr: autocorr[param] = np.asarray(autocorr[param])
if fig is None:
fig, ax = plt.subplots()
else:
ax = fig.axes[0]
ax.grid(True)
ax.set_xlabel('step $N$', fontsize=labelsize)
ax.set_ylabel('$\tau$', fontsize=labelsize)
for param in params:
ax.plot(slices, autocorr[param], label=chains[0][param].param.latex(inline=True), linestyle='--', linewidth=1)
if threshold is not None:
ax.plot(slices, slices * 1. / threshold, label='$N/{:d}$'.format(threshold), linestyle='--', linewidth=1, color='k')
ax.legend()
return fig
[docs]
def add_legend(labels, colors=None, linestyles=None, fig=None, kw_handle=None, **kwargs):
"""
Add legend to figure.
Parameters
----------
labels : list, str
Label(s) for profiles within each :class:`Profiles` instance.
colors : list, str, default=None
Color(s) for profiles within each :class:`Profiles` instance.
linestyles : list, str, default=None
Linestyle(s) for profiles within each :class:`Profiles` instance.
fig : matplotlib.figure.Figure, default=None
Optionally, figure to add legend to. Else, take ``plt.gcf()``.
**kwargs : dict
Other arguments for :meth:`fig.legend`.
"""
if fig is None: fig = plt.gcf()
labels = _make_list(labels)
nlabels = len(labels)
colors = _make_list(colors, length=nlabels, default=None)
for i, color in enumerate(colors):
if color is None: colors[i] = 'C{:d}'.format(i)
linestyles = _make_list(linestyles, length=nlabels, default=None)
kw_handle = dict(kw_handle or {})
from matplotlib.lines import Line2D
handles = [Line2D([0, 1], [0, 1], color=color, linestyle=linestyle, **kw_handle) for color, linestyle in zip(colors, linestyles)]
kwargs.setdefault('handles', handles)
kwargs.setdefault('labels', labels)
fig.legend(**kwargs)
[docs]
def add_1d_profile(profile, param, ax=None, **kwargs):
"""
Add 1D profile to axes.
Requires :attr:`Profiles.profile` (or :attr:`Profiles.bestfit` and :attr:`Profiles.error` or :attr:`Profiles.covariance` for Gaussian approximation).
Parameters
----------
profile : Profile
:class:`Profile` instance.
param : Parameter, str
Parameter to plot profile for.
ax : matplotlib.axes.Axes, default=None
Axes where to add profile. Defaults to ``plt.gca()``.
**kwargs : dict
Other arguments for :meth:`plt.plot`.
"""
if ax is None: ax = plt.gca()
def get_gaussian_1d_profile(mean, std, nsigma=3):
t = np.linspace(mean - nsigma * std, mean + nsigma * std, endpoint=False)
return t, np.exp(-(t - mean)**2 / (2 * std**2))
pro = profile.get('profile', {})
if param in pro:
x = pro[param][:, 0]
pdf = np.exp(pro[param][:, 1] - pro[param][:, 1].max())
else:
mean = profile.get('bestfit', None)
std = profile.get('error', None)
is_cov = std is None
if is_cov: std = profile.get('covariance', None)
if mean is not None and std is not None and (param in mean.params() and param in std.params()):
index = mean.logposterior.argmax()
mean = mean[param][index]
std = std.std(param) if is_cov else std[param][index]
x, pdf = get_gaussian_1d_profile(mean, std)
else:
return
ax.plot(x, pdf, **kwargs)
[docs]
def add_2d_contour(profile, param1, param2, ax=None, cl=(1, 2), color='C0', filled=False, pale_factor=0.6, alpha=1., **kwargs):
r"""
Add 2D contour to axes.
Requires :attr:`Profiles.contour` (or :attr:`Profiles.bestfit` and :attr:`Profiles.covariance` for Gaussian approximation).
Parameters
----------
profile : Profile
:class:`Profile` instance.
param1 : Parameter, str
First parameter to plot contour for.
param2 : Parameter, str
Second parameter to plot contour for.
ax : matplotlib.axes.Axes, default=None
Axes where to add profile. Defaults to ``plt.gca()``.
cl : int, default=2
Plot contours up to ``cl`` :math:`\sigma`.
color : str, default='C0'
Color.
filled : bool, default=False
If ``True``, draw filled contours.
pale_factor : float, default=0.6
When ``filled``, lightens contour colors of increasing confidence levels by this amount.
alpha : float, default=1.
Opacity.
**kwargs : dict
Other arguments for :meth:`plt.plot`.
"""
if ax is None: ax = plt.gca()
def pale_colors(color, nlevels, pale_factor=pale_factor):
"""Make color paler. Same as GetDist."""
from matplotlib.colors import colorConverter
color = colorConverter.to_rgb(color)
colors = [color]
for _ in range(1, nlevels):
colors.append([c * (1 - pale_factor) + pale_factor for c in colors[-1]])
return colors
def get_gaussian_2d_contour(mean, cov, nsigma):
radius = utils.nsigmas_to_deltachi2(nsigma, ddof=2)**0.5
t = np.linspace(0., 2. * np.pi, 1000, endpoint=False)
ct, st = np.cos(t), np.sin(t)
sigx2, sigy2, sigxy = cov[0, 0], cov[1, 1], cov[0, 1]
a = radius * np.sqrt(0.5 * (sigx2 + sigy2) + np.sqrt(0.25 * (sigx2 - sigy2)**2. + sigxy**2.))
b = radius * np.sqrt(0.5 * (sigx2 + sigy2) - np.sqrt(0.25 * (sigx2 - sigy2)**2. + sigxy**2.))
th = 0.5 * np.arctan2(2. * sigxy, sigx2 - sigy2)
x1 = mean[0] + a * ct * np.cos(th) - b * st * np.sin(th)
x2 = mean[1] + a * ct * np.sin(th) + b * st * np.cos(th)
x1, x2 = (np.concatenate([xx, xx[:1]], axis=0) for xx in (x1, x2))
return x1, x2
cl = _make_list(cl)
ccolors = dict(zip(cl, pale_colors(color, len(cl), pale_factor=pale_factor)))
for nsigma in cl[::-1]:
contour = profile.get('contour', {}).get(nsigma, [])
if (param1, param2) in contour:
x1, x2 = contour[param1, param2]
else:
mean = profile.get('bestfit', None)
cov = profile.get('covariance', None)
if mean is not None and cov is not None and all(param in mean.params() and param in cov.params() for param in [param1, param2]):
mean = mean.choice(params=[param1, param2], return_type='nparray')
cov = cov.view(params=[param1, param2], return_type='nparray')
x1, x2 = get_gaussian_2d_contour(mean, cov, nsigma)
else:
continue
if filled:
ax.fill(x1, x2, color=ccolors[nsigma], alpha=alpha)
ax.plot(x1, x2, color=ccolors[cl[0]], **kwargs)
[docs]
@plotting.plotter
def plot_triangle_contours(profiles, params=None, labels=None, colors=None, linestyles=None, filled=False, pale_factor=0.6, cl=2, alpha=1., truths=None,
kw_contour=None, kw_truth=None, labelsize=None, kw_legend=None, figsize=None, fig=None):
r"""
Triangle plot for likelihood profiling.
Requires :attr:`Profiles.profile` (or :attr:`Profiles.bestfit` and :attr:`Profiles.error` or :attr:`Profiles.covariance` for Gaussian approximation)
and :attr:`Profiles.contour` (or :attr:`Profiles.bestfit` and :attr:`Profiles.covariance` for Gaussian approximation).
Parameters
----------
profiles : list, default=None
List of (or single) :class:`Profiles` instance(s).
params : list, ParameterCollection, default=None
Parameters to plot distribution for.
Defaults to varied and not derived parameters.
labels : list, str
Label(s) for profiles within each :class:`Profiles` instance.
colors : list, str, default=None
Color(s) for profiles within each :class:`Profiles` instance.
linestyles : list, str, default=None
Linestyle(s) for profiles within each :class:`Profiles` instance.
filled : list, bool, default=None
If ``True``, draw filled contours. Can be provided for each :class:`Profiles` instance.
pale_factor : float, default=0.6
When ``filled``, lightens contour colors of increasing confidence levels by this amount.
cl : int, default=2
Plot contours up to ``cl`` :math:`\sigma`.
alpha : list, float, default=1.
Opacity(ies). Can be provided for each :class:`Profiles` instance.
truths : list, dict, default=None
Plot these truth / reference value for each parameter.
kw_contour : dict, default=None
Other options for plots.
kw_truth : dict, default=None
If ``None``, and ``truth`` not provided, no truth is plotted.
Else, optional arguments for :meth:`matplotlib.axes.Axes.axhline`.
Defaults to ``{'color': 'k', 'linestyle': ':', 'linewidth': 2}``.
labelsize : int, default=None
Label sizes.
fig : matplotlib.figure.Figure, list, array, default=None
Optionally, figure or array / list of axes.
fn : str, Path, default=None
Optionally, path where to save figure.
If not provided, figure is not saved.
kw_save : dict, default=None
Optionally, arguments for :meth:`matplotlib.figure.Figure.savefig`.
show : bool, default=False
If ``True``, show figure.
Returns
-------
fig : matplotlib.figure.Figure
"""
profiles = _make_list(profiles)
params = _get_default_profiles_params(profiles, params=params, of=['bestfit', 'profile'], varied=True, derived=False)
nprofiles = len(profiles)
if isinstance(truths, dict): truths = [truths.get(param.name, None) for param in params]
truths = _make_list(truths, length=len(params), default=None)
labels = _make_list(labels, length=nprofiles, default=None)
colors = _make_list(colors, length=nprofiles, default=None)
for i, color in enumerate(colors):
if color is None: colors[i] = 'C{:d}'.format(i)
alpha = _make_list(alpha, length=nprofiles, default=1.)
filled = _make_list(filled, length=nprofiles, default=False)
linestyles = _make_list(linestyles, length=nprofiles, default=None)
_add_legend = any(label is not None for label in labels)
kw_contour = dict(kw_contour or {})
kw_legend = dict(kw_legend or {})
kw_truth = dict(kw_truth or {'color': 'gray', 'linestyle': '--', 'linewidth': 0.5})
ncols = nrows = len(params)
if fig is None:
from matplotlib.ticker import MaxNLocator
max_nticks = 5
factor = 2
pltdim = factor * nrows
lbdim = 0.5 * factor # size of left/bottom margin
trdim = 0.2 * factor # size of top/right margin
dim = lbdim + pltdim + trdim
figsize = figsize or (dim, dim)
#fig, lax = plt.subplots(nrows=nrows, ncols=ncols, figsize=(dim, dim))
fig = plt.figure(figsize=figsize)
gs = gridspec.GridSpec(nrows=nrows, ncols=ncols, figure=fig, wspace=0., hspace=0.)
lax = np.ndarray((nrows, ncols), dtype=object)
lax[...] = None
for i1, param1 in enumerate(params):
for i2 in range(nrows - 1, i1, -1):
param2 = params[i2]
ax = lax[i2, i1] = fig.add_subplot(gs[i2, i1], sharex=lax[nrows - 1, i1] if i2 != nrows - 1 else None, sharey=lax[i2, 0] if i1 > 0 else None)
if i1 > 0:
ax.get_yaxis().set_visible(False)
else:
if i2 < nrows - 1: ax.yaxis.set_major_locator(MaxNLocator(max_nticks, prune='lower'))
ax.set_ylabel(param2.latex(inline=True), fontsize=labelsize)
if i2 < nrows - 1:
ax.get_xaxis().set_visible(False)
else:
if i1 > 0: ax.xaxis.set_major_locator(MaxNLocator(max_nticks, prune='lower'))
ax.set_xlabel(param1.latex(inline=True), fontsize=labelsize)
#ax.set_aspect('equal', adjustable='box')
#ax.set_box_aspect(1)
#for i2 in range(i1 - 1, -1, -1):
# ax = lax[i2, i1]
# ax.set_frame_on(False)
# ax.set_xticks([])
# ax.set_yticks([])
ax = lax[i1, i1] = fig.add_subplot(gs[i1, i1], sharex=lax[nrows - 1, i1] if i1 != nrows - 1 else None)
#ax.set_box_aspect(1)
ax.set_ylim(0., 1.1)
ax.get_yaxis().set_visible(False)
if i1 < nrows - 1:
ax.get_xaxis().set_visible(False)
else:
if i1 > 0: ax.xaxis.set_major_locator(MaxNLocator(max_nticks, prune='lower'))
ax.set_xlabel(param1.latex(inline=True), fontsize=labelsize)
#ax.set_aspect('equal', adjustable='box')
# Format the figure.
lb = lbdim / dim
tr = (lbdim + pltdim) / dim
fig.subplots_adjust(left=lb, bottom=lb, right=tr, top=tr, wspace=0., hspace=0.)
else:
lax = fig.axes
lax = np.ravel(lax)
nsigmas = min(max([cl for pro in profiles for cl in pro.get('contour', [])] + [cl]), cl)
nsigmas = list(range(1, 1 + nsigmas))
for i1, param1 in enumerate(params):
for i2 in range(nrows - 1, i1, -1):
param2 = params[i2]
i = i2 * nrows + i1
for ipro, pro in enumerate(profiles):
color = colors[ipro]
add_2d_contour(pro, param1, param2, ax=lax[i], cl=nsigmas, color=colors[ipro], pale_factor=pale_factor,
filled=filled[ipro], alpha=alpha[ipro], linestyle=linestyles[ipro], **kw_contour)
if truths[i1] is not None:
lax[i].axvline(x=truths[i1], ymin=0., ymax=1., **kw_truth)
if truths[i2] is not None:
lax[i].axhline(y=truths[i2], xmin=0., xmax=1., **kw_truth)
i = i1 * (nrows + 1)
for ipro, pro in enumerate(profiles):
add_1d_profile(pro, param1, ax=lax[i], color=colors[ipro], linestyle=linestyles[ipro], **kw_contour)
if truths[i1] is not None:
lax[i].axvline(x=truths[i1], ymin=0., ymax=1., **kw_truth)
if _add_legend:
add_legend(colors=colors, labels=labels, kw_handle=kw_contour, fig=fig)
return fig
'''
Version not using profiles.
@plotting.plotter
def plot_triangle(samples, params=None, labels=None, g=None, **kwargs):
"""
Triangle plot, specifically for chains, or Gaussian approximations.
For likelihood profiling, use :func:`plot_triangle_contours` instead.
Uses *GetDist* package as a backend.
Parameters
----------
samples : list, default=None
List of (or single) :class:`Chain`, :class:`Profiles` or :class:`LikelihoodFisher` instance(s).
params : list, ParameterCollection, default=None
Parameters to plot distribution for.
Defaults to varied and not derived parameters.
labels : str, list, default=None
Name for *GetDist* to use for input samples.
fn : str, Path, default=None
Optionally, path where to save figure.
If not provided, figure is not saved.
kw_save : dict, default=None
Optionally, arguments for :meth:`matplotlib.figure.Figure.savefig`.
g : getdist subplot_plotter
can be created with `g = getdist.plots.get_subplot_plotter()` and can be modified with g.settings
show : bool, default=False
If ``True``, show figure.
**kwargs : dict
Optional parameters for :meth:`GetDistPlotter.triangle_plot`.
Returns
-------
g : getdist.plots.GetDistPlotter
"""
from getdist import plots
if g is None: g = plots.get_subplot_plotter()
samples = _make_list(samples)
labels = _make_list(labels, length=len(samples), default=None)
params = _get_default_chain_params(samples, params=params, varied=True, input=True)
samples = [sample.to_getdist(label=label, params=sample.params(name=params.names())) for sample, label in zip(samples, labels)]
g.triangle_plot(samples, [str(param) for param in params], **kwargs)
return g
'''
[docs]
@plotting.plotter
def plot_triangle(samples, params=None, labels=None, g=None, contour_colors=None, contour_ls=None, filled=False, legend_ncol=None, legend_loc=None, markers=None, **kwargs):
"""
Triangle plot.
*GetDist* package is used to plot chains.
If :class:`Profiles` are provided, requires :attr:`Profiles.profile` (or :attr:`Profiles.bestfit` and :attr:`Profiles.error` or :attr:`Profiles.covariance` for Gaussian approximation)
and :attr:`Profiles.contour` (or :attr:`Profiles.bestfit` and :attr:`Profiles.covariance` for Gaussian approximation).
Parameters
----------
samples : list, default=None
List of (or single) :class:`Chain`, :class:`Profiles` or :class:`LikelihoodFisher` instance(s).
params : list, ParameterCollection, default=None
Parameters to plot distribution for.
Defaults to varied and not derived parameters.
labels : str, list, default=None
Name for *GetDist* to use for input samples.
fn : str, Path, default=None
Optionally, path where to save figure.
If not provided, figure is not saved.
kw_save : dict, default=None
Optionally, arguments for :meth:`matplotlib.figure.Figure.savefig`.
g : getdist subplot_plotter
can be created with `g = getdist.plots.get_subplot_plotter()` and can be modified with g.settings
show : bool, default=False
If ``True``, show figure.
**kwargs : dict
Optional parameters for :meth:`GetDistPlotter.triangle_plot`.
Returns
-------
g : getdist.plots.GetDistPlotter
"""
from desilike.samples import Chain, Profiles
from getdist import plots
if g is None: g = plots.get_subplot_plotter()
samples = _make_list(samples)
nsamples = len(samples)
labels = _make_list(labels, length=nsamples, default=None)
contour_colors = _make_list(contour_colors, length=nsamples, default=None)
for i, color in enumerate(contour_colors):
if color is None: contour_colors[i] = g.settings.solid_colors[i]
filled = _make_list(filled, length=nsamples, default=False)
contour_ls = _make_list(contour_ls, length=nsamples, default=None)
input_params = params
params = _get_default_chain_params([sample for sample in samples if not isinstance(sample, Profiles)], params=input_params, varied=True, input=True)
params += _get_default_profiles_params([sample for sample in samples if isinstance(sample, Profiles)], of=['bestfit', 'profile'], params=input_params, varied=True, input=True)
for_getdist, getdist_contour_colors, getdist_contour_ls, getdist_filled = [], [], [], []
profiles, profiles_colors, profiles_linestyles, profiles_filled = [], [], [], []
# Sort between what GetDist can handle (chains) and profiles
for idx, (sample, label) in enumerate(zip(samples, labels)):
if isinstance(sample, Chain) or (not hasattr(sample, 'profile') and not hasattr(sample, 'contour')):
for_getdist.append(sample.to_getdist(label=label, params=sample.params(name=params.names())))
getdist_contour_colors.append(contour_colors[idx])
getdist_contour_ls.append(contour_ls[idx])
getdist_filled.append(filled[idx])
else:
profiles.append(sample)
profiles_colors.append(contour_colors[idx])
profiles_linestyles.append(contour_ls[idx])
profiles_filled.append(filled[idx])
if for_getdist:
g.triangle_plot(for_getdist, [str(param) for param in params], contour_colors=getdist_contour_colors, contour_ls=getdist_contour_ls, filled=filled,
legend_ncol=legend_ncol, legend_loc=legend_loc, markers=markers, **kwargs)
kwargs = {'pale_factor': g.settings.solid_contour_palefactor, 'cl': g.settings.num_plot_contours, 'alpha': g.settings.alpha_factor_contour_lines, 'truths': None}
fig = g.subplots
else:
fig = None
kwargs['truths'] = markers
fig = plot_triangle_contours(profiles, params=params, colors=profiles_colors, linestyles=profiles_linestyles, filled=profiles_filled, fig=fig, **kwargs)
if for_getdist and profiles:
# From GetDist
if not legend_loc and g.settings.figure_legend_loc == 'upper center' and len(params) < 4:
legend_loc = 'upper right'
else:
legend_loc = legend_loc or g.settings.figure_legend_loc
args = {}
if 'upper' in legend_loc:
args['bbox_to_anchor'] = (g.plot_col / (2 if 'center' in legend_loc else 1), 1)
args['bbox_transform'] = g.subplots[0, 0].transAxes
args['borderaxespad'] = 0
profiles_lines = [dict(color=color, linestyle=linestyle) for color, linestyle in zip(profiles_colors, profiles_linestyles)]
g.contours_added += [None] * len(profiles_lines)
try: g.legend.remove()
except: pass
g.lines_added.update({len(for_getdist) + i: line for i, line in enumerate(profiles_lines)})
g.finish_plot(labels, legend_ncol=legend_ncol or g.settings.figure_legend_ncol, legend_loc=legend_loc,
no_extra_legend_space=True, **args)
elif profiles:
add_legend(labels=labels, colors=contour_colors, linestyles=contour_ls, fig=fig)
return g
[docs]
@plotting.plotter
def plot_aligned(profiles, param, ids=None, labels=None, colors=None, truth=None, error='error',
labelsize=None, ticksize=None, kw_scatter=None, yband=None, kw_mean=None, kw_truth=None, kw_yband=None,
kw_legend=None, fig=None):
"""
Plot best fit estimates for single parameter.
Parameters
----------
profiles : list
List of (or single) :class:`Profiles` instance(s).
param : Parameter, str
Parameter name.
ids : list, str, default=None
Label(s) for input profiles.
labels : list, str, default=None
Label(s) for best fits within each :class:`Profiles` instance.
colors : list, str, default=None
Color(s) for best fits within each :class:`Profiles` instance.
truth : float, bool, default=None
Plot this truth / reference value for parameter.
If ``True``, take :attr:`Parameter.value`.
error : str, default='error'
What to take as error:
- 'error' for parabolic error
- 'interval' for lower and upper errors corresponding to :math:`\Delta \chi^{2} = 1`.
labelsize : int, default=None
Label sizes.
ticksize : int, default=None
Tick sizes.
kw_scatter : dict, default=None
Optional arguments for :meth:`matplotlib.axes.Axes.scatter`.
Defaults to ``{'marker': 'o'}``.
yband : float, tuple, default=None
If not ``None``, plot horizontal band.
If tuple and last element set to ``'abs'``,
absolute lower and upper y-coordinates of band;
lower and upper fraction around truth.
If float, fraction around truth.
kw_mean : dict, default=None
If ``None``, no mean is plotted.
Else, optional arguments for :meth:`matplotlib.axes.Axes.errorbar`.
Defaults to ``{'marker': 'o'}``.
kw_truth : dict, default=None
If ``None``, and ``truth`` not provided, no truth is plotted.
Else, optional arguments for :meth:`matplotlib.axes.Axes.axhline`.
Defaults to ``{'color': 'k', 'linestyle': ':', 'linewidth': 2}``.
kw_yband : dict, default=None
Optional arguments for :meth:`matplotlib.axes.Axes.axhspan`.
kw_legend : dict, default=None
Optional arguments for :meth:`matplotlib.axes.Axes.legend`.
fig : matplotlib.figure.Figure, default=None
Optionally, a figure with at least 1 axis.
fn : str, Path, default=None
Optionally, path where to save figure.
If not provided, figure is not saved.
kw_save : dict, default=None
Optionally, arguments for :meth:`matplotlib.figure.Figure.savefig`.
show : bool, default=False
If ``True``, show figure.
Returns
-------
fig : matplotlib.figure.Figure
"""
profiles = _make_list(profiles)
if truth is True or (truth is None and kw_truth is not None):
truth = profiles[0].bestfit[param].param.value
kw_truth = dict(kw_truth or {'color': 'k', 'linestyle': ':', 'linewidth': 2})
maxpoints = max(map(lambda prof: len(prof.bestfit), profiles))
ids = _make_list(ids, length=len(profiles), default=None)
labels = _make_list(labels, length=maxpoints, default=None)
colors = _make_list(colors, length=maxpoints, default=['C{:d}'.format(i) for i in range(maxpoints)])
add_legend = any(label is not None for label in labels)
add_mean = kw_mean is not None
if add_mean:
kw_mean = kw_mean if isinstance(kw_mean, dict) else {'marker': 'o'}
kw_scatter = dict(kw_scatter or {'marker': 'o'})
kw_yband = dict(kw_yband or {})
kw_legend = dict(kw_legend or {})
xmain = np.arange(len(profiles))
xaux = np.linspace(-0.15, 0.15, maxpoints)
if fig is None:
fig, ax = plt.subplots()
else:
ax = fig.axes[0]
for iprof, prof in enumerate(profiles):
if param not in prof.bestfit: continue
ibest = prof.bestfit.logposterior.argmax()
for ipoint, point in enumerate(prof.bestfit[param]):
yerr = None
if error:
try:
yerr = prof.get(error)[param]
except KeyError:
yerr = None
else:
if len(yerr) == 1:
yerr = yerr[0] # only for best fit
else:
yerr = yerr[ibest]
label = labels[ipoint] if iprof == 0 else None
ax.errorbar(xmain[iprof] + xaux[ipoint], point, yerr=yerr, color=colors[ipoint], label=label, linestyle='none', **kw_scatter)
if add_mean:
ax.errorbar(xmain[iprof], prof.bestfit[param].mean(), yerr=prof.bestfit[param].std(ddof=1), linestyle='none', **kw_mean)
if truth is not None:
ax.axhline(y=truth, xmin=0., xmax=1., **kw_truth)
if yband is not None:
if np.ndim(yband) == 0:
yband = (yband, yband)
if yband[-1] == 'abs':
low, up = yband[0], yband[1]
else:
if truth is None:
raise ValueError('Plotting relative band requires truth value.')
low, up = truth * (1 - yband[0]), truth * (1 + yband[1])
ax.axhspan(low, up, **kw_yband)
ax.set_xticks(xmain)
ax.set_xticklabels(ids, rotation=40, ha='right', fontsize=ticksize)
ax.grid(True, axis='y')
ax.set_ylabel(profiles[0].bestfit[param].param.latex(inline=True), fontsize=labelsize)
ax.tick_params(labelsize=ticksize)
if add_legend: ax.legend(**{**{'ncol': maxpoints}, **kw_legend})
return fig
[docs]
@plotting.plotter
def plot_aligned_stacked(profiles, params=None, ids=None, labels=None, truths=None, ybands=None, ylimits=None, figsize=None, fig=None, **kwargs):
"""
Plot best fits, with a panel for each parameter.
Parameters
----------
profiles : list
List of (or single) :class:`Profiles` instance(s).
params : list, ParameterCollection, default=None
Parameters to plot best fits for.
Defaults to varied and not derived parameters.
ids : list, str
Label(s) for input profiles.
labels : list, str
Label(s) for best fits within each :class:`Profiles` instance.
truths : list, dict, default=None
Plot these truth / reference value for each parameter.
ybands : list, default=None
If not ``None``, plot horizontal bands.
See :func:`plot_aligned`.
ylimits : list, default=None
If not ``None``, limits for y-axis.
figsize : float, tuple, default=None
Figure size.
fig : matplotlib.figure.Figure, default=None
Optionally, a figure with at least as many axes as ``params``.
fn : str, Path, default=None
Optionally, path where to save figure.
If not provided, figure is not saved.
kw_save : dict, default=None
Optionally, arguments for :meth:`matplotlib.figure.Figure.savefig`.
show : bool, default=False
If ``True``, show figure.
Returns
-------
fig : matplotlib.figure.Figure
"""
profiles = _make_list(profiles)
params = _get_default_profiles_params(profiles, params=params, varied=True, derived=False)
if isinstance(truths, dict): truths = [truths.get(param.name, None) for param in params]
truths = _make_list(truths, length=len(params), default=None)
ybands = _make_list(ybands, length=len(params), default=None)
ylimits = _make_list(ybands, length=len(params), default=None)
maxpoints = max(map(lambda prof: len(prof.bestfit), profiles))
nrows = len(params)
ncols = len(profiles) if len(profiles) > 1 else maxpoints
if fig is None:
figsize = figsize or (ncols, 3. * nrows)
fig, lax = plt.subplots(nrows, 1, figsize=figsize, squeeze=False)
fig.subplots_adjust(wspace=0.1, hspace=0.1)
else:
lax = fig.axes
lax = np.ravel(lax)
for iparam1, param1 in enumerate(params):
ax = lax[iparam1]
plot_aligned(profiles, param=param1, ids=ids, labels=labels, truth=truths[iparam1], yband=ybands[iparam1], fig=ax, **kwargs)
if (iparam1 < nrows - 1) or not ids: ax.get_xaxis().set_visible(False)
ax.set_ylim(ylimits[iparam1])
if iparam1 != 0:
leg = ax.get_legend()
if leg is not None: leg.remove()
return fig
[docs]
@plotting.plotter
def plot_profile(profiles, params=None, offsets=0., nrows=1, labels=None, colors=None, linestyles=None,
cl=(1, 2, 3), labelsize=None, ticksize=None, kw_profile=None, kw_cl=None,
kw_legend=None, figsize=None, fig=None):
"""
Plot profiles, with a panel for each parameter.
Parameters
----------
profiles : list
List of (or single) :class:`Profiles` instance(s).
params : list, ParameterCollection, default=None
Parameters to plot profiles for.
Defaults to varied and not derived parameters.
offsets : list, float, default=0
Vertical offset for each profile.
nrows : int, default=1
Number of rows in figure.
labels : list, str
Label(s) for profiles within each :class:`Profiles` instance.
colors : list, str, default=None
Color(s) for profiles within each :class:`Profiles` instance.
linestyles : list, str, default=None
Linestyle(s) for profiles within each :class:`Profiles` instance.
cl : int, tuple, default=(1, 2, 3)
Confidence levels to plot.
labelsize : int, default=None
Label sizes.
ticksize : int, default=None
Tick sizes.
kw_profile : dict, default=None
Optional arguments for :meth:`matplotlib.axes.Axes.plot`.
Defaults to ``{'marker': 'o'}``.
kw_cl : dict, default=None
Optional arguments for :meth:`matplotlib.axes.Axes.axhline`.
Defaults to ``{'color': 'k', 'linestyle': ':', 'linewidth': 2}``.
kw_legend : dict, default=None
Optional arguments for :meth:`matplotlib.axes.Axes.legend`.
figsize : float, tuple, default=None
Figure size.
fig : matplotlib.figure.Figure, default=None
Optionally, a figure with at least as many axes as ``params``.
fn : str, Path, default=None
Optionally, path where to save figure.
If not provided, figure is not saved.
kw_save : dict, default=None
Optionally, arguments for :meth:`matplotlib.figure.Figure.savefig`.
show : bool, default=False
If ``True``, show figure.
Returns
-------
fig : matplotlib.figure.Figure
"""
profiles = _make_list(profiles)
params = _get_default_profiles_params(profiles, params=params, of='profile', varied=True, derived=False)
nprofiles = len(profiles)
offsets = _make_list(offsets, length=nprofiles, default=0.)
labels = _make_list(labels, length=nprofiles, default=None)
colors = _make_list(colors, length=nprofiles, default=None)
linestyles = _make_list(linestyles, length=nprofiles, default=None)
if np.ndim(cl) == 0: cl = [cl]
add_legend = any(label is not None for label in labels)
kw_profile = dict(kw_profile or {})
kw_cl = dict(kw_cl if kw_cl is not None else {'color': 'k', 'linestyle': ':', 'linewidth': 2})
xshift_cl = kw_cl.pop('xhift', 0.9)
kw_legend = dict(kw_legend or {})
ncols = int((len(params) + nrows - 1) * 1. / nrows)
if fig is None:
figsize = figsize or (4. * ncols, 4. * nrows)
fig, lax = plt.subplots(nrows, ncols, figsize=figsize, squeeze=False)
lax = lax.ravel()
fig.subplots_adjust(wspace=0.2, hspace=0.2)
else:
lax = fig.axes
for iparam1, param1 in enumerate(params):
ax = lax[iparam1]
for ipro, pro in enumerate(profiles):
pro = pro.profile
if param1 not in pro: continue
ax.plot(pro[param1][:, 0], - 2 * (pro[param1][:, 1] - offsets[ipro]), color=colors[ipro], linestyle=linestyles[ipro], label=labels[ipro], **kw_profile)
for nsigma in cl:
y = utils.nsigmas_to_deltachi2(nsigma, ddof=1)
ax.axhline(y=y, xmin=0., xmax=1., **kw_cl)
ax.text(xshift_cl, y + 0.1, r'${:d}\sigma$'.format(nsigma), horizontalalignment='left', verticalalignment='bottom',
transform=transforms.blended_transform_factory(ax.transAxes, ax.transData), color='k', fontsize=labelsize)
lim = ax.get_ylim()
ax.set_ylim(0., lim[-1] + 2.)
ax.tick_params(labelsize=ticksize)
ax.set_xlabel(param1.latex(inline=True), fontsize=labelsize)
if iparam1 == 0: ax.set_ylabel(r'$\Delta \chi^{2}$', fontsize=labelsize)
if add_legend and iparam1 == 0: ax.legend(**kw_legend)
return fig
[docs]
def plot_profile_comparison(profiles, profiles_ref, params=None, labels=None, colors=None, **kwargs):
r"""
Plot profile comparison, wrapping :func:`plot_profile`.
Profiles ``profiles`` and ``profiles_ref`` are both offset by ``profiles`` minimum :math:`\chi^{2}`.
Parameters
----------
profiles : list
List of (or single) :class:`Profiles` instance(s).
profiles_ref : list
List of (or single) :class:`Profiles` instance(s) to compare to.
params : list, ParameterCollection, default=None
Parameters to plot profiles for.
Defaults to varied and not derived parameters.
labels : list, str
Label(s) for profiles within each :class:`Profiles` instance.
colors : list, str, default=None
Color(s) for profiles within each :class:`Profiles` instance.
**kwargs : dict
Optional arguments for :func:`plot_profile`
('nrows', 'cl', 'labelsize', 'ticksize', 'kw_profile', 'kw_cl', 'kw_legend', 'figsize').
fig : matplotlib.figure.Figure, default=None
Optionally, a figure with at least as many axes as ``params``.
fn : str, Path, default=None
Optionally, path where to save figure.
If not provided, figure is not saved.
kw_save : dict, default=None
Optionally, arguments for :meth:`matplotlib.figure.Figure.savefig`.
show : bool, default=False
If ``True``, show figure.
Returns
-------
fig : matplotlib.figure.Figure
"""
profiles = _make_list(profiles)
profiles_ref = _make_list(profiles_ref)
if len(profiles) != len(profiles_ref):
raise ValueError('profiles_ref must be of same length as profiles')
nprofiles = len(profiles)
labels = _make_list(labels, length=nprofiles, default=None)
colors = _make_list(colors, length=nprofiles, default=None)
# Subtract chi2_min of profiles from both profiles and profiles_ref
offsets = [pro.bestfit.logposterior.max() for pro in profiles] * 2
colors = colors * 2
linestyles = ['-'] * nprofiles + ['--'] * nprofiles
return plot_profile(profiles + profiles_ref, params=params, offsets=offsets, labels=labels, colors=colors, linestyles=linestyles, **kwargs)