0

When creating a histogram plot with seaborn, I would like to dynamically put the bar with the lower count to the front. Below is a minimal example where now the blue bar is always in the front, no matter its count. For example, in the second bin, I would like the orange bar to be in the front. Basically, I am looking for something similar to multiple="stack", however without adding the columns up. Is that possible?

import numpy as np
import pandas as pd
import seaborn as sns
sns.set()
df_A = pd.DataFrame(np.random.randint(0, 10, 100), columns=["value"])
df_A["label"] = "A"
df_B = pd.DataFrame(np.random.randint(0, 10, 100), columns=["value"])
df_B["label"] = "B"
df = pd.concat([df_A, df_B])
sns.histplot(df, x="value", bins=np.arange(0, 10, 1), hue="label")

histogram

Trenton McKinney
  • 56,955
  • 33
  • 144
  • 158
timbmg
  • 3,192
  • 7
  • 34
  • 52

1 Answers1

3

You could loop through the generated bar containers and compare the heights of corresponding bars. The order of the heights can then be used to set their z-order. Optionally, you could set alpha=1 to make them opaque.

import numpy as np
import pandas as pd
import seaborn as sns

sns.set()
df_A = pd.DataFrame(np.random.randint(0, 10, 100), columns=["value"])
df_A["label"] = "A"
df_B = pd.DataFrame(np.random.randint(0, 10, 100), columns=["value"])
df_B["label"] = "B"
df = pd.concat([df_A, df_B])
ax = sns.histplot(df, x="value", bins=np.arange(0, 10, 1), hue="label")

for bar0, bar1 in zip(ax.containers[0], ax.containers[1]):
    order = np.argsort([bar0.get_height(), bar1.get_height()])
    bar0.set_zorder(4 - order[0])
    bar1.set_zorder(4 - order[1])

z-order of histograms

Here is an example with more hue values. np.argsort needs to be called twice to get the inverse order.

import numpy as np
import pandas as pd
import seaborn as sns

sns.set()
df = pd.DataFrame({"value": np.random.normal(0.01, 1, 4000).cumsum(),
                   "label": np.repeat([*"ABCD"], 1000)})
ax = sns.histplot(df, x="value", bins=30, hue="label", kde=True,
                  line_kws={'zorder': 10, 'lw': 3, 'ls': ':'})
num_groups = len(df["label"].unique())

for bars in zip(*ax.containers):
    order = np.argsort(np.argsort([b.get_height() for b in bars]))
    for bar, bar_order in zip(bars, order):
        bar.set_zorder(2 + num_groups - bar_order)

ordering histograms depending on heights

JohanC
  • 71,591
  • 8
  • 33
  • 66