Source code for bfade.viewers

from typing import Dict, Any, List, Tuple
import numpy as np
import matplotlib.pyplot as plt

from bfade.util import grid_factory, logger_factory,  state_modifier, printer
from bfade.util import dummy_translator
from bfade.abstract import AbstractMAPViewer

_log = logger_factory(name=__name__, level="DEBUG")

[docs] class BayesViewer(AbstractMAPViewer): def __init__(self, p1: str, b1: list, n1: int, p2: str, b2: list, n2: int, spacing: str = "lin", **kwargs: Dict[str, Any]) -> None: super().__init__(p1, b1, n1, p2, b2, n2, spacing, **kwargs) self.config() self.config_contour()
[docs] @printer def contour(self, element:str = "log_prior", bayes=None, dataset=None): """ Create a contour plot for the specified element. Parameters ---------- element : str, optional The element for which the contour plot is generated. The default is "log_prior". bayes : AbstractBayes An instance of the Bayesian class. The default is None. dataset : Dataset The training dataset. The default is None. Returns ------- None """ _log.debug(f"{self.__class__.__name__}.{self.contour.__name__}. Contour: {element:s}") el2latex = {"log_likelihood": r"$\log\ P[D | \theta]$", "log_prior": r"$\log P[\theta]$", "log_posterior": r"$\log P[\theta | D]$"} fig, ax = plt.subplots(dpi=300) if element == "log_prior": el_cnt = np.array([getattr(bayes, element)(pp1, pp2) for pp1,pp2, in zip(getattr(self, self.p1), getattr(self, self.p2))]) else: el_cnt = np.array([getattr(bayes, element)(dataset, pp1, pp2) for pp1,pp2, in zip(getattr(self, self.p1), getattr(self, self.p2))]) cnt = ax.tricontour(getattr(self, self.p1), getattr(self, self.p2), el_cnt, levels=np.linspace(el_cnt.min(), el_cnt.max(), self.levels), cmap=self.cmap) cbar = plt.gcf().colorbar(cnt, ax=ax, orientation="vertical", pad=0.1, format="%.3f", ticks=np.linspace(el_cnt.min(), el_cnt.max(), self.clevels), label=el2latex[element], alpha=0.65) ax.set_xlabel(self.xlabel) ax.set_ylabel(self.ylabel) ax.set_xlim(self.b1) ax.set_ylim(self.b2) ax.tick_params(direction='in', top=1, right =1) cbar.ax.tick_params(direction='in', top=1, size=2.5) return fig, self.name + "_bay_" + element
[docs] class LaplacePosteriorViewer(AbstractMAPViewer): def __init__(self, p1: str, c1: float, n1: int, p2: str, c2: float, n2: int, bayes, **kwargs: Dict[str, Any]) -> None: """ Initialize LaplacePosteriorViewer. Parameters ---------- p1 : str Name of the first parameter. c1 : float Coverage factor for the first parameter. n1 : int Number of grid points for the first parameter. p2 : str Name of the second parameter. c2 : float Coverage factor for the second parameter. n2 : int Number of grid points for the second parameter. bayes : AbstractBayes An instance of AbstractBayes. Returns ------- None """ _log.debug(f"{self.__class__.__name__}.{self.__init__.__name__}") self.c1 = c1 self.c2 = c2 idx_1 = bayes.pars.index(p1) idx_2 = bayes.pars.index(p2) b1 = np.array([-c1, c1])*(bayes.ihess[idx_1][idx_1]**0.5) + bayes.theta_hat[idx_1] b2 = np.array([-c2, c2])*(bayes.ihess[idx_2][idx_2]**0.5) + bayes.theta_hat[idx_2] super().__init__(p1, b1, n1, p2, b2, n2, spacing="lin", **kwargs) self.config() self.config_contour()
[docs] @printer def contour(self, bayes): """ Plot the contour of joint posterior distribution. Parameters ---------- bayes : AbstractBayes The Bayesian infrastructure for the considered problem. """ _log.debug(f"{self.__class__.__name__}.{self.contour.__name__} -- joint poterior") fig, ax = plt.subplots(dpi=300) el_cnt = np.array([bayes.joint.pdf([pp1, pp2]) for pp1, pp2 in zip(getattr(self, self.p1), getattr(self, self.p2))]) cnt = ax.tricontour(getattr(self, self.p1), getattr(self, self.p2), el_cnt, levels=np.linspace(el_cnt.min(), el_cnt.max(), 21)) cbar = plt.gcf().colorbar(cnt, ax=ax, orientation="vertical", pad=0.1, format="%.3f", label=r"$P[\theta | D]$", alpha=0.65) ax.tick_params(direction='in', top=1, right =1) ax.set_xlabel(self.xlabel) ax.set_ylabel(self.ylabel) ax.set_xlim(self.b1) ax.set_ylim(self.b2) cbar.ax.tick_params(direction='in', top=1, size=2.5) return fig, self.name + "_laplace_joint"
[docs] @printer def marginals(self, p: str, bayes): """ Plot marginal posterior distribution. Parameters ---------- p : str Name of the parameter to be inspected. bayes : AbstractBayes Instance of AbstractBayes to query. Returns ------- None """ _log.debug(f"{self.__class__.__name__}.{self.marginals.__name__}") fig, ax = plt.subplots(dpi=300) ax.plot(np.sort(getattr(self, p)), getattr(bayes, "marginal_" + p).pdf(np.sort(getattr(self, p))), "k") ax.set_xlabel(p) ax.set_ylabel(rf"Marginal Posterior Probability") ax.set_title(f"mean = {getattr(bayes, 'marginal_' + p).mean():.2f}" + \ f"-- st. dev. = {getattr(bayes, 'marginal_' + p).std():.2f}") ax.tick_params(direction='in', top=1, right =1) ax.set_xlabel(self.xlabel) return fig, self.name + "_lap_marginal_" + p
[docs] class PreProViewer(): def __init__(self, x_edges: List[float] = [1,1000], y_edges: List[float]=[100,1000], n: int = 1000, scale: str = "linear", **args: Dict[str, Any]) -> None: """ Initialize the instance. Parameters ---------- x_edges : List[float] Edges for the x-axis. Default is [1, 1000]. y_edges : List[float] Edges for the y-axis. Default is [100, 1000]. n : int Resolution of the curves along the x-axis. The default is 1000. scale : str, optional Scale for both x and y axes. Options are "linear" (default) or "log". **args : Dict[str, Any] - name : str The name of the instance. Returns ------- None """ self.x_edges = x_edges self.y_edges = y_edges self.x_scale = scale self.y_scale = scale self.n = n try: self.name = args.pop("name") except: self.name = "Untitled" self.det_pars = args _log.debug(f"{self.__class__.__name__}.{self.__init__.__name__} -- {self}") if scale == "log": self.x = np.logspace(np.log10(x_edges[0]), np.log10(x_edges[1]), n) else: self.x = np.linspace(x_edges[0], x_edges[1], n) self.config() self.config_canvas()
[docs] def config(self, save: bool = False, folder: str = "./", fmt: str = "png", dpi: int = 300) -> None: """ Configure settings for saving plots. Parameters ---------- save : bool, optional Flag indicating whether to save plots. The default is False. folder : str, optional Folder path where plots will be saved. The default is "./". fmt : str, optional Format for saving plots. The default is "png". dpi : int, optional Dots per inch for saving plots. The default is 300. Returns ------- None """ _log.debug(f"{self.__class__.__name__}.{self.config.__name__}") self.save = save self.folder = folder self.fmt = fmt self.dpi = dpi
[docs] def config_canvas(self, xlabel: str = "x1", ylabel: str = "x2", cbarlabel: str = "Class", class0: str = "0", class1: str = "1", legend_config: Dict = None, translator: Dict = dummy_translator) -> None: """ Configure the canvas for plotting. Parameters ---------- xlabel : str, optional Label for the x-axis. The default is "x1". ylabel : str, optional Label for the y-axis. The default is "x2". cbarlabel : str, optional Label for the color bar. The default is "Class". class0 : str, optional Label for class 0. The default is "0". class1 : str, optional Label for class 1. The default is "1". legend_config : Dict, optional Configuration for the legend. The default is None. translator : Dict or callable Translator for labels. The default is dummy_translator (from util). Returns ------- None """ _log.debug(f"{self.__class__.__name__}.{self.config_canvas.__name__}") self.xlabel = translator[xlabel] self.ylabel = translator[ylabel] self.cbarlabel = translator[cbarlabel] self.class0 = translator[class0] self.class1 = translator[class1] self.legend_config = legend_config
[docs] def add_scatter(self, x1: np.ndarray, x2: np.ndarray, marker: str, label: str, c: np.ndarray, vmin: float, vmax: float): """ Add scatter plot to the canvas. Parameters ---------- x1 : np.ndarray x-coordinates. x2 : np.ndarray y-coordinates. marker : str Marker style. label : str Label for the scatter plot. c : np.ndarray Color values. vmin : float Minimum value for color normalization. vmax : float Maximum value for color normalization. Returns ------- matplotlib.collections.PathCollection Scatter plot. """ return self.ax.scatter(x1, x2, marker=marker, c=c, vmin=vmin, vmax=vmax, cmap='RdYlBu_r', edgecolor='k', s=50, label=label, zorder=2)
[docs] @staticmethod def cbar_edges(data) -> Tuple: """ Compute color bar edges. Parameters ---------- data : Dataset Dataset containing color information. Returns ------- Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, float, float] Indices of class 0, indices of class 1, color values for class 0, color values for class 1, minimum value for color normalization, maximum value for color normalization. """ y0 = np.where(data.y==0) y1 = np.where(data.y==1) try: c0 = data.aux[y0] c1 = data.aux[y1] vmin = data.aux_min vmax = data.aux_max except: c0 = [0]*len(y0[0]) c1 = [1]*len(y1[0]) vmin = 0 vmax = 1 return y0, y1, c0, c1, vmin, vmax
[docs] def add_colourbar(self, ref, vmin: float, vmax: float): """ Add a colorbar to the El Haddad plot. Parameters ---------- ref : matplotlib.image.AxesImage A reference to the image onto which the colorbar is drawn. vmin : float Minimum value for color normalization. vmax : float Maximum value for color normalization. Returns ------- None """ _log.debug(f"{self.__class__.__name__}.{self.add_colourbar.__name__}") cbar = self.fig.colorbar(ref, ax=self.ax, orientation="vertical", pad=0.05, format="%.1f", ticks=list(np.linspace(vmin, vmax, 11)), label=self.cbarlabel) cbar.ax.tick_params(direction='in', top=1, size=2.5)
[docs] @printer def view(self, **kwargs: Dict[str, Any]): """ Compose canvas. Regarding the prediction interval and predictive posterior kwargs are used to probe the interface of the corresponding methods. kwargs : Dict[str, Any] - train_data : Dataset Training data to display. - test_data : Dataset Test data to display. - curve : List[AbstractCurve] Curves to plot. - prediction_interval : MonteCarlo An instance of MonteCarlo. - mc_bayes : AbstractBayes Bayesian infrastructure. - mc_samples : int Sample to draw from the posterior. - mc_distribution : str Posterior to be sampled: "joint" or "marginals". - confidence : float Confidence level for the prediction interval. - predictive_posterior : AbstractBayes Instance of AbstractBayes that contains the predictive_posterior to probe. - post_samples : int Samples to draw from the posterior. - post_data : Dataset Provided input to forecast. - post_op : callable Function used to post-process. """ self.fig, self.ax = plt.subplots(dpi=self.dpi) self.sr = None self.ss = None self.state = self.name try: mc_bayes = kwargs.pop("mc_bayes") except KeyError: pass try: mc_samples = kwargs.pop("mc_samples") except KeyError: pass try: mc_distribution = kwargs.pop("mc_distribution") except KeyError: pass try: confidence = kwargs.pop("confidence") except KeyError: pass try: post_samples = kwargs.pop("post_samples") except KeyError: pass try: post_data = kwargs.pop("post_data") except KeyError: pass try: post_op = kwargs.pop("post_op") except KeyError: pass for k in kwargs: if k == "train_data": _log.info("Inspect training data") y0, y1, c0, c1, vmin, vmax = self.cbar_edges(kwargs[k]) self.sr = self.add_scatter(kwargs[k].X[y0, 0], kwargs[k].X[y0, 1], 'o', self.class0+" (Train)", c0, vmin=vmin, vmax=vmax) self.add_scatter(kwargs[k].X[y1, 0], kwargs[k].X[y1, 1], 'X', self.class1+" (Train)", c1, vmin, vmax) if self.ss is None: self.add_colourbar(self.sr, vmin, vmax) self.state = state_modifier(self.state, "test", "data", "train") _log.debug(f"State: {self.state}") elif k == "test_data": _log.info("Inspect test data") y0, y1, c0, c1, vmin, vmax = self.cbar_edges(kwargs[k]) self.ss = self.add_scatter(kwargs[k].X[y0, 0], kwargs[k].X[y0, 1], 's', self.class0+" (Test)", c0, vmin, vmax) self.add_scatter(kwargs[k].X[y1, 0], kwargs[k].X[y1, 1], 'P', self.class1+" (Test)", c1, vmin, vmax) if self.sr is None: self.add_colourbar(self.ss, vmin, vmax) self.state = state_modifier(self.state, "train", "data", "test") _log.debug(f"State: {self.state}") elif k == "curve": _log.info("Inspect given curves") for c in kwargs[k]: self.ax.plot(self.x, c.equation(self.x), label=c.name, zorder=1) self.state += "_" + c.name.replace(" ", "") _log.debug(f"State: {self.state}") elif k == "prediction_interval": _log.info("Inspect prediction interval") kwargs[k].sample(mc_samples, mc_distribution, mc_bayes) mean, pred, _, = kwargs[k].prediction_interval(self.x_edges, self.n, self.x_scale, confidence) self.ax.plot(self.x, mean - pred, "-.k", label=fr"Pred. band. @{50 - kwargs[k].confidence/2}$\%$", zorder=1) self.ax.plot(self.x, mean + pred, "--k", label=fr"Pred. band. @{50 + kwargs[k].confidence/2}$\%$", zorder=1) self.state += "_pi" _log.debug(f"State: {self.state}") elif k == "predictive_posterior": _log.info("Inspect predictive posterior") predictions = post_op(kwargs[k].predictive_posterior(post_samples, post_data), axis=0) pp = self.ax.tricontourf(post_data.X[:,0], post_data.X[:,1], predictions, cmap='RdBu_r', levels=np.linspace(predictions.min(), predictions.max()+1e-15, 21), antialiased='False', zorder=0) cbar = self.fig.colorbar(pp, ax=self.ax, orientation="vertical", pad=0.03, format="%.2f", ticks = list(np.linspace(predictions.min(), predictions.max(), 11)), label=post_op.__name__.capitalize()) cbar.ax.tick_params(direction='in', top=1, size=2.5) self.state += "_" + post_op.__name__ _log.debug(f"State: {self.state}") else: raise KeyError self.ax.set_xscale(self.x_scale) self.ax.set_yscale(self.y_scale) self.ax.set_xlim(self.x_edges) self.ax.set_ylim(self.y_edges) self.ax.set_xlabel(self.xlabel) self.ax.set_ylabel(self.ylabel) self.ax.tick_params(direction="in", which='both', right=1, top=1) try: legend = self.ax.legend(**self.legend_config) _log.debug(f"{__class__.__name__}.{self.view.__name__}. Custom legend config") except: _log.info(f"{__class__.__name__}.{self.view.__name__}. Legend Setting 'best'") legend = self.ax.legend(loc="best") return self.fig, self.state
def __repr__(self): attributes_str = ',\n '.join(f'{key} = {value}' for key, value in vars(self).items()) return f"{self.__class__.__name__}({attributes_str})"