Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Seaborn Bar Plot with numberline spacing distribution

Tags:

python

seaborn

I want to make a bar plot that has number line type spacing.

So if we have data like this:

d = {'Avg_Price': [22.1, 19.98, 24.4, 24.4, 12.0, 41.98, 12.0, 35.0, 25.84, 25.0, 60.0], 
     'estimated_purchasers': [2796.9999999999995, 1000.0, 672.98, 672.98, 335.0, 299.0, 500.0, 104.22, 42.96, 500.0, 225.0]}

revenues = pd.DataFrame(data=d)

This is just a basic bar plot:

ax = sns.barplot(x='Avg_Price',
                 y='estimated_purchasers',
                 data=revenues)

I want it to be spaced like a number line (so let's equally spaced from 0 to 60) - something more like this:

enter image description here

I am likely fully overthinking this, but how can I do this??

like image 381
mk2080 Avatar asked Dec 16 '25 13:12

mk2080


1 Answers

The big problem you're bumping into is that a seaborn automatically casts a barplot to have an x-axis that is categorical. So instead of true numeric positions, seaborn resamples your x-axis to be in the range of 0 - (number of unique x-values), and then labels them with the string representation of that category. To achieve the plot you want, you can either

  • Implement a workaround with seaborn to fix the x-axis range, and move the drawn rectangles to the appropriate positions (this requires some in-depth knowledge of matplotlib)
import seaborn as sns
import matplotlib.pyplot as plt
sns.set() # invoke seaborn styling

# manually making axes to make a wider plot for viewing
fig, ax = plt.subplots(figsize=(12, 4))

ax = sns.barplot(x='Avg_Price',
                 y='estimated_purchasers',
                 data=revenues)

# Get all unique x-values in ascending order
x_values = sorted(revenues["Avg_Price"].unique())

# New xlim spans from -1 to [max(x_values) + 1]
ax.set_xlim(-1, x_values[-1] + 1)

# a barplot w/ error bars are rectangles (patches) & lines
# so we fetch all artists related to these to update their position on the Axes
artists = zip(ax.patches, ax.lines)
for x_val, (rect, err_line) in zip(x_values, artists):
    # ensure everything is centered on the x_val
    new_rect_x = x_val - (rect.get_width() / 2)
    rect.set_x(new_rect_x)
    
    err_line.set_xdata([x_val, x_val])
    
# Take care to update the x-axis itself
new_xticks = [0, 30, 60]
ax.set_xticks(new_xticks)
ax.set_xticklabels(new_xticks)

enter image description here

My preferred solution will be to skip seaborn all together in this case

  • Draw the plot yourself via matplotlib
import seaborn as sns
import matplotlib.pyplot as plt
sns.set() # invoke seaborn styling

# Perform data aggregation explicitly instead of relying on seaborn
agg_rev = (
    revenues.groupby("Avg_Price")["estimated_purchasers"]
    .agg(["mean", "sem"])
    .reset_index()
)
agg_rev["sem"] = agg_rev["sem"].fillna(0)

# Now we can plot :)
fig, ax = plt.subplots(figsize=(12, 4))
ax.bar(x="Avg_Price", height="mean", data=agg_rev)
ax.errorbar(x="Avg_Price", y="mean", yerr="sem", data=agg_rev, fmt="none", ecolor="black")

ax.set_xticks([0, 30, 60])

ax.set_xlabel("Avg Price")
ax.set_ylabel("estimated_purchases")

ax.grid(False, axis="x") # turn off vertical gird lines b/c they look silly

enter image description here

like image 198
Cameron Riddell Avatar answered Dec 19 '25 04:12

Cameron Riddell



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!