Fix: Seaborn Not Working — FutureWarning, FacetGrid Errors, and Figure-Level Confusion
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 crampedOr 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 deprecatedIn 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 functionhistplot,kdeplot,ecdfplot,rugplotboxplot,violinplot,stripplot,swarmplot,barplot,countplot,pointplotheatmap,clustermap(returnsClusterGrid)regplot,residplot
Figure-level functions (return seaborn.FacetGrid or similar):
relplot→FacetGrid(replaces multiple axes-level functions)displot→FacetGridcatplot→FacetGridpairplot→PairGridjointplot→JointGridlmplot→FacetGrid
The distinction table:
| Figure-level | Axes-level |
|---|---|
| Creates its own figure | Draws into current or specified axes |
Returns a FacetGrid (or subclass) | Returns a matplotlib.axes.Axes |
Supports col= and row= for small multiples | Does not support facets |
Resize with height/aspect | Resize 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 effectplt.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 suptitleFix 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 devNew (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=95 | errorbar=('ci', 95) |
ci=None | errorbar=None |
ci='sd' | errorbar='sd' |
ci=0 | errorbar=None (equivalent) |
| N/A | errorbar=('se', 2) (2 standard errors) |
| N/A | errorbar=('pi', 50) (50% percentile interval) |
Other renames in 0.12–0.13:
| Old | New |
|---|---|
hue='x' with implicit color cycle | hue='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 blockColorblind-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 + grayscaleFix 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.11 | 0.13+ |
|---|---|
sns.distplot(x) | sns.histplot(x) or sns.displot(x) |
sns.tsplot(...) | sns.lineplot(...) |
ci=95 | errorbar=('ci', 95) |
ci=None | errorbar=None |
palette without hue | hue=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 snsFor 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
histplotinstead ofkdeplot(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
| Goal | Function |
|---|---|
| Scatter with regression | sns.regplot, sns.lmplot (figure-level) |
| Distribution | sns.histplot, sns.kdeplot, sns.displot |
| Category comparison | sns.boxplot, sns.violinplot, sns.boxenplot |
| Aggregated bars | sns.barplot, sns.catplot(kind='bar') |
| Time series | sns.lineplot, sns.relplot(kind='line') |
| Pairwise relationships | sns.pairplot |
| Correlation matrix | sns.heatmap |
| Strip/swarm points | sns.stripplot, sns.swarmplot |
| Joint distribution | sns.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.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: Matplotlib Not Working — Plots Not Showing, Blank Output, and Figure Layout Problems
How to fix Matplotlib errors — plot not displaying, blank figure, RuntimeError main thread not in main loop, tight_layout UserWarning, overlapping subplots, savefig saving blank image, backend errors, and figure/axes confusion.
Fix: Dask Not Working — Scheduler Errors, Out of Memory, and Delayed Not Computing
How to fix Dask errors — KilledWorker out of memory, client cannot connect to scheduler, delayed not computing, DataFrame partition size wrong, map_partitions TypeError, diagnostics dashboard not showing, and version mismatch.
Fix: Jupyter Notebook Not Working — Kernel Dead, Module Not Found, and Widget Errors
How to fix Jupyter errors — kernel fails to start or dies, ModuleNotFoundError despite pip install, matplotlib plots not showing, ipywidgets not rendering in JupyterLab, port already in use, and jupyter command not found.
Fix: LightGBM Not Working — Installation Errors, Categorical Features, and Training Issues
How to fix LightGBM errors — ImportError libomp libgomp not found, do not support special JSON characters in feature name, categorical feature index out of range, num_leaves vs max_depth overfitting, early stopping callback changes, and GPU build errors.