Topic 31 of

Matplotlib vs Seaborn — Which Visualization Library to Use?

Two libraries. Same goal — visualizations. But Matplotlib is like Photoshop, while Seaborn is like Instagram filters. Here's when to use each.

📚Beginner
⏱️9 min
10 quizzes
📊

Matplotlib vs Seaborn — Quick Overview

Python has two dominant visualization libraries for data analysis: Matplotlib and Seaborn. They're not competitors — Seaborn is built on top of Matplotlib. Here's the relationship:

Matplotlib is the low-level foundation:

  • Fine-grained control over every element (colors, fonts, axes, legends)
  • Verbose — requires more code for styled charts
  • Works with NumPy arrays and basic Python data structures
  • Best for custom visualizations and publication-ready figures

Seaborn is the high-level interface:

  • Beautiful defaults out of the box
  • Built for statistical visualizations (distributions, relationships, categories)
  • Designed for Pandas DataFrames
  • Less code for common chart types

The 80/20 Rule for Analysts

Use Seaborn for:

  • Exploratory data analysis (quick insights)
  • Statistical plots (distributions, correlations, regressions)
  • Multi-plot grids (faceted charts)
  • When you want charts that look good immediately

Use Matplotlib when:

  • You need pixel-perfect customization
  • You're creating charts Seaborn doesn't offer
  • You're fine-tuning a Seaborn plot (Seaborn returns Matplotlib objects)
  • You're building dashboards or custom visualizations
Think of it this way...

Matplotlib is a blank canvas with brushes. Seaborn is a paint-by-numbers kit with beautiful defaults. Most analysts start with Seaborn (fast, pretty), then customize with Matplotlib (control, precision).

📈

Creating Basic Charts — Side by Side

Let's compare the same chart in both libraries to see the difference in code and output.

Bar Chart: Revenue by City

Matplotlib:

code.pyPython
import matplotlib.pyplot as plt
import pandas as pd

# Sample data
cities = ['Mumbai', 'Delhi', 'Bangalore', 'Chennai']
revenue = [450000, 380000, 350000, 280000]

# Create chart
plt.figure(figsize=(10, 6))
plt.bar(cities, revenue, color='steelblue')
plt.xlabel('City', fontsize=12)
plt.ylabel('Revenue (₹)', fontsize=12)
plt.title('Revenue by City', fontsize=14, fontweight='bold')
plt.xticks(rotation=45)
plt.grid(axis='y', alpha=0.3)
plt.tight_layout()
plt.show()

Seaborn:

code.pyPython
import seaborn as sns
import pandas as pd

# Sample data
df = pd.DataFrame({
    'city': ['Mumbai', 'Delhi', 'Bangalore', 'Chennai'],
    'revenue': [450000, 380000, 350000, 280000]
})

# Create chart (fewer lines, beautiful by default)
sns.set_style('whitegrid')
plt.figure(figsize=(10, 6))
sns.barplot(data=df, x='city', y='revenue', palette='Blues_d')
plt.xlabel('City', fontsize=12)
plt.ylabel('Revenue (₹)', fontsize=12)
plt.title('Revenue by City', fontsize=14, fontweight='bold')
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()

Key Differences:

  • Seaborn requires DataFrame (more natural for analysis)
  • Seaborn's default styling is more polished
  • Matplotlib has slightly less code for simple bar charts
  • Both require similar customization for labels and titles

Line Chart: Daily Orders Over Time

Matplotlib:

code.pyPython
dates = pd.date_range('2026-03-01', periods=10)
orders = [120, 135, 128, 145, 150, 142, 138, 155, 160, 148]

plt.figure(figsize=(12, 6))
plt.plot(dates, orders, marker='o', linewidth=2, color='#2E86AB')
plt.xlabel('Date', fontsize=12)
plt.ylabel('Orders', fontsize=12)
plt.title('Daily Orders — March 2026', fontsize=14)
plt.xticks(rotation=45)
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

Seaborn:

code.pyPython
df = pd.DataFrame({'date': dates, 'orders': orders})

sns.set_style('darkgrid')
plt.figure(figsize=(12, 6))
sns.lineplot(data=df, x='date', y='orders', marker='o', linewidth=2)
plt.xlabel('Date', fontsize=12)
plt.ylabel('Orders', fontsize=12)
plt.title('Daily Orders — March 2026', fontsize=14)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()

Scatter Plot: Delivery Time vs Rating

Matplotlib:

code.pyPython
delivery_time = [25, 30, 35, 40, 45, 50, 55, 60]
rating = [4.8, 4.7, 4.5, 4.3, 4.0, 3.8, 3.5, 3.2]

plt.figure(figsize=(10, 6))
plt.scatter(delivery_time, rating, s=100, alpha=0.6, color='coral')
plt.xlabel('Delivery Time (minutes)', fontsize=12)
plt.ylabel('Customer Rating', fontsize=12)
plt.title('Delivery Time vs Customer Rating', fontsize=14)
plt.grid(True, alpha=0.3)
plt.show()

Seaborn (with regression line):

code.pyPython
df = pd.DataFrame({'delivery_time': delivery_time, 'rating': rating})

plt.figure(figsize=(10, 6))
sns.regplot(data=df, x='delivery_time', y='rating', scatter_kws={'s': 100, 'alpha': 0.6})
plt.xlabel('Delivery Time (minutes)', fontsize=12)
plt.ylabel('Customer Rating', fontsize=12)
plt.title('Delivery Time vs Customer Rating', fontsize=14)
plt.show()

Notice: Seaborn's regplot() automatically adds a regression line and confidence interval — showing the negative correlation without extra code.

⚠️ CheckpointQuiz error: Missing or invalid options array

📊

Where Seaborn Excels — Statistical Plots

Seaborn shines for statistical visualizations that would require significant code in pure Matplotlib.

Distribution Plots

code.pyPython
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt

# PhonePe transaction amounts
np.random.seed(42)
amounts = np.random.lognormal(7, 1.5, 10000)  # Right-skewed distribution

# Histogram with KDE (kernel density estimate)
plt.figure(figsize=(12, 5))

plt.subplot(1, 2, 1)
sns.histplot(amounts, bins=50, kde=True, color='skyblue')
plt.xlabel('Transaction Amount (₹)')
plt.title('Distribution of Transaction Amounts')

plt.subplot(1, 2, 2)
sns.boxplot(y=amounts, color='lightcoral')
plt.ylabel('Transaction Amount (₹)')
plt.title('Boxplot — Outliers and Quartiles')

plt.tight_layout()
plt.show()

Categorical Plots

code.pyPython
# Flipkart orders by category and city
df = pd.DataFrame({
    'city': ['Mumbai', 'Mumbai', 'Delhi', 'Delhi', 'Bangalore', 'Bangalore'] * 50,
    'category': ['Electronics', 'Fashion', 'Electronics', 'Fashion', 'Electronics', 'Fashion'] * 50,
    'orders': np.random.randint(50, 300, 300)
})

# Grouped bar chart with error bars
plt.figure(figsize=(10, 6))
sns.barplot(data=df, x='city', y='orders', hue='category', errorbar='sd', palette='Set2')
plt.title('Orders by City and Category')
plt.xlabel('City')
plt.ylabel('Average Orders')
plt.legend(title='Category')
plt.show()

Correlation Heatmap

code.pyPython
# Zomato restaurant metrics
df = pd.DataFrame({
    'avg_rating': np.random.uniform(3.5, 4.8, 100),
    'delivery_time': np.random.randint(20, 60, 100),
    'order_count': np.random.randint(50, 500, 100),
    'price_range': np.random.randint(1, 5, 100)
})

# Correlation matrix
corr_matrix = df.corr()

plt.figure(figsize=(8, 6))
sns.heatmap(corr_matrix, annot=True, cmap='coolwarm', center=0, fmt='.2f', linewidths=1)
plt.title('Correlation Matrix — Restaurant Metrics')
plt.show()

Pair Plot (Multi-Variable Relationships)

code.pyPython
# Visualize all pairwise relationships
sns.pairplot(df, diag_kind='kde', corner=True)
plt.suptitle('Pairwise Relationships — Zomato Metrics', y=1.01)
plt.show()

Why Seaborn Wins Here: These plots require 50+ lines in Matplotlib. Seaborn does them in 1-2 lines with beautiful styling.

🎨

Where Matplotlib Excels — Customization

Matplotlib gives you pixel-level control over every element. Use it when Seaborn's defaults aren't enough.

Multiple Subplots with Custom Layouts

code.pyPython
# Dashboard-style layout
fig, axes = plt.subplots(2, 2, figsize=(14, 10))

# Top-left: Line chart
axes[0, 0].plot(dates, orders, color='#2E86AB', linewidth=2)
axes[0, 0].set_title('Daily Orders', fontweight='bold')
axes[0, 0].grid(True, alpha=0.3)

# Top-right: Bar chart
axes[0, 1].bar(cities, revenue, color=['#FF6B6B', '#4ECDC4', '#45B7D1', '#FFA07A'])
axes[0, 1].set_title('Revenue by City', fontweight='bold')

# Bottom-left: Scatter
axes[1, 0].scatter(delivery_time, rating, s=100, alpha=0.6, c=delivery_time, cmap='RdYlGn_r')
axes[1, 0].set_title('Delivery Time vs Rating', fontweight='bold')

# Bottom-right: Histogram
axes[1, 1].hist(amounts[:1000], bins=30, color='mediumpurple', alpha=0.7)
axes[1, 1].set_title('Transaction Distribution', fontweight='bold')

plt.tight_layout()
plt.show()

Annotations and Custom Text

code.pyPython
plt.figure(figsize=(12, 6))
plt.plot(dates, orders, marker='o', linewidth=2, color='#2E86AB')

# Annotate the highest point
max_idx = orders.index(max(orders))
plt.annotate(f'Peak: {max(orders)} orders',
             xy=(dates[max_idx], orders[max_idx]),
             xytext=(dates[max_idx] + pd.Timedelta(days=1), orders[max_idx] + 5),
             arrowprops=dict(arrowstyle='->', color='red', lw=2),
             fontsize=11, color='red', fontweight='bold')

plt.xlabel('Date')
plt.ylabel('Orders')
plt.title('Daily Orders with Peak Annotation')
plt.grid(True, alpha=0.3)
plt.show()

Dual Y-Axis (Two Metrics, One Chart)

code.pyPython
# Orders and revenue on the same chart
fig, ax1 = plt.subplots(figsize=(12, 6))

# First y-axis: Orders
ax1.plot(dates, orders, color='#2E86AB', marker='o', linewidth=2, label='Orders')
ax1.set_xlabel('Date', fontsize=12)
ax1.set_ylabel('Orders', color='#2E86AB', fontsize=12)
ax1.tick_params(axis='y', labelcolor='#2E86AB')

# Second y-axis: Revenue
ax2 = ax1.twinx()
revenue_daily = [o * 350 for o in orders]  # Assume ₹350 per order
ax2.plot(dates, revenue_daily, color='#FF6B6B', marker='s', linewidth=2, label='Revenue')
ax2.set_ylabel('Revenue (₹)', color='#FF6B6B', fontsize=12)
ax2.tick_params(axis='y', labelcolor='#FF6B6B')

plt.title('Daily Orders and Revenue', fontsize=14, fontweight='bold')
fig.tight_layout()
plt.show()
Info

Pro Tip: Start with Seaborn for quick charts. When you need to customize, use plt.gca() to access the underlying Matplotlib axes object and apply custom styling.

🤝

Combining Both — Best of Both Worlds

You don't have to choose one or the other. Use Seaborn for the chart, Matplotlib for customization.

Example: Seaborn Chart + Matplotlib Customization

code.pyPython
# Create a Seaborn chart
df = pd.DataFrame({
    'city': ['Mumbai', 'Delhi', 'Bangalore', 'Chennai'] * 3,
    'month': ['Jan', 'Jan', 'Jan', 'Jan', 'Feb', 'Feb', 'Feb', 'Feb', 'Mar', 'Mar', 'Mar', 'Mar'],
    'revenue': [45000, 38000, 35000, 28000, 48000, 40000, 37000, 30000, 52000, 43000, 39000, 32000]
})

plt.figure(figsize=(12, 6))

# Seaborn does the heavy lifting
sns.barplot(data=df, x='city', y='revenue', hue='month', palette='viridis')

# Matplotlib adds custom touches
plt.title('Q1 2026 Revenue by City and Month', fontsize=16, fontweight='bold', pad=20)
plt.xlabel('City', fontsize=13)
plt.ylabel('Revenue (₹)', fontsize=13)
plt.legend(title='Month', fontsize=11, title_fontsize=12, loc='upper right')
plt.axhline(40000, color='red', linestyle='--', linewidth=1.5, alpha=0.7, label='Target: ₹40K')
plt.text(3.5, 41000, 'Target', color='red', fontsize=10, fontweight='bold')

plt.tight_layout()
plt.show()

Setting Global Styles

code.pyPython
# Set Seaborn style globally
sns.set_style('whitegrid')
sns.set_palette('husl')
sns.set_context('talk')  # Larger fonts for presentations

# Now all plots (Seaborn or Matplotlib) use these defaults

Available Styles:

  • darkgrid — dark background, white grid
  • whitegrid — white background, gray grid
  • dark — dark background, no grid
  • white — white background, no grid
  • ticks — white background with ticks

Contexts (font sizes):

  • paper — smallest (for papers)
  • notebook — default
  • talk — larger (for presentations)
  • poster — largest (for posters)

⚠️ FinalQuiz error: Missing or invalid questions array

⚠️ SummarySection error: Missing or invalid items array

Received: {"hasItems":false,"isArray":false}