horizontal plot of confidence interval and values in matplotlib

I am working with Python with matplotlib and I would like to be able to plot values compared to given confidence interval so it would be easy to read.

Suppose I have the data:

labelstr = [ 'name1','name2','name3' ] 
values = [ 2.1, 40.5, 10.9 ]
lower_bound =  [ 1.8, 38.9 , 10.2 ]
upper_bound =  [ 2.3, 43.8 , 10.7 ]

I would like to plot the values with the corresponding confidence interval to show whether the value belongs or not. I am looking for a way to show the results horizontally. Ideally mark the point in a color if they belong and in a different color if they don’t.

I would also like the show the values for the points and the lower and upper bounds inside the plot.

Something in as in this example would convenient https://blog.uvm.edu/tbplante/2018/03/14/code-to-make-a-dot-and-95-confidence-interval-figure-in-stata/

any suggestions? thank you

Answer

Since you’ve already got aggregated values and bounds, use Axes.errorbar which accepts xerr for horizontal errors:

shape(2, N): Separate lower and upper values for each bar. First row contains the lower errors, the second row contains the upper errors.
Note that all error arrays should have positive values.

  1. Convert the bounds to relative errors of shape 2xN:

    xerr = [
        [value-lower for value, lower in zip(values, lower_bound)],
        [upper-value for value, upper in zip(values, upper_bound)],
    ]
    
    # [[0.30000000000000004, 1.6000000000000014, 0.7000000000000011],
    #  [0.19999999999999973, 3.299999999999997, -0.20000000000000107]]
    

    Note that when the documentation says these values should be positive, it assumes all your values fall within the bounds. In your example, 10.9 falls outside the upper bound of 10.7, so the negative value is expected and should stay negative.

  2. Use numpy.any and numpy.where (or list comprehension) to generate a color array (orange if xerr is negative, else green):

    import numpy as np
    
    inbounds = np.any(np.array(xerr) < 0, axis=0)
    colors = np.where(inbounds, 'orange', 'green')
    
    # array(['green', 'green', 'orange'], dtype='<U8')
    
  3. Plot the values with Axes.scatter, errors with Axes.errorbar, and labels with Axes.annotate:

    import matplotlib.pyplot as plt
    
    fig, ax = plt.subplots()
    ax.scatter(values, labelstr, c=colors, s=30, marker='|')
    ax.set_ylim(-0.5, len(labelstr) - 0.5) # add some vertical padding
    ax.set_xticks([])
    
    ax.errorbar(values, labelstr, xerr=xerr,
        fmt='none',     # don't connect data points
        ecolor='black', # color of error lines
        elinewidth=1,   # width of error lines
        capsize=4,      # length of error caps
        zorder=-1,      # put error bars behind scatter points
    )
    
    for value, lower, upper, label, color in zip(values, lower_bound, upper_bound, labelstr, colors):
        shared = dict(xy=(value, label), color=color, textcoords='offset points', ha='center')
        ax.annotate(value, xytext=(0, 5), va='bottom', **shared)
        ax.annotate(f'[{lower}, {upper}]', xytext=(0, -5), va='top', **shared)
    

    scatter with horizontal error bars