"""
=============
plotlib.plots
=============
Implementation of plot classes. These inherit from the classes in
plotlib.base and must specify a figure() method which returns
a matplotlib figure object.
Plots should also have a "description" attribute with a one line
description of the type of plot.
A list of implemented plot classes :py:data:`plotlib.plots.plot_types`
is found at the bottom of this module. This is useful for the GUI,
which needs to enumerate the available plots. So if a new plot type
is implemented, it should be added to this list.
Also includes a function to save the current plot.
"""
from itertools import groupby
# SuperPy modules.
from base import *
# External modules.
from scipy.stats import chi2
import matplotlib.pyplot as plt
from matplotlib.ticker import MaxNLocator
[docs]def save_plot(name):
"""
Save a plot with a descriptive name.
.. Warning::
Figure properties specfied in by mplstyle, but could be
overridden here.
:param name: Prefix of filename, without extension
:type name: string
"""
plt.savefig(name)
[docs]class OneDimStandard(OneDimPlot):
"""
Makes a one dimensional plot, showing profile likelihood,
marginalised posterior, and statistics.
"""
description = "One-dimensional plot."
def figure(self):
# Plot mean, median, mode @ 0.02 * height of PDF
fig, ax = self._new_plot(point_height=0.02 * self.pdf_data.pdf.max())
opt = self.plot_options
# Autoscale the y-axis.
ax.autoscale(axis='y')
# Plot posterior PDF
if opt.show_posterior_pdf:
pm.plot_data(self.pdf_data.bin_centers, self.pdf_data.pdf, schemes.posterior)
# Plot profile likelihood
if opt.show_prof_like:
pm.plot_data(self.prof_data.bin_centers, self.prof_data.prof_like, schemes.prof_like)
# Credible regions
lower_credible_region = [
one_dim.credible_region(
self.pdf_data.pdf, self.pdf_data.bin_centers, alpha=aa, region="lower")
for aa in opt.alpha]
upper_credible_region = [
one_dim.credible_region(self.pdf_data.pdf, self.pdf_data.bin_centers, alpha=aa, region="upper")
for aa in opt.alpha]
self.summary.append("Lower credible region: {}".format(lower_credible_region))
self.summary.append("Upper credible region: {}".format(upper_credible_region))
if opt.show_credible_regions:
# Plot the credible region line @ +10% of the max PDF value
cr_height = 1.1 * self.pdf_data.pdf.max()
for lower, upper, scheme in zip(lower_credible_region, upper_credible_region, schemes.credible_regions):
pm.plot_data([lower, upper], [cr_height, cr_height], scheme)
# Confidence intervals
conf_intervals = [one_dim.conf_interval(self.prof_data.prof_chi_sq, self.prof_data.bin_centers, alpha=aa) for aa
in
opt.alpha]
for intervals, scheme in zip(conf_intervals, schemes.conf_intervals):
if opt.show_conf_intervals:
# Plot the CI line @ the max PDF value
pm.plot_data(intervals, [self.pdf_data.pdf.max()] * int(opt.nbins), scheme)
self.summary.append("{}:".format(scheme.label))
for interval in intervals:
self.summary.append(str(interval))
# Add plot legend
pm.legend(opt.leg_title, opt.leg_position)
# Override y-axis label. This prevents the y axis from taking its
# label from the 'y-axis variable' selction in the GUI.
if opt.show_posterior_pdf and not opt.show_prof_like:
plt.ylabel(schemes.posterior.label)
elif opt.show_prof_like and not opt.show_posterior_pdf:
plt.ylabel(schemes.prof_like.label)
else:
plt.ylabel("")
return self.plot_data(figure=fig, summary=self.summary)
[docs]class OneDimChiSq(OneDimPlot):
"""
Makes a one dimensional plot, showing delta-chisq only,
and excluded regions.
"""
description = "One-dimensional chi-squared plot."
def figure(self):
fig, ax = self._new_plot()
opt = self.plot_options
# Plot the delta chi-squared
pm.plot_data(self.prof_data.bin_centers, self.prof_data.prof_chi_sq, schemes.prof_chi_sq)
# Alter the y-axis limit so that it extends to 10.
opt.plot_limits[3] = 10.
pm.plot_limits(ax, opt.plot_limits)
# Confidence intervals as filled regions
critical_chi_sq = [chi2.ppf(1. - aa, 1) for aa in opt.alpha]
for chi_sq, facecolor, name in zip(critical_chi_sq, schemes.prof_chi_sq.colours,
schemes.prof_chi_sq.level_names):
# Create a list where element i is True if bin i should be filled.
fill_where = self.prof_data.prof_chi_sq >= chi_sq
# Fill in areas on the chart above the threshold
if opt.show_conf_intervals:
ax.fill_between(self.prof_data.bin_centers,
0,
10,
where=fill_where,
facecolor=facecolor,
interpolate=False,
alpha=0.7
)
# List the boundaries of the regions that were filled in the summary file
# as comma separated pairs. itertools.groupby splits the list into
# contiguous regions according to the key function - we take the first
# and last elements of the "True" regions.
self.summary.append(name + ":")
for filled, group in groupby(zip(self.prof_data.bin_centers, fill_where), key=lambda x: x[1]):
if filled:
bins = [g[0] for g in group]
self.summary.append("{},{}".format(min(bins), max(bins)))
# Plot a proxy for the legend - plot spurious data outside plot limits,
# with legend entry matching colours of filled regions.
if opt.show_conf_intervals:
plt.plot(-1, -1, 's', color=facecolor, label=name, alpha=0.7, ms=15)
if opt.tau is not None:
# Plot the theory error as a band around the usual line
pm.plot_band(self.prof_data.bin_centers, self.prof_data.prof_chi_sq, opt.tau, ax, schemes.tau_band)
# Add plot legend
pm.legend(opt.leg_title, opt.leg_position)
# Override y-axis label. This prevents the y axis from taking its
# label from the 'y-axis variable' selction in the GUI (as
# in this plot it should always be chi-squared).
plt.ylabel(schemes.prof_chi_sq.label)
return self.plot_data(figure=fig, summary=self.summary)
[docs]class TwoDimPlotFilledPDF(TwoDimPlot):
""" Makes a two dimensional plot with filled credible regions only, showing
best-fit and posterior mean. """
description = "Two-dimensional posterior pdf, filled contours only."
def figure(self):
fig, ax = self._new_plot()
opt = self.plot_options
# Credible regions
levels = [two_dim.critical_density(self.pdf_data.pdf, aa) for aa in opt.alpha]
# Make sure pdf is correctly normalised.
pdf = self.pdf_data.pdf
pdf = pdf / pdf.sum()
# Plot contours
if opt.show_credible_regions:
pm.plot_filled_contour(
pdf,
levels,
schemes.posterior,
bin_limits=opt.bin_limits)
# Add legend
pm.legend(opt.leg_title, opt.leg_position)
return self.plot_data(figure=fig, summary=self.summary)
[docs]class TwoDimPlotFilledPL(TwoDimPlot):
""" Makes a two dimensional plot with filled confidence intervals only, showing
best-fit and posterior mean. """
description = "Two-dimensional profile likelihood, filled contours only."
def figure(self):
fig, ax = self._new_plot()
opt = self.plot_options
levels = [two_dim.critical_prof_like(aa) for aa in opt.alpha]
if opt.show_conf_intervals:
pm.plot_filled_contour(
self.prof_data.prof_like,
levels,
schemes.prof_like,
bin_limits=opt.bin_limits)
# Add legend
pm.legend(opt.leg_title, opt.leg_position)
return self.plot_data(figure=fig, summary=self.summary)
[docs]class TwoDimPlotPDF(TwoDimPlot):
""" Makes a two dimensional marginalised posterior plot, showing
best-fit and posterior mean and credible regions. """
description = "Two-dimensional posterior pdf."
def figure(self):
fig, ax = self._new_plot()
opt = self.plot_options
if opt.show_posterior_pdf:
pm.plot_image(
self.pdf_data.pdf,
opt.bin_limits,
opt.plot_limits,
schemes.posterior)
# Credible regions
levels = [two_dim.critical_density(self.pdf_data.pdf, aa) for aa in opt.alpha]
# Make sure pdf is correctly normalised.
pdf = self.pdf_data.pdf
pdf = pdf / pdf.sum()
if opt.show_credible_regions:
pm.plot_contour(
pdf,
levels,
schemes.posterior,
bin_limits=opt.bin_limits)
# Add legend
pm.legend(opt.leg_title, opt.leg_position)
return self.plot_data(figure=fig, summary=self.summary)
[docs]class TwoDimPlotPL(TwoDimPlot):
""" Makes a two dimensional profile likelihood plot, showing
best-fit and posterior mean and confidence intervals. """
description = "Two-dimensional profile likelihood."
def figure(self):
fig, ax = self._new_plot()
opt = self.plot_options
if opt.show_prof_like:
pm.plot_image(
self.prof_data.prof_like,
opt.bin_limits,
opt.plot_limits,
schemes.prof_like)
levels = [two_dim.critical_prof_like(aa) for aa in opt.alpha]
if opt.show_conf_intervals:
pm.plot_contour(
self.prof_data.prof_like,
levels,
schemes.prof_like,
bin_limits=opt.bin_limits)
# Add legend
pm.legend(opt.leg_title, opt.leg_position)
return self.plot_data(figure=fig, summary=self.summary)
[docs]class Scatter(TwoDimPlot):
""" Makes a three dimensional scatter plot, showing
best-fit and posterior mean and credible regions and confidence intervals.
The scattered points are coloured by the zdata. """
description = "Three-dimensional scatter plot."
def figure(self):
fig, ax = self._new_plot()
opt = self.plot_options
min_ = min(opt.cb_limits) if opt.cb_limits else np.percentile(self.zdata, 5.)
max_ = max(opt.cb_limits) if opt.cb_limits else np.percentile(self.zdata, 95.)
# Plot scatter of points.
sc = plt.scatter(
self.xdata,
self.ydata,
s=schemes.scatter.size,
c=self.zdata,
marker=schemes.scatter.symbol,
cmap=schemes.scatter.colour_map,
norm=None,
vmin=min_,
vmax=max_,
linewidth=0.,
verts=None,
rasterized=True)
# Plot a colour bar. NB "magic" values for fraction and pad taken from
# http://stackoverflow.com/questions/18195758/set-matplotlib-colorbar-size-to-match-graph
cb = plt.colorbar(sc, orientation='vertical', fraction=0.046, pad=0.04)
# Colour bar label
cb.ax.set_ylabel(opt.zlabel)
# Set reasonable number of ticks
cb.locator = MaxNLocator(opt.cbticks)
cb.update_ticks()
# Credible regions
levels = [two_dim.critical_density(self.pdf_data.pdf, aa) for aa in opt.alpha]
# Make sure pdf is correctly normalised
pdf = self.pdf_data.pdf
pdf = pdf / pdf.sum()
if opt.show_credible_regions:
pm.plot_contour(
pdf,
levels,
schemes.posterior,
bin_limits=opt.bin_limits)
levels = [two_dim.critical_prof_like(aa) for aa in opt.alpha]
if opt.show_conf_intervals:
pm.plot_contour(
self.prof_data.prof_like,
levels,
schemes.prof_like,
bin_limits=opt.bin_limits)
# Add legend
pm.legend(opt.leg_title, opt.leg_position)
return self.plot_data(figure=fig, summary=self.summary)
plot_types = [
OneDimStandard,
OneDimChiSq,
TwoDimPlotFilledPDF,
TwoDimPlotFilledPL,
TwoDimPlotPDF,
TwoDimPlotPL,
Scatter
]
"""
List of Plot classes in this module.
"""