1

enter image description here

This is my current plot. There are 3 major groups (v1, v2, v3), each with 3 classes (a, b, c). A class can also have a subclass (a2, c2), but not necessarily.

I need to plot in stacked format, and want to have subclasses grouped together. That is, I need to remove the white gap between a/a2 c/c2 and have a common x-label (a and c, remove a2 and c2), like in the plot below (edited with Paint).

enter image description here

I have no idea how to do it. I thought about manually changing the white gap, but it seems it must be the same across all bars. I tried manually specifying the positioning of the bars but I couldn't. Now I thought about multiple grouping but I am having trouble. Any help is really appreciated.

from matplotlib.colors import to_rgb
import matplotlib.pyplot as plt
import seaborn as sns
import itertools
import numpy as np
import pandas as pd

idx = ['a', 'a2', 'b', 'c', 'c2']
groups = ['v1', 'v2', 'v3']

def model_to_patterns(k):
    return 'o' if '2' in k else ''
patterns = tuple(model_to_patterns(k) for k in idx)

edgecolor = ['black' if '2' not in m else 'white'
                for m in idx]
barplot_colors = ['#1f77b4', 'tab:purple', '#ff7f0e']
palette_bars = itertools.cycle(barplot_colors)

color_map = dict()
ys = dict()
errs = dict()
for f in groups:
    color_map.update({f: next(palette_bars)})
    ys.update({f: []})
    errs.update({f: []})

    for m in idx:
        ys[f].append(np.random.rand())
        errs[f].append([0, np.random.rand() / 10])


# ------------------------------------------------------------------------------

k = list(ys.keys())
for i in reversed(range(1, len(ys))):
    for j in range(len(ys[k[i]])):
        ys[k[i]][j] = np.maximum(1e-6, ys[k[i]][j] - ys[k[i-1]][j])
for k in list(errs.keys())[:-1]:
    errs[k] = [[np.nan, np.nan]] * len(errs[k])

df = pd.DataFrame(ys,
    index=idx
)

errs = list(errs.values())
errs = np.swapaxes(errs, 1, 2)
error_args = dict(lw=1, capsize=2, capthick=1)

color = []
for c in df.columns.map(color_map):
    tmp_color_list = [c if '2' not in m else list(to_rgb(c)) + [0.7]
                            for m in idx]
    color.append(tmp_color_list)

ax = df.interpolate().plot(kind='bar',
                           color=color,
                           figsize=(12,4),
                           width=0.7,
                           edgecolor=edgecolor,
                           yerr=errs,
                           error_kw=error_args,
                           stacked=True)
ax.autoscale(enable=True, axis='x', tight=True)

bars = ax.patches
for bar, hatch in zip(bars, patterns * len(groups)):
    bar.set_hatch(hatch)

plt.draw()
plt.show()

EDIT

This question was marked as duplicate, but none of the linked questions actually solves my problem.

  • This question is closed and does not even have a solution.
  • This and this are not exactly what I am looking for. None of them, in fact, deals with missing 'subclasses'. In my case, I have subclasses only for groups 'a' and 'c', not 'b', and these solutions leave an empty bars for 'b2' (which is missing).
  • Even if this would not be a big problem, the solutions with matplotlib raise errors in my case, and the ones with altair (that look much better) do not include the standard error markers in grouped stacked charts.
Simon
  • 5,070
  • 5
  • 33
  • 59

0 Answers0