Matplotlib vs Seaborn: Complete Data Visualization Guide
Master Python data visualization with Matplotlib and Seaborn. Compare features, learn when to use each library, create publication-quality charts, and build custom visualizations with practical examples.

The Visualization Library That Changed Everything
I spent hours tweaking Matplotlib code to create a simple bar chart with nice colors. Lines and lines of customization. Then someone showed me Seaborn: sns.barplot(data=df, x='category', y='value'). One line. Beautiful output.
But here's what they didn't tell me: Sometimes you need Matplotlib's fine-grained control. The best data scientists know both libraries and when to use each.
This comprehensive guide compares Matplotlib and Seaborn, teaching you when to use which, how to combine them, and how to create publication-quality visualizations.
Quick Comparison
| Feature | Matplotlib | Seaborn |
|---|---|---|
| Ease of Use | Moderate | Easy |
| Customization | Maximum | Good |
| Statistical Plots | Manual | Built-in |
| Aesthetics | Basic | Beautiful |
| Learning Curve | Steep | Gentle |
| Use Case | Full control needed | Quick analysis |
Understanding Matplotlib
Matplotlib is the foundation of Python visualization. It's verbose but powerful.
Installation and Setup
# pip install matplotlib
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
# Set style (optional)
plt.style.use('seaborn-v0_8') # Modern style
# For Jupyter notebooks
%matplotlib inline
Basic Matplotlib Plots
# Line plot
x = np.linspace(0, 10, 100)
y = np.sin(x)
plt.figure(figsize=(10, 6))
plt.plot(x, y, label='sin(x)', linewidth=2, color='blue')
plt.plot(x, np.cos(x), label='cos(x)', linewidth=2, color='red', linestyle='--')
plt.xlabel('X axis', fontsize=12)
plt.ylabel('Y axis', fontsize=12)
plt.title('Sine and Cosine Functions', fontsize=14, fontweight='bold')
plt.legend(fontsize=10)
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
Scatter Plot
# Generate data
np.random.seed(42)
x = np.random.randn(100)
y = 2 * x + np.random.randn(100)
plt.figure(figsize=(8, 6))
plt.scatter(x, y, alpha=0.6, s=50, c=y, cmap='viridis', edgecolors='black')
plt.colorbar(label='Y value')
plt.xlabel('X')
plt.ylabel('Y')
plt.title('Scatter Plot with Color Mapping')
plt.show()
Bar Plot
categories = ['A', 'B', 'C', 'D', 'E']
values = [23, 45, 56, 78, 32]
plt.figure(figsize=(10, 6))
bars = plt.bar(categories, values, color='skyblue', edgecolor='black')
# Add value labels on bars
for bar in bars:
height = bar.get_height()
plt.text(bar.get_x() + bar.get_width()/2., height,
f'{int(height)}',
ha='center', va='bottom')
plt.xlabel('Categories')
plt.ylabel('Values')
plt.title('Bar Chart Example')
plt.show()
Subplots
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
# Plot 1: Line
axes[0, 0].plot(x, np.sin(x))
axes[0, 0].set_title('Sine Wave')
# Plot 2: Scatter
axes[0, 1].scatter(x, y, alpha=0.5)
axes[0, 1].set_title('Scatter Plot')
# Plot 3: Histogram
axes[1, 0].hist(y, bins=20, color='green', alpha=0.7)
axes[1, 0].set_title('Histogram')
# Plot 4: Box plot
axes[1, 1].boxplot([x, y], labels=['X', 'Y'])
axes[1, 1].set_title('Box Plot')
plt.tight_layout()
plt.show()
Understanding Seaborn
Seaborn is built on Matplotlib, offering high-level interface and better defaults.
Installation and Setup
# pip install seaborn
import seaborn as sns
import matplotlib.pyplot as plt
# Set Seaborn style
sns.set_theme(style="darkgrid") # or "whitegrid", "dark", "white", "ticks"
sns.set_palette("husl") # Color palette
Basic Seaborn Plots
# Create sample data
tips = sns.load_dataset('tips')
# Scatter plot with regression line
plt.figure(figsize=(10, 6))
sns.scatterplot(data=tips, x='total_bill', y='tip', hue='time', size='size')
plt.title('Tips vs Total Bill')
plt.show()
# With regression line
plt.figure(figsize=(10, 6))
sns.regplot(data=tips, x='total_bill', y='tip')
plt.title('Tips vs Total Bill (with regression)')
plt.show()
Bar Plot
plt.figure(figsize=(10, 6))
sns.barplot(data=tips, x='day', y='total_bill', hue='sex', ci='sd')
plt.title('Average Bill by Day and Gender')
plt.show()
Box Plot and Violin Plot
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
# Box plot
sns.boxplot(data=tips, x='day', y='total_bill', hue='sex', ax=axes[0])
axes[0].set_title('Box Plot')
# Violin plot (shows distribution)
sns.violinplot(data=tips, x='day', y='total_bill', hue='sex', split=True, ax=axes[1])
axes[1].set_title('Violin Plot')
plt.tight_layout()
plt.show()
Head-to-Head Comparison
Creating the Same Plot: Both Libraries
# Sample data
df = pd.DataFrame({
'category': ['A', 'B', 'C', 'D', 'E'],
'values': [23, 45, 56, 78, 32]
})
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
# Matplotlib version
axes[0].bar(df['category'], df['values'], color='steelblue', edgecolor='black')
axes[0].set_xlabel('Category', fontsize=12)
axes[0].set_ylabel('Values', fontsize=12)
axes[0].set_title('Matplotlib Bar Chart', fontsize=14)
axes[0].grid(axis='y', alpha=0.3)
# Seaborn version
sns.barplot(data=df, x='category', y='values', ax=axes[1], color='steelblue')
axes[1].set_title('Seaborn Bar Chart', fontsize=14)
plt.tight_layout()
plt.show()
Distribution Plots Comparison
# Generate data
data = np.random.normal(100, 15, 1000)
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# Matplotlib histogram
axes[0, 0].hist(data, bins=30, color='skyblue', edgecolor='black', alpha=0.7)
axes[0, 0].set_title('Matplotlib Histogram')
axes[0, 0].set_xlabel('Value')
axes[0, 0].set_ylabel('Frequency')
# Seaborn histogram
sns.histplot(data, bins=30, kde=True, ax=axes[0, 1])
axes[0, 1].set_title('Seaborn Histogram with KDE')
# Matplotlib KDE (manual)
from scipy import stats
density = stats.gaussian_kde(data)
x_range = np.linspace(data.min(), data.max(), 100)
axes[1, 0].plot(x_range, density(x_range), linewidth=2)
axes[1, 0].fill_between(x_range, density(x_range), alpha=0.3)
axes[1, 0].set_title('Matplotlib KDE (Manual)')
# Seaborn KDE
sns.kdeplot(data, ax=axes[1, 1], fill=True)
axes[1, 1].set_title('Seaborn KDE (Built-in)')
plt.tight_layout()
plt.show()
Advanced Seaborn Features
Pair Plot (Correlation Matrix)
# Load iris dataset
iris = sns.load_dataset('iris')
# Create pair plot
sns.pairplot(iris, hue='species', diag_kind='kde', height=2.5)
plt.suptitle('Iris Dataset Pair Plot', y=1.02)
plt.show()
Heatmap
# Correlation heatmap
plt.figure(figsize=(10, 8))
correlation = iris.select_dtypes(include=[np.number]).corr()
sns.heatmap(correlation, annot=True, cmap='coolwarm', center=0,
square=True, linewidths=1, cbar_kws={"shrink": 0.8})
plt.title('Correlation Heatmap', fontsize=14)
plt.show()
Cat Plot (Categorical Plots)
# Flexible categorical plots
sns.catplot(data=tips, x='day', y='total_bill', hue='sex',
kind='box', height=6, aspect=1.5)
plt.title('Total Bill by Day and Gender')
plt.show()
# Different kinds: 'strip', 'swarm', 'box', 'violin', 'boxen', 'point', 'bar', 'count'
FacetGrid (Small Multiples)
# Create grid of plots
g = sns.FacetGrid(tips, col='time', row='sex', height=4)
g.map(sns.scatterplot, 'total_bill', 'tip')
g.add_legend()
plt.show()
Combining Matplotlib and Seaborn
The best approach: Use Seaborn for quick plots, Matplotlib for customization.
# Use Seaborn for plotting, Matplotlib for customization
fig, ax = plt.subplots(figsize=(12, 6))
# Seaborn plot
sns.violinplot(data=tips, x='day', y='total_bill', hue='sex', split=True, ax=ax)
# Matplotlib customization
ax.set_title('Tips Analysis', fontsize=16, fontweight='bold', pad=20)
ax.set_xlabel('Day of Week', fontsize=12, fontweight='bold')
ax.set_ylabel('Total Bill ($)', fontsize=12, fontweight='bold')
ax.grid(axis='y', alpha=0.3, linestyle='--')
# Add custom annotation
ax.annotate('Weekend peak', xy=(2, 40), xytext=(1, 45),
arrowprops=dict(arrowstyle='->', color='red', lw=2),
fontsize=12, color='red', fontweight='bold')
plt.tight_layout()
plt.show()
Styling and Themes
Matplotlib Styles
# Available styles
print(plt.style.available)
# Apply style
plt.style.use('ggplot') # or 'seaborn-v0_8', 'fivethirtyeight', etc.
# Create plot with style
plt.figure(figsize=(10, 6))
plt.plot(x, y)
plt.title('Styled Plot')
plt.show()
# Reset to default
plt.style.use('default')
Seaborn Contexts
# Set context (affects scale of elements)
sns.set_context("paper") # or "notebook", "talk", "poster"
# Set palette
sns.set_palette("Set2") # or "husl", "Paired", "deep", etc.
# Create plot
plt.figure(figsize=(10, 6))
sns.boxplot(data=tips, x='day', y='total_bill')
plt.title('Styled with Seaborn Context')
plt.show()
Custom Color Palettes
# Custom Seaborn palette
custom_palette = ["#FF6B6B", "#4ECDC4", "#45B7D1", "#FFA07A"]
sns.set_palette(custom_palette)
# Matplotlib custom colors
colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#FFA07A']
plt.bar(range(4), [1, 2, 3, 4], color=colors)
plt.show()
Creating Publication-Quality Figures
def create_publication_plot(data, output_file='figure.png'):
"""Create publication-ready plot"""
# Set style
sns.set_style("whitegrid")
sns.set_context("paper", font_scale=1.5)
# Create figure
fig, ax = plt.subplots(figsize=(8, 6), dpi=300)
# Plot
sns.scatterplot(data=data, x='x', y='y', hue='category',
s=100, alpha=0.7, ax=ax)
# Styling
ax.set_xlabel('X Variable', fontsize=14, fontweight='bold')
ax.set_ylabel('Y Variable', fontsize=14, fontweight='bold')
ax.set_title('Publication Title', fontsize=16, fontweight='bold', pad=20)
# Legend
ax.legend(title='Category', title_fontsize=12, fontsize=11,
frameon=True, shadow=True)
# Grid
ax.grid(True, alpha=0.3, linestyle='--')
# Tight layout
plt.tight_layout()
# Save (high DPI for publication)
plt.savefig(output_file, dpi=300, bbox_inches='tight',
facecolor='white', edgecolor='none')
plt.show()
print(f"Figure saved to {output_file}")
# Create sample data
data = pd.DataFrame({
'x': np.random.randn(100),
'y': np.random.randn(100),
'category': np.random.choice(['A', 'B', 'C'], 100)
})
create_publication_plot(data)
When to Use Which Library
Use Matplotlib When:
- Fine-grained control needed
- Custom annotations
- Complex layouts
- Specific styling requirements
- Creating custom visualizations
- New plot types
- Scientific visualizations
- Interactive plots
- 3D plots
from mpl_toolkits.mplot3d import Axes3D fig = plt.figure() ax = fig.add_subplot(111, projection='3d') ax.scatter(x, y, z)
Use Seaborn When:
- Statistical visualization
- Distribution plots
- Regression plots
- Categorical comparisons
- Quick exploratory analysis
- Pair plots
- Heatmaps
- FacetGrids
- Beautiful defaults needed
- Presentation plots
- Dashboard visualizations
- Reports
Complete Real-World Example
# Sales dashboard with both libraries
# Sample data
np.random.seed(42)
months = pd.date_range('2024-01', periods=12, freq='M')
sales_data = pd.DataFrame({
'month': months,
'product_A': np.random.randint(100, 200, 12),
'product_B': np.random.randint(80, 150, 12),
'product_C': np.random.randint(120, 180, 12),
'region': np.random.choice(['North', 'South'], 12)
})
# Create dashboard
fig = plt.figure(figsize=(16, 10))
gs = fig.add_gridspec(3, 2, hspace=0.3, wspace=0.3)
# 1. Time series (Matplotlib)
ax1 = fig.add_subplot(gs[0, :])
ax1.plot(sales_data['month'], sales_data['product_A'], marker='o', label='Product A')
ax1.plot(sales_data['month'], sales_data['product_B'], marker='s', label='Product B')
ax1.plot(sales_data['month'], sales_data['product_C'], marker='^', label='Product C')
ax1.set_title('Monthly Sales Trend', fontsize=14, fontweight='bold')
ax1.set_xlabel('Month')
ax1.set_ylabel('Sales')
ax1.legend()
ax1.grid(True, alpha=0.3)
# 2. Bar chart (Seaborn)
ax2 = fig.add_subplot(gs[1, 0])
total_sales = sales_data[['product_A', 'product_B', 'product_C']].sum()
sns.barplot(x=total_sales.index, y=total_sales.values, ax=ax2, palette='Set2')
ax2.set_title('Total Sales by Product', fontweight='bold')
ax2.set_ylabel('Total Sales')
# 3. Distribution (Seaborn)
ax3 = fig.add_subplot(gs[1, 1])
sales_melted = sales_data.melt(id_vars=['month', 'region'],
value_vars=['product_A', 'product_B', 'product_C'],
var_name='product', value_name='sales')
sns.boxplot(data=sales_melted, x='product', y='sales', ax=ax3, palette='Set3')
ax3.set_title('Sales Distribution', fontweight='bold')
ax3.set_xticklabels(['A', 'B', 'C'])
# 4. Heatmap (Seaborn)
ax4 = fig.add_subplot(gs[2, :])
correlation = sales_data[['product_A', 'product_B', 'product_C']].corr()
sns.heatmap(correlation, annot=True, cmap='coolwarm', center=0, ax=ax4,
square=True, linewidths=1)
ax4.set_title('Product Sales Correlation', fontweight='bold')
plt.suptitle('Sales Dashboard', fontsize=18, fontweight='bold', y=0.995)
plt.savefig('sales_dashboard.png', dpi=300, bbox_inches='tight')
plt.show()
Performance Considerations
When working with large datasets, performance matters.
Matplotlib Performance
# Rasterize complex plots for faster rendering
plt.plot(large_data_x, large_data_y, rasterized=True)
plt.savefig('plot.pdf', dpi=300) # Vector with rasterized data
# Use Agg backend for server-side rendering
import matplotlib
matplotlib.use('Agg') # Before importing pyplot
import matplotlib.pyplot as plt
Seaborn Performance
# Downsample for scatter plots
import numpy as np
# Sample large dataset
if len(data) > 10000:
sample_size = 10000
data_sample = data.sample(n=sample_size, random_state=42)
sns.scatterplot(data=data_sample, x='x', y='y')
else:
sns.scatterplot(data=data, x='x', y='y')
Common Mistakes and Solutions
Mistake 1: Not Closing Figures
# WRONG: Memory leak with many plots
for i in range(100):
plt.figure()
plt.plot(data[i])
plt.savefig(f'plot_{i}.png')
# RIGHT: Close figures
for i in range(100):
fig, ax = plt.subplots()
ax.plot(data[i])
plt.savefig(f'plot_{i}.png')
plt.close(fig) # Free memory
Mistake 2: Modifying Seaborn Plots Incorrectly
# WRONG: Direct modification
sns.lineplot(data=df, x='x', y='y')
plt.title('My Plot') # Works but not recommended
# RIGHT: Get axes object
ax = sns.lineplot(data=df, x='x', y='y')
ax.set_title('My Plot')
ax.set_xlabel('X Label')
Mistake 3: Inconsistent Styling
# WRONG: Mixing styles
sns.set_style('darkgrid')
plt.plot(x, y)
plt.style.use('seaborn-v0_8-bright') # Conflict!
# RIGHT: Choose one approach
# Option 1: Seaborn style
sns.set_theme(style='darkgrid', palette='muted')
sns.lineplot(x=x, y=y)
# Option 2: Matplotlib style
with plt.style.context('seaborn-v0_8-darkgrid'):
plt.plot(x, y)
Pro Tips for Better Visualizations
- Save Configuration: Create a custom style file for consistent branding
- Use Context Managers: Keep style changes local with
withstatements - Label Everything: Always add titles, axis labels, and legends
- Choose Appropriate DPI: 72 for screen, 300+ for print
- Test Colorblind-Friendly Palettes: Use
sns.color_palette("colorblind")
Your Visualization Toolkit
You now understand:
- Matplotlib - Maximum control, verbose
- Seaborn - Beautiful defaults, statistical focus
- When to use which - Context-dependent
- Combining both - Best of both worlds
- Styling - Professional appearance
- Publication quality - High-DPI outputs
Master both libraries, and you'll handle any visualization need!
When I Use Each Library
My Daily Matplotlib Uses:
- Quick exploratory plots during data analysis sessions
- Custom publication figures with specific size and DPI requirements
- Dashboards with multiple subplot layouts and precise positioning
- Annotations and arrows for explanatory diagrams
My Daily Seaborn Uses:
- Statistical distributions and relationships (violinplots, boxplots, pairplots)
- Categorical comparisons with automatic confidence intervals
- Heatmaps for correlation matrices and confusion matrices
- Quick beautiful plots for presentations and reports
When I Combine Both:
- Start with Seaborn for statistical plots and beautiful defaults
- Add Matplotlib customizations for annotations, layout adjustments, and fine-tuning
- Use Matplotlib's figure-level control with Seaborn's axes-level plotting
- Leverage Seaborn themes but customize with Matplotlib's detailed control
The key insight: they're not competing tools, they're complementary. Seaborn builds on Matplotlib, so learning both gives you the best of both worlds—statistical power with full customization control.
Next Steps
- Explore Matplotlib gallery
- Study Seaborn examples
- Learn Plotly for interactive plots
- Practice with real datasets
Remember: The best visualization is the one that clearly communicates your insight!
Found this comparison helpful? Share with your data viz community! Connect with me on Twitter or LinkedIn for more visualization tips.
Support My Work
If this guide helped you choose between Matplotlib and Seaborn, create beautiful visualizations, or understand when to use each library, I'd greatly appreciate your support, I'd really appreciate your support! Creating comprehensive, free content like this takes significant time and effort. Your support helps me continue sharing knowledge and creating more helpful resources for developers.
☕ Buy me a coffee - Every contribution, big or small, means the world to me and keeps me motivated to create more content!
Cover image by Jakub Żerdzicki on Unsplash