How to avoid overlapping error bars in matplotlib?

Question:

I want to create a plot for two different datasets similar to the one presented in this answer:

enter image description here

In the above image, the author managed to fix the overlapping problem of the error bars by adding some small random scatter in x to the new dataset.

In my problem, I must plot a similar graphic, but having some categorical data in the x axis:

enter image description here

Any ideas on how to slightly move one the error bars of the second dataset using categorical variables at the x axis? I want to avoid the overlapping between the bars for making the visualization easier.

Asked By: revy

||

Answers:

Consider the following approach to highlight plots – combination of errorbar and fill_between with non-zero transparency:

import random
import matplotlib.pyplot as plt

# create sample data
N = 8
data_1 = {
    'x': list(range(N)),
    'y': [10. + random.random() for dummy in range(N)],
    'yerr': [.25 + random.random() for dummy in range(N)]}
data_2 = {
    'x': list(range(N)),
    'y': [10.25 + .5 * random.random() for dummy in range(N)],
    'yerr': [.5 * random.random() for dummy in range(N)]}

# plot
plt.figure()
# only errorbar
plt.subplot(211)
for data in [data_1, data_2]:
    plt.errorbar(**data, fmt='o')
# errorbar + fill_between
plt.subplot(212)
for data in [data_1, data_2]:
    plt.errorbar(**data, alpha=.75, fmt=':', capsize=3, capthick=1)
    data = {
        'x': data['x'],
        'y1': [y - e for y, e in zip(data['y'], data['yerr'])],
        'y2': [y + e for y, e in zip(data['y'], data['yerr'])]}
    plt.fill_between(**data, alpha=.25)

Result:

enter image description here

Answered By: Poolka

You can translate each errorbar by adding the default data transform to a prior translation in data space. This is possible when knowing that categories are in general one data unit away from each other.

import numpy as np; np.random.seed(42)
import matplotlib.pyplot as plt
from matplotlib.transforms import Affine2D

x = list("ABCDEF")
y1, y2 = np.random.randn(2, len(x))
yerr1, yerr2 = np.random.rand(2, len(x))*4+0.3

fig, ax = plt.subplots()

trans1 = Affine2D().translate(-0.1, 0.0) + ax.transData
trans2 = Affine2D().translate(+0.1, 0.0) + ax.transData
er1 = ax.errorbar(x, y1, yerr=yerr1, marker="o", linestyle="none", transform=trans1)
er2 = ax.errorbar(x, y2, yerr=yerr2, marker="o", linestyle="none", transform=trans2)

plt.show()

enter image description here

Alternatively, you could translate the errorbars after applying the data transform and hence move them in units of points.

import numpy as np; np.random.seed(42)
import matplotlib.pyplot as plt
from matplotlib.transforms import ScaledTranslation

x = list("ABCDEF")
y1, y2 = np.random.randn(2, len(x))
yerr1, yerr2 = np.random.rand(2, len(x))*4+0.3

fig, ax = plt.subplots()

trans1 = ax.transData + ScaledTranslation(-5/72, 0, fig.dpi_scale_trans)
trans2 = ax.transData + ScaledTranslation(+5/72, 0, fig.dpi_scale_trans)
er1 = ax.errorbar(x, y1, yerr=yerr1, marker="o", linestyle="none", transform=trans1)
er2 = ax.errorbar(x, y2, yerr=yerr2, marker="o", linestyle="none", transform=trans2)

plt.show()

enter image description here

While results look similar in both cases, they are fundamentally different. You will observe this difference when interactively zooming the axes or changing the figure size.

Threre is example on lib site: https://matplotlib.org/stable/gallery/lines_bars_and_markers/errorbar_subsample.html
enter image description here

You need parameter errorevery=(m, n),
n – how often plot error lines, m – shift with range from 0 to n

Answered By: Vik Nik
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.