2025, Dec 25 09:00

How to Align Seaborn X-Axis Ticks with Bars: Mark Integer Categories as Discrete (displot, countplot, catplot)

Fix misaligned x-axis ticks in Seaborn by treating integer categories as discrete. Learn displot, countplot and catplot tweaks to keep bars and ticks aligned.

When you plot categorical integers with seaborn, it’s easy to end up with x-axis ticks that don’t line up with the bars. The plot looks like a histogram, but the ticks seem to “float” between bars instead of anchoring to each integer. The fix is simple, but it’s not obvious at first glance.

Minimal example that reproduces the misalignment

The following snippet generates integer ages and draws a grouped distribution. The bars render, but the ticks don’t consistently align with the integer centers.

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

# Data
np.random.seed(42)
rows = 5000
frame = pd.DataFrame({
    'PERSON': np.random.randint(100000, 999999, rows),
    'Fruit': np.random.choice(['Banana', 'Strawberry'], rows),
    'Age': np.random.randint(9, 18, rows)
})

chart = sns.displot(
    data=frame,
    x='Age',
    hue='Fruit',
    multiple='dodge'
).figure

chart.show()

What’s really going on

Seaborn needs to know whether the x values should be treated as discrete categories or as a continuous range. Integer values can go either way. If they’re interpreted as continuous, seaborn will bin them, which causes the bar centers and the tick positions to drift apart. That’s why some integers look aligned while others don’t.

The fix: mark the x-axis as discrete

Explicitly telling seaborn the x values are discrete aligns the ticks with the bar centers. Adding a bit of shrink leaves breathing room between grouped bars.

import numpy as np
import pandas as pd
import seaborn as sns
from matplotlib import pyplot as plt

# Data
np.random.seed(42)
rows = 5000
frame = pd.DataFrame({
    'PERSON': np.random.randint(100000, 999999, rows),
    'Fruit': np.random.choice(['Banana', 'Strawberry'], rows),
    'Age': np.random.randint(9, 18, rows)
})

sns.displot(
    data=frame,
    x='Age',
    hue='Fruit',
    multiple='dodge',
    discrete=True,
    shrink=0.8
)

plt.show()

This keeps the relationship between bars and ticks consistent at each integer age.

Alternative: countplot for a single axes-level chart

If you prefer an axes-level approach that draws a single subplot with its legend inside, you can reach for countplot. It produces the same categorical alignment without extra parameters.

import seaborn as sns

sns.countplot(
    data=frame,
    x='Age',
    hue='Fruit',
    dodge=True
)

If you want a figure-level interface instead, the figure-level counterpart is sns.catplot with kind='count':

import seaborn as sns

sns.catplot(data=frame, kind='count', x='Age', hue='Fruit')

Why this matters

Misaligned ticks undermine trust in the visualization. When your audience reads a chart that mixes continuous binning with categorical ticks, it becomes harder to reason about exact counts per category. Being explicit about discrete axes makes the plot interpretable at a glance and avoids accidental miscommunication.

Takeaways

When plotting integer categories with seaborn, treat the x-axis as discrete to keep bars and ticks in sync. Use discrete=True for figure-level displot and, if you need some spacing between grouped bars, add shrink=0.8. If you prefer working at the axes level, countplot handles the same scenario directly; and if you want a figure-level counterpart with the same semantics, catplot with kind='count' is available. Small switches like these make categorical distributions read cleanly and reliably.