def plot_stack( self: hist.stack.Stack, *, ax: matplotlib.axes.Axes | None = None, legend: bool | None = False, **kwargs: Any, ) -> Any: if self[0].ndim != 1: raise NotImplementedError("Please project to 1D before calling plot") if "label" not in kwargs: if all(h.name is not None for h in self): kwargs["label"] = [h.name for h in self] ret = histplot(list(self), ax=ax, **kwargs) final_ax = ret[0].stairs.axes _plot_keywords_wrapper(final_ax, legend=legend) return ret
def _plot_ratiolike( self: hist.BaseHist, other: Union[hist.BaseHist, Callable[[np.ndarray], np.ndarray], str], likelihood: bool = False, *, ax_dict: Optional[Dict[str, matplotlib.axes.Axes]] = None, view: Literal["ratio", "pull"], fit_fmt: Optional[str] = None, **kwargs: Any, ) -> Tuple[MainAxisArtists, RatiolikeArtists]: r""" Plot ratio-like plots (ratio plots and pull plots) for BaseHist ``fit_fmt`` can be a string such as ``r"{name} = {value:.3g} $\pm$ {error:.3g}"`` """ try: from iminuit import Minuit # noqa: F401 from scipy.optimize import curve_fit # noqa: F401 except ModuleNotFoundError: print( f"Hist.plot_{view} requires scipy and iminuit. Please install hist[plot] or manually install dependencies.", file=sys.stderr, ) raise if self.ndim != 1: raise TypeError( f"Only 1D-histogram supports ratio plot, try projecting {self.__class__.__name__} to 1D" ) if isinstance(other, hist.hist.Hist) and other.ndim != 1: raise TypeError( f"Only 1D-histogram supports ratio plot, try projecting other={other.__class__.__name__} to 1D" ) if ax_dict: try: main_ax = ax_dict["main_ax"] subplot_ax = ax_dict[f"{view}_ax"] except KeyError: raise ValueError("All axes should be all given or none at all") else: fig = plt.gcf() grid = fig.add_gridspec(2, 1, hspace=0, height_ratios=[3, 1]) main_ax = fig.add_subplot(grid[0]) subplot_ax = fig.add_subplot(grid[1], sharex=main_ax) plt.setp(main_ax.get_xticklabels(), visible=False) # Keyword Argument Conversion: convert the kwargs to several independent args # error bar keyword arguments eb_kwargs = _filter_dict(kwargs, "eb_") eb_kwargs.setdefault("label", "Histogram Data") # Use "fmt" over "marker" to avoid UserWarning on keyword precedence eb_kwargs.setdefault("fmt", "o") eb_kwargs.setdefault("linestyle", "none") # fit plot keyword arguments fp_kwargs = _filter_dict(kwargs, "fp_") fp_kwargs.setdefault("label", "Counts") # bar plot keyword arguments bar_kwargs = _filter_dict(kwargs, "bar_", ignore={"bar_width"}) # uncertainty band keyword arguments ub_kwargs = _filter_dict(kwargs, "ub_") ub_kwargs.setdefault("label", "Uncertainty") # ratio plot keyword arguments rp_kwargs = _filter_dict(kwargs, "rp_") rp_kwargs.setdefault("uncertainty_type", "poisson") rp_kwargs.setdefault("legend_loc", "best") rp_kwargs.setdefault("num_label", None) rp_kwargs.setdefault("denom_label", None) # patch plot keyword arguments pp_kwargs = _filter_dict(kwargs, "pp_") # Judge whether some arguments are left if kwargs: raise ValueError(f"{set(kwargs)}' not needed") main_ax.set_ylabel(fp_kwargs["label"]) # Computation and Fit hist_values = self.values() main_ax_artists: MainAxisArtists # Type now due to control flow if callable(other) or isinstance(other, str): if isinstance(other, str): if other in {"gauss", "gaus", "normal"}: other = _construct_gaussian_callable(self) else: other = _expr_to_lambda(other) ( compare_values, model_uncert, hist_values_uncert, bestfit_result, ) = _fit_callable_to_hist(other, self, likelihood) if fit_fmt is not None: parnames = list(inspect.signature(other).parameters)[1:] popt, pcov = bestfit_result perr = np.sqrt(np.diag(pcov)) fp_label = "Fit" for name, value, error in zip(parnames, popt, perr): fp_label += "\n " fp_label += fit_fmt.format(name=name, value=value, error=error) fp_kwargs["label"] = fp_label else: fp_kwargs["label"] = "Fitted value" main_ax_artists = _plot_fit_result( self, model_values=compare_values, model_uncert=model_uncert, ax=main_ax, eb_kwargs=eb_kwargs, fp_kwargs=fp_kwargs, ub_kwargs=ub_kwargs, ) else: compare_values = other.values() self_artists = histplot(self, ax=main_ax, label=rp_kwargs["num_label"]) other_artists = histplot(other, ax=main_ax, label=rp_kwargs["denom_label"]) main_ax_artists = self_artists, other_artists subplot_ax_artists: RatiolikeArtists # Type now due to control flow # Compute ratios: containing no INF values with np.errstate(divide="ignore", invalid="ignore"): if view == "ratio": ratios = hist_values / compare_values ratio_uncert = ratio_uncertainty( num=hist_values, denom=compare_values, uncertainty_type=rp_kwargs["uncertainty_type"], ) # ratio: plot the ratios using Matplotlib errorbar or bar subplot_ax_artists = plot_ratio_array( self, ratios, ratio_uncert, ax=subplot_ax, **rp_kwargs ) elif view == "pull": pulls = (hist_values - compare_values) / hist_values_uncert pulls[np.isnan(pulls) | np.isinf(pulls)] = 0 # Pass dicts instead of unpacking to avoid conflicts subplot_ax_artists = plot_pull_array( self, pulls, ax=subplot_ax, bar_kwargs=bar_kwargs, pp_kwargs=pp_kwargs ) if main_ax.get_legend_handles_labels()[0]: # Don't plot an empty legend main_ax.legend(loc=rp_kwargs["legend_loc"]) return main_ax_artists, subplot_ax_artists
def plot2d_full( self: hist.BaseHist, *, ax_dict: Optional[Dict[str, matplotlib.axes.Axes]] = None, **kwargs: Any, ) -> Tuple[Hist2DArtists, Hist1DArtists, Hist1DArtists]: """ Plot2d_full method for BaseHist object. Pass a dict of axes to ``ax_dict``, otherwise, the current figure will be used. """ # Type judgement if self.ndim != 2: raise TypeError("Only 2D-histogram has plot2d_full") if ax_dict is None: ax_dict = {} # Default Figure: construct the figure and axes if ax_dict: try: main_ax = ax_dict["main_ax"] top_ax = ax_dict["top_ax"] side_ax = ax_dict["side_ax"] except KeyError: raise ValueError("All axes should be all given or none at all") else: fig = plt.gcf() grid = fig.add_gridspec( 2, 2, hspace=0, wspace=0, width_ratios=[4, 1], height_ratios=[1, 4] ) main_ax = fig.add_subplot(grid[1, 0]) top_ax = fig.add_subplot(grid[0, 0], sharex=main_ax) side_ax = fig.add_subplot(grid[1, 1], sharey=main_ax) # keyword arguments main_kwargs = _filter_dict(kwargs, "main_", ignore={"main_cbar"}) top_kwargs = _filter_dict(kwargs, "top_") side_kwargs = _filter_dict(kwargs, "side_") # judge whether some arguments left if len(kwargs): raise ValueError(f"{set(kwargs)} not needed") # Plot: plot the 2d-histogram # main plot main_art = hist2dplot(self, ax=main_ax, cbar=False, **main_kwargs) # top plot top_art = histplot( self.project(self.axes[0].name or 0), ax=top_ax, **top_kwargs, ) top_ax.spines["top"].set_visible(False) top_ax.spines["right"].set_visible(False) top_ax.xaxis.set_visible(False) top_ax.set_ylabel("Counts") # side plot base = side_ax.transData rot = transforms.Affine2D().rotate_deg(90).scale(-1, 1) side_art = histplot( self.project(self.axes[1].name or 1), ax=side_ax, transform=rot + base, **side_kwargs, ) side_ax.spines["top"].set_visible(False) side_ax.spines["right"].set_visible(False) side_ax.yaxis.set_visible(False) side_ax.set_xlabel("Counts") return main_art, top_art, side_art