Alessandro Peca
Alessandro Peca

Reputation: 923

Upper limit with upper error

Is it possibile to have an upper limit (with the down arrow) with the point centered in the the best value and, at the same time, the upper error?

Something like this:

enter image description here

I'm trying with:

import numpy as np
import matplotlib.pyplot as plt

x = np.array([10, 15, 20, 25, 30, 35])
x_el = np.array([1, 1, 2, 25, 1, 2, 1])
x_eu = np.array([1, 1, 2, 1, 1, 2, 1])
y = np.array([29, 15, 9, 10, 25, 14])
y_el = np.array([1, 1, 2, 1, 1, 2, 1])
y_eu = np.array([11,1,2,1,1,2,1])

fig, ax = plt.subplots()

for i in range(len(x)):
    if (x[i] - x_el[i]) == 0:
        el = 0
        ax.errorbar(x[i], y[i], yerr=[[y_el[i]], [y_eu[i]]], xerr=[[el],[x_eu[i]]],
                    c='b', capsize=2, elinewidth=1, marker='o',
                    xuplims=True)
    else:
        ax.errorbar(x[i], y[i], yerr=[[y_el[i]], [y_eu[i]]], xerr=[[x_el[i]], [x_eu[i]]],
                    c='b', capsize=2, elinewidth=1, marker='o')

But this is the result:

enter image description here

The point number 4 has neither the uplim nor the upper error.

Upvotes: 0

Views: 2645

Answers (1)

Mad Physicist
Mad Physicist

Reputation: 114290

The short answer is yes, but you have to plot the upper limits and the error bars separately. Let's start by plotting your normal error bars properly. You can do this without looping if your data is in a numpy array already:

import numpy as np
import matplotlib.pyplot as plt

x = np.array([10, 15, 20, 25, 30, 35])
x_el = np.array([1, 1, 2, 25, 1, 2])
x_eu = np.array([1, 1, 2, 1, 1, 2])
y = np.array([29, 15, 9, 10, 25, 14])
y_el = np.array([1, 1, 2, 1, 1, 2])
y_eu = np.array([11, 1, 2, 1, 1, 2])

fig, ax = plt.subplots()

mask = (x != x_el)

ax.errorbar(x, y, yerr=[y_el, y_eu], xerr=[x_el * mask, x_eu],
            c='b', capsize=2, elinewidth=1, marker='o', linestyle='none')

Notice that I trimmed the error bar arrays down to the same size as x, which allows me to compute the mask using the != operator. Since you are interested in having all the error bars besides the one in x_el, I multiply by the mask. The mask is a boolean, and any error bar that is masked out will just be set to zero that way. All the other bars get plotted properly at this point:

enter image description here

Now you can use the same mask (but inverted) to plot the upper limits:

ax.errorbar(x[~mask], y[~mask], xerr=x_el[~mask],
            c='b', capsize=2, elinewidth=1, marker='o', linestyle='none',
            xuplims=True)

The result is

enter image description here

If you were not interested in having an obscenely long arrow that stretches to zero, you can shorten it to whatever size you like:

ax.errorbar(x[~mask], y[~mask], xerr=1,
            c='b', capsize=2, elinewidth=1, marker='o', linestyle='none',
            xuplims=True)

enter image description here

Alternative

You could even get pretty close with a single plotting call, since xuplims accepts an array of booleans. However, anywhere it is True will eliminate the right bar:

mask = (x == x_el)
ax.errorbar(x, y, yerr=[y_el, y_eu], xerr=[x_el, x_eu],
            c='b', capsize=2, elinewidth=1, marker='o', linestyle='none',
            xuplims=mask)

enter image description here

You end up having to fill in the right bars in this case:

ax.errorbar(x[mask], y[mask], xerr=[np.zeros_like(x_eu)[mask], x_eu[mask]],
            c='b', capsize=2, elinewidth=1, marker='o', linestyle='none')

enter image description here

Upvotes: 2

Related Questions