1

I have the following problem:

I would like to annotate my bar plot - which has std error line - with the bar height (y value) along with the error line height (error value).

I found this link to annotate bar plots Annotate bars with values on Pandas bar plots. The proposed strategy iterates over Axes Patches and get the patch height to retrieve the y value, but I don't find anything to extract the error value.

I would like to obtain the following result:

My goal

1 Answers1

0

Below, I've reworked bar_label to do something similar to what you are asking, considering asymmetric yerrors.

Edit '%d[%d,%d]' % (value,err[0,1],err[1,1]) in the end of the code to show errors in whatever format you want. I.e., use '%.2f$\pm$%.2f' % (value,err[0,1]) to show symmetric errors. xerrors can also be easily taken into account by adapting err matrix indexing.

import itertools
def bar_label(container, labels=None, *, fmt="%g", label_type="edge",
              padding=0, **kwargs):

    # want to know whether to put label on positive or negative direction
    # cannot use np.sign here because it will return 0 if x == 0
    def sign(x):
        return 1 if x >= 0 else -1

    bars = container.patches
    errorbar = container.errorbar
    datavalues = container.datavalues
    orientation = container.orientation

    if errorbar:
        # check "ErrorbarContainer" for the definition of these elements
        lines = errorbar.lines  # attribute of "ErrorbarContainer" (tuple)
        barlinecols = lines[2]  # 0: data_line, 1: caplines, 2: barlinecols
        barlinecol = barlinecols[0]  # the "LineCollection" of error bars
        errs = barlinecol.get_segments()
    else:
        errs = []

    if labels is None:
        labels = []

    annotations = []

    for bar, err, dat, lbl in itertools.zip_longest(
        bars, errs, datavalues, labels
    ):
        (x0, y0), (x1, y1) = bar.get_bbox().get_points()
        xc, yc = (x0 + x1) / 2, (y0 + y1) / 2

        if orientation == "vertical":
            extrema = max(y0, y1) if dat >= 0 else min(y0, y1)
            length = abs(y0 - y1)
        elif orientation == "horizontal":
            extrema = max(x0, x1) if dat >= 0 else min(x0, x1)
            length = abs(x0 - x1)

        if err is None:
            endpt = extrema
        elif orientation == "vertical":
            endpt = err[:, 1].max() if dat >= 0 else err[:, 1].min()
        elif orientation == "horizontal":
            endpt = err[:, 0].max() if dat >= 0 else err[:, 0].min()

        if label_type == "center":
            value = sign(dat) * length
        elif label_type == "edge":
            value = extrema

        if label_type == "center":
            xy = xc, yc
        elif label_type == "edge" and orientation == "vertical":
            xy = xc, endpt
        elif label_type == "edge" and orientation == "horizontal":
            xy = endpt, yc

        if orientation == "vertical":
            xytext = 0, sign(dat) * padding
        else:
            xytext = sign(dat) * padding, 0

        if label_type == "center":
            ha, va = "center", "center"
        elif label_type == "edge":
            if orientation == "vertical":
                ha = 'center'
                va = 'top' if dat < 0 else 'bottom'  # also handles NaN
            elif orientation == "horizontal":
                ha = 'right' if dat < 0 else 'left'  # also handles NaN
                va = 'center'

        if np.isnan(dat):
            lbl = ''

        annotation = plt.annotate('%d[%d,%d]' % (value,err[0,1],err[1,1]), #fmt % value if lbl is None else lbl,
                                  xy, xytext, textcoords="offset points",
                                  ha=ha, va=va, **kwargs)
        annotations.append(annotation)

    return annotations
Werner
  • 2,537
  • 1
  • 26
  • 38