Пример #1
0
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
Пример #2
0
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
Пример #3
0
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