Skip to content

Fix: Seaborn Not Working — FutureWarning, FacetGrid Errors, and Figure-Level Confusion

FixDevs ·

Quick Answer

How to fix Seaborn errors — FutureWarning use of palette without hue, figure-level vs axes-level function confusion, FacetGrid layout issues, tight_layout with seaborn, seaborn 0.13 breaking changes, and ci parameter deprecated.

The Error

You upgrade Seaborn and your plots flood the console with warnings:

FutureWarning: Passing `palette` without assigning `hue` is deprecated and
will be removed in v0.14.0. Assign the `x` variable to `hue` and set `legend=False`

Or tight_layout() doesn’t work the way it does in pure Matplotlib:

g = sns.relplot(data=df, x="x", y="y", col="category")
plt.tight_layout()   # Has no effect — layout still cramped

Or the ci parameter you used last year is now gone:

TypeError: barplot() got an unexpected keyword argument 'ci'

Or you can’t figure out why some functions return a FacetGrid and others return an Axes:

ax = sns.scatterplot(data=df, x="x", y="y")        # Returns Axes
g = sns.relplot(data=df, x="x", y="y")             # Returns FacetGrid
ax.set_title("Hello")   # Works
g.set_title("Hello")    # AttributeError: 'FacetGrid' has no attribute 'set_title'

Seaborn is a high-level statistical plotting library built on Matplotlib. It’s opinionated about DataFrames, provides two tiers of functions (figure-level vs axes-level), and has made several breaking API changes in recent major versions. This guide covers the most common sources of confusion.

Why This Happens

Seaborn’s 0.12 release (September 2022) renamed many parameters for consistency and deprecated several shortcuts that encouraged non-declarative code. The 0.13 release (September 2023) completed the transition. Code written for 0.11 or earlier produces FutureWarning or TypeError when run under 0.13+.

The figure-level vs axes-level distinction is the single most confusing aspect of Seaborn. Figure-level functions (relplot, displot, catplot) return a FacetGrid object and create their own figure. Axes-level functions (scatterplot, histplot, barplot) return a Matplotlib Axes and draw onto the current figure. They have different methods, different customization APIs, and different layout behavior.

Fix 1: FutureWarning: palette without hue

FutureWarning: Passing `palette` without assigning `hue` is deprecated

In Seaborn 0.12+, palette only makes sense when you have a hue mapping. Using palette with just x and y produces a single-color plot, so the palette is effectively ignored — which is why it’s deprecated.

Old pattern (works but warns):

import seaborn as sns
import pandas as pd

df = pd.DataFrame({'day': ['Mon', 'Tue', 'Wed'], 'count': [5, 8, 3]})

# Deprecated — palette has no effect without hue
sns.barplot(data=df, x='day', y='count', palette='Set2')

Fix — assign hue, set legend=False:

# Use x as hue to apply the palette
sns.barplot(data=df, x='day', y='count', hue='day', palette='Set2', legend=False)

# Or just drop the palette if you don't need multi-color
sns.barplot(data=df, x='day', y='count', color='steelblue')

Common Mistake: Treating this as cosmetic and ignoring the warning. In Seaborn 0.14 the parameter will be removed entirely — code that warns now will break later. Fix warnings as they appear.

Fix 2: Figure-Level vs Axes-Level Functions

Seaborn has two parallel APIs. Knowing which one you’re calling determines what customization works.

Axes-level functions (return matplotlib.axes.Axes):

  • scatterplot, lineplot, relplot’s underlying function
  • histplot, kdeplot, ecdfplot, rugplot
  • boxplot, violinplot, stripplot, swarmplot, barplot, countplot, pointplot
  • heatmap, clustermap (returns ClusterGrid)
  • regplot, residplot

Figure-level functions (return seaborn.FacetGrid or similar):

  • relplotFacetGrid (replaces multiple axes-level functions)
  • displotFacetGrid
  • catplotFacetGrid
  • pairplotPairGrid
  • jointplotJointGrid
  • lmplotFacetGrid

The distinction table:

Figure-levelAxes-level
Creates its own figureDraws into current or specified axes
Returns a FacetGrid (or subclass)Returns a matplotlib.axes.Axes
Supports col= and row= for small multiplesDoes not support facets
Resize with height/aspectResize with matplotlib figsize
Use g.set_titles(), g.set_axis_labels()Use ax.set_title(), ax.set_xlabel()

Axes-level example (integrates with matplotlib subplots):

import matplotlib.pyplot as plt
import seaborn as sns

fig, axes = plt.subplots(1, 2, figsize=(12, 5))

sns.scatterplot(data=tips, x='total_bill', y='tip', ax=axes[0])
axes[0].set_title("Scatter")

sns.boxplot(data=tips, x='day', y='total_bill', ax=axes[1])
axes[1].set_title("Boxplot")

fig.tight_layout()

Figure-level example (creates its own figure, supports faceting):

g = sns.relplot(
    data=tips,
    x='total_bill', y='tip',
    col='time', row='smoker',   # Creates a 2x2 grid of subplots
    kind='scatter',
    height=3, aspect=1.2,
)
g.set_axis_labels("Total Bill ($)", "Tip ($)")
g.set_titles("{col_name} | {row_name}")
g.fig.suptitle("Tips by Time and Smoker", y=1.03)
g.savefig("facet.png", bbox_inches='tight')

Pro Tip: Default to axes-level functions when you want to control the figure layout yourself (multiple panels with different plot types, custom subplot positions). Use figure-level functions only when you specifically want faceting (col/row) or when you want Seaborn’s automatic layout logic. Mixing the two in one figure is hard.

Fix 3: tight_layout Doesn’t Work with FacetGrid

g = sns.relplot(data=df, x='x', y='y', col='category')
plt.tight_layout()   # No effect

plt.tight_layout() operates on the current figure’s axes, but figure-level Seaborn functions use their own layout manager.

Fix — use the FacetGrid’s own methods:

g = sns.relplot(data=df, x='x', y='y', col='category')

# Correct ways to adjust layout
g.tight_layout()            # Method on the FacetGrid

# Or adjust spacing directly
g.fig.subplots_adjust(top=0.9, hspace=0.3, wspace=0.2)

# Save with tight bounding box
g.savefig("plot.png", bbox_inches='tight')

Adding a suptitle to a FacetGrid without overlap:

g = sns.relplot(data=df, x='x', y='y', col='category')
g.fig.suptitle("My Title", fontsize=16, y=1.02)   # y > 1 pushes above
g.fig.subplots_adjust(top=0.9)                      # Leave space for suptitle

Fix 4: ci Parameter Removed in 0.12

TypeError: barplot() got an unexpected keyword argument 'ci'

Seaborn 0.12 replaced ci (confidence interval) with errorbar across all plotting functions. The new API is more flexible but breaks existing code.

Old:

sns.barplot(data=tips, x='day', y='total_bill', ci=95)
sns.barplot(data=tips, x='day', y='total_bill', ci=None)   # Disable
sns.barplot(data=tips, x='day', y='total_bill', ci='sd')   # Std dev

New (0.12+):

sns.barplot(data=tips, x='day', y='total_bill', errorbar=('ci', 95))
sns.barplot(data=tips, x='day', y='total_bill', errorbar=None)
sns.barplot(data=tips, x='day', y='total_bill', errorbar='sd')

Full errorbar options:

Old ci=New errorbar=
ci=95errorbar=('ci', 95)
ci=Noneerrorbar=None
ci='sd'errorbar='sd'
ci=0errorbar=None (equivalent)
N/Aerrorbar=('se', 2) (2 standard errors)
N/Aerrorbar=('pi', 50) (50% percentile interval)

Other renames in 0.12–0.13:

OldNew
hue='x' with implicit color cyclehue='x', palette='Set2' explicit
sns.distplot()sns.histplot() or sns.displot()
FacetGrid.map(plt.hist)g.map_dataframe(sns.histplot, 'col_name')
factorplot()catplot() (renamed in 0.9, removed in 0.12)

sns.distplot() was removed entirely in 0.14 — if your code uses it, migrate to histplot or displot.

Fix 5: FacetGrid Customization

FacetGrid is a container — it has its own methods distinct from matplotlib.Axes.

g = sns.FacetGrid(tips, col='day', row='time', height=3)
g.map_dataframe(sns.scatterplot, x='total_bill', y='tip')

# Axis labels on the whole grid
g.set_axis_labels("Bill ($)", "Tip ($)")

# Subplot titles — {col_name}, {row_name}, {col_var}, {row_var}
g.set_titles(col_template="Day: {col_name}", row_template="{row_name}")

# Remove inner labels (cleaner with shared axes)
g.set(xticklabels=[])

# Shared legend
g.add_legend(title="Legend")

# Access individual axes
for ax in g.axes.flat:
    ax.axhline(y=5, color='red', linestyle='--', alpha=0.5)

# Or reach specific axes by position
g.axes[0, 0].set_xlim(0, 50)

Iterate over axes in a figure-level plot:

g = sns.relplot(data=tips, x='total_bill', y='tip', col='day')

# Add reference line to every subplot
for ax in g.axes.flat:
    ax.axhline(y=tips['tip'].mean(), color='gray', linestyle=':', alpha=0.5)

Fix 6: Style, Palette, and Context

Seaborn has four global style systems that affect appearance:

import seaborn as sns

# 1. Style — background, grid
sns.set_style("darkgrid")    # Default
sns.set_style("whitegrid")
sns.set_style("white")
sns.set_style("dark")
sns.set_style("ticks")

# 2. Context — font sizes (paper < notebook < talk < poster)
sns.set_context("paper")     # Smallest
sns.set_context("notebook")  # Default
sns.set_context("talk")
sns.set_context("poster")    # Largest

# 3. Palette — colors for categorical data
sns.set_palette("Set2")
sns.set_palette("husl")
sns.set_palette("colorblind")   # Colorblind-safe

# 4. Theme — combination of style + palette + context
sns.set_theme(style="whitegrid", palette="Set2", context="notebook")

Temporary style with a context manager:

with sns.axes_style("dark"):
    fig, ax = plt.subplots()
    sns.scatterplot(data=df, x='x', y='y', ax=ax)
# Style reverts after the with block

Colorblind-safe palettes for publication:

# All colorblind-safe options
sns.color_palette("colorblind")
sns.color_palette("viridis")      # Perceptually uniform, colorblind-safe
sns.color_palette("cividis")       # Optimized for colorblind + grayscale

Fix 7: Working with Long vs Wide Data

Seaborn strongly prefers long-format (tidy) data — one row per observation, columns for variables. Wide-format data requires reshaping first:

Wide format (hard to plot with Seaborn):

| date       | temperature | humidity | pressure |
|------------|-------------|----------|----------|
| 2024-01-01 | 20          | 60       | 1013     |
| 2024-01-02 | 22          | 55       | 1010     |

Long format (Seaborn-friendly):

| date       | metric      | value |
|------------|-------------|-------|
| 2024-01-01 | temperature | 20    |
| 2024-01-01 | humidity    | 60    |
| 2024-01-01 | pressure    | 1013  |
| 2024-01-02 | temperature | 22    |
| 2024-01-02 | humidity    | 55    |
| 2024-01-02 | pressure    | 1010  |

Convert wide to long with pandas.melt:

import pandas as pd
import seaborn as sns

wide_df = pd.DataFrame({
    'date': pd.date_range('2024-01-01', periods=30),
    'temperature': 20 + np.random.randn(30),
    'humidity': 60 + np.random.randn(30) * 5,
    'pressure': 1013 + np.random.randn(30) * 3,
})

# Melt to long format
long_df = wide_df.melt(
    id_vars=['date'],
    value_vars=['temperature', 'humidity', 'pressure'],
    var_name='metric',
    value_name='value',
)

# Now easy to plot with hue
sns.lineplot(data=long_df, x='date', y='value', hue='metric')

For pandas reshape and DataFrame manipulation patterns, see pandas SettingWithCopyWarning.

Fix 8: Heatmaps and Correlation Matrices

import seaborn as sns
import pandas as pd

# Correlation matrix heatmap
corr = df.corr(numeric_only=True)   # numeric_only=True in Pandas 2.0+

fig, ax = plt.subplots(figsize=(10, 8))
sns.heatmap(
    corr,
    annot=True,                     # Show values in cells
    fmt=".2f",                      # Format: 2 decimal places
    cmap='coolwarm',                # Red-blue diverging
    center=0,                        # Center colormap at 0
    vmin=-1, vmax=1,                # Fixed range for correlation
    square=True,                     # Square cells
    linewidths=0.5,
    cbar_kws={'shrink': 0.8},
    ax=ax,
)
ax.set_title("Feature Correlations")
fig.tight_layout()

Mask the upper triangle for cleaner correlation display:

import numpy as np

mask = np.triu(np.ones_like(corr, dtype=bool))

sns.heatmap(
    corr,
    mask=mask,                       # Hide upper triangle
    annot=True,
    fmt=".2f",
    cmap='coolwarm',
    center=0,
    vmin=-1, vmax=1,
    square=True,
)

Clustermap for automatic row/column clustering:

g = sns.clustermap(
    corr,
    cmap='coolwarm',
    center=0,
    annot=True,
    fmt=".2f",
    figsize=(10, 10),
    dendrogram_ratio=0.1,
)
# clustermap returns a ClusterGrid (not Axes)
g.fig.suptitle("Clustered Correlations", y=1.02)

Still Not Working?

Seaborn 0.13 Breaking Changes Summary

If you’re upgrading from 0.11 or earlier:

0.110.13+
sns.distplot(x)sns.histplot(x) or sns.displot(x)
sns.tsplot(...)sns.lineplot(...)
ci=95errorbar=('ci', 95)
ci=Noneerrorbar=None
palette without huehue=x, palette=..., legend=False
sns.factorplot(...)sns.catplot(...)

Always check sns.__version__ if behavior is unexpected:

import seaborn as sns
print(sns.__version__)

Jupyter Display Issues

In Jupyter, Seaborn plots render inline by default. If they don’t appear:

%matplotlib inline
import seaborn as sns

For Jupyter-specific display issues with Matplotlib and Seaborn, see Jupyter not working and Matplotlib not working.

Performance with Large Datasets

Seaborn is slow on large DataFrames (>100k rows) for density-based plots like kdeplot. Strategies:

  • Sample before plotting: df.sample(10000) for exploration
  • Use histplot instead of kdeplot (faster for large data)
  • Aggregate: group and plot summary statistics instead of raw points

For NumPy array operations behind Seaborn’s statistical functions, see NumPy not working.

Saving High-Resolution Figures

Seaborn figures inherit Matplotlib’s save behavior. For publication-quality output:

# Figure-level
g = sns.relplot(data=tips, x='total_bill', y='tip', col='day')
g.savefig("figure.png", dpi=300, bbox_inches='tight')
g.savefig("figure.pdf", bbox_inches='tight')   # Vector graphics, no DPI

# Axes-level — go through the parent figure
fig, ax = plt.subplots(figsize=(8, 6))
sns.scatterplot(data=tips, x='total_bill', y='tip', ax=ax)
fig.savefig("scatter.png", dpi=300, bbox_inches='tight')

Always use bbox_inches='tight' — otherwise labels and legends that extend outside the plotting area get cropped.

Custom Legend Placement

Legends often cover data. Move them outside the plot area:

# Axes-level
ax = sns.scatterplot(data=tips, x='total_bill', y='tip', hue='day')
ax.legend(bbox_to_anchor=(1.05, 1), loc='upper left', borderaxespad=0)
fig.savefig("plot.png", bbox_inches='tight')   # Includes external legend

# Figure-level — FacetGrid manages its own legend
g = sns.relplot(data=tips, x='total_bill', y='tip', hue='day')
sns.move_legend(g, "upper left", bbox_to_anchor=(1, 1))

Integration with Polars

Seaborn expects Pandas DataFrames. Convert from Polars first:

import polars as pl
import seaborn as sns

pl_df = pl.read_csv("data.csv")
pandas_df = pl_df.to_pandas()
sns.scatterplot(data=pandas_df, x='x', y='y')

For Polars DataFrame operations and conversion patterns, see Polars not working.

Common Plot Types Cheat Sheet

GoalFunction
Scatter with regressionsns.regplot, sns.lmplot (figure-level)
Distributionsns.histplot, sns.kdeplot, sns.displot
Category comparisonsns.boxplot, sns.violinplot, sns.boxenplot
Aggregated barssns.barplot, sns.catplot(kind='bar')
Time seriessns.lineplot, sns.relplot(kind='line')
Pairwise relationshipssns.pairplot
Correlation matrixsns.heatmap
Strip/swarm pointssns.stripplot, sns.swarmplot
Joint distributionsns.jointplot

For choosing the right chart type, remember: Seaborn’s strength is statistical summaries with hue/col/row mapping. If you need fully custom plotting (log scales, twin axes, complex annotations), drop down to raw Matplotlib.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles