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
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:
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:
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:
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:
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:
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):
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
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
# 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
# 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)
# 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
# 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
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)
# 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()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
# 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
# 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 defaultsAvailable Styles:
darkgrid— dark background, white gridwhitegrid— white background, gray griddark— dark background, no gridwhite— white background, no gridticks— white background with ticks
Contexts (font sizes):
paper— smallest (for papers)notebook— defaulttalk— 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}