Prerequisites
- Basic understanding of programming concepts π
- Python installation (3.8+) π
- VS Code or preferred IDE π»
What you'll learn
- Understand the concept fundamentals π―
- Apply the concept in real projects ποΈ
- Debug common issues π
- Write clean, Pythonic code β¨
π― Introduction
Welcome to this exciting tutorial on Matplotlib Advanced: Subplots and Styling! π In this guide, weβll explore how to create professional, publication-ready visualizations that tell compelling data stories.
Youβll discover how advanced subplot layouts and custom styling can transform your data visualizations from basic charts to stunning insights. Whether youβre building dashboards π, scientific reports π, or interactive presentations π, mastering these techniques is essential for effective data communication.
By the end of this tutorial, youβll feel confident creating complex multi-panel figures with custom themes! Letβs dive in! πββοΈ
π Understanding Subplots and Styling
π€ What are Subplots?
Subplots are like apartment buildings for your plots π’. Think of it as dividing your canvas into multiple rooms, where each room can display its own visualization that tells part of your data story.
In Matplotlib terms, subplots allow you to create multiple axes within a single figure. This means you can:
- β¨ Compare different datasets side by side
- π Show multiple perspectives of the same data
- π‘οΈ Create dashboard-style visualizations
π‘ Why Master Advanced Subplots?
Hereβs why data scientists love advanced subplot techniques:
- Efficient Space Usage π: Pack more insights into less space
- Better Comparisons π»: Easy side-by-side analysis
- Professional Layouts π: Publication-ready figures
- Storytelling Power π§: Guide viewers through your data narrative
Real-world example: Imagine building a weather dashboard π€οΈ. With subplots, you can show temperature trends, precipitation, humidity, and wind patterns all in one cohesive view!
π§ Basic Syntax and Usage
π Simple Subplot Creation
Letβs start with a friendly example:
import matplotlib.pyplot as plt
import numpy as np
# π Hello, Subplots!
fig, axes = plt.subplots(2, 2, figsize=(10, 8))
# π¨ Generate some sample data
x = np.linspace(0, 10, 100)
# π Plot in each subplot
axes[0, 0].plot(x, np.sin(x))
axes[0, 0].set_title('Sine Wave π')
axes[0, 1].plot(x, np.cos(x), 'r-')
axes[0, 1].set_title('Cosine Wave π΄')
axes[1, 0].plot(x, x**2, 'g-')
axes[1, 0].set_title('Quadratic π')
axes[1, 1].plot(x, np.sqrt(x), 'm-')
axes[1, 1].set_title('Square Root πΏ')
plt.tight_layout()
plt.show()
π‘ Explanation: Notice how we use subplots(2, 2)
to create a 2x2 grid! The tight_layout()
automatically adjusts spacing for a clean look.
π― Common Subplot Patterns
Here are patterns youβll use daily:
# ποΈ Pattern 1: Uneven layouts with GridSpec
from matplotlib.gridspec import GridSpec
fig = plt.figure(figsize=(12, 8))
gs = GridSpec(3, 3, figure=fig)
# π¨ Create subplots of different sizes
ax1 = fig.add_subplot(gs[0, :]) # Top row, all columns
ax2 = fig.add_subplot(gs[1:, 0]) # Bottom 2 rows, first column
ax3 = fig.add_subplot(gs[1:, 1:]) # Bottom 2 rows, last 2 columns
# π Pattern 2: Sharing axes
fig, axes = plt.subplots(2, 2, sharex=True, sharey=True)
# π― Pattern 3: Subplot with different scales
fig, (ax1, ax2) = plt.subplots(1, 2)
ax2_twin = ax2.twinx() # Create second y-axis
π‘ Practical Examples
π Example 1: Stock Market Dashboard
Letβs build something real:
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from datetime import datetime, timedelta
# π Generate sample stock data
dates = pd.date_range(start='2024-01-01', periods=100, freq='D')
stock_data = {
'AAPL': 150 + np.cumsum(np.random.randn(100) * 2),
'GOOGL': 100 + np.cumsum(np.random.randn(100) * 1.5),
'MSFT': 300 + np.cumsum(np.random.randn(100) * 2.5),
'Volume': np.random.randint(1000000, 5000000, 100)
}
# π¨ Create custom style
plt.style.use('seaborn-v0_8-darkgrid')
# ποΈ Create dashboard layout
fig = plt.figure(figsize=(15, 10))
gs = GridSpec(3, 2, figure=fig, height_ratios=[2, 1, 1])
# π Main price chart
ax_main = fig.add_subplot(gs[0, :])
for stock, prices in stock_data.items():
if stock != 'Volume':
ax_main.plot(dates, prices, label=f'{stock} π', linewidth=2)
ax_main.set_title('Stock Price Trends πΉ', fontsize=16, fontweight='bold')
ax_main.set_xlabel('Date')
ax_main.set_ylabel('Price ($)')
ax_main.legend(loc='upper left')
ax_main.grid(True, alpha=0.3)
# π Volume chart
ax_volume = fig.add_subplot(gs[1, :])
ax_volume.bar(dates, stock_data['Volume'], color='skyblue', alpha=0.7)
ax_volume.set_title('Trading Volume π', fontsize=14)
ax_volume.set_ylabel('Volume')
# π Individual performance
ax_perf1 = fig.add_subplot(gs[2, 0])
returns = [(stock_data['AAPL'][-1] - stock_data['AAPL'][0]) / stock_data['AAPL'][0] * 100,
(stock_data['GOOGL'][-1] - stock_data['GOOGL'][0]) / stock_data['GOOGL'][0] * 100,
(stock_data['MSFT'][-1] - stock_data['MSFT'][0]) / stock_data['MSFT'][0] * 100]
colors = ['green' if r > 0 else 'red' for r in returns]
ax_perf1.bar(['AAPL', 'GOOGL', 'MSFT'], returns, color=colors)
ax_perf1.set_title('Returns % π°', fontsize=14)
ax_perf1.axhline(y=0, color='black', linestyle='-', linewidth=0.5)
# π Correlation heatmap
ax_corr = fig.add_subplot(gs[2, 1])
corr_data = np.random.rand(3, 3)
im = ax_corr.imshow(corr_data, cmap='RdYlGn', aspect='auto')
ax_corr.set_xticks([0, 1, 2])
ax_corr.set_yticks([0, 1, 2])
ax_corr.set_xticklabels(['AAPL', 'GOOGL', 'MSFT'])
ax_corr.set_yticklabels(['AAPL', 'GOOGL', 'MSFT'])
ax_corr.set_title('Correlation Matrix π', fontsize=14)
plt.colorbar(im, ax=ax_corr)
plt.suptitle('Stock Market Dashboard π', fontsize=20, fontweight='bold')
plt.tight_layout()
plt.show()
π― Try it yourself: Add a moving average indicator and volatility chart!
π‘οΈ Example 2: Weather Analysis Dashboard
Letβs make it fun:
# π€οΈ Weather monitoring system
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import Circle
from matplotlib.collections import PatchCollection
# π¨ Custom color scheme
plt.rcParams['figure.facecolor'] = '#f0f0f0'
plt.rcParams['axes.facecolor'] = 'white'
plt.rcParams['axes.edgecolor'] = '#cccccc'
plt.rcParams['grid.color'] = '#e0e0e0'
# π Generate weather data
hours = np.arange(24)
temp = 20 + 10 * np.sin((hours - 6) * np.pi / 12) + np.random.randn(24)
humidity = 60 + 20 * np.sin((hours + 3) * np.pi / 12) + np.random.randn(24) * 5
wind_speed = 5 + 3 * np.sin(hours * np.pi / 6) + np.random.randn(24)
# ποΈ Create dashboard
fig = plt.figure(figsize=(16, 10))
gs = GridSpec(3, 3, figure=fig)
# π‘οΈ Temperature gauge (main display)
ax_temp = fig.add_subplot(gs[:2, 0])
ax_temp.plot(hours, temp, 'r-', linewidth=3, label='Temperature')
ax_temp.fill_between(hours, temp, alpha=0.3, color='red')
ax_temp.set_title('Temperature Throughout the Day π‘οΈ', fontsize=16, pad=20)
ax_temp.set_xlabel('Hour of Day')
ax_temp.set_ylabel('Temperature (Β°C)')
ax_temp.grid(True, alpha=0.3)
# Add temperature zones
ax_temp.axhspan(30, 40, alpha=0.2, color='red', label='Hot π₯')
ax_temp.axhspan(20, 30, alpha=0.2, color='orange', label='Warm βοΈ')
ax_temp.axhspan(10, 20, alpha=0.2, color='green', label='Comfortable π')
# π§ Humidity chart
ax_humid = fig.add_subplot(gs[0, 1:])
ax_humid.plot(hours, humidity, 'b-', linewidth=2, marker='o', markersize=4)
ax_humid.set_title('Humidity Levels π§', fontsize=14)
ax_humid.set_ylabel('Humidity (%)')
ax_humid.set_ylim(0, 100)
ax_humid.grid(True, alpha=0.3)
# π¨ Wind speed with direction
ax_wind = fig.add_subplot(gs[1, 1:])
ax_wind.barh(hours[::2], wind_speed[::2], color='skyblue', alpha=0.7)
ax_wind.set_title('Wind Speed π¨', fontsize=14)
ax_wind.set_xlabel('Speed (km/h)')
ax_wind.set_ylabel('Hour')
# βοΈ UV Index circular plot
ax_uv = fig.add_subplot(gs[2, 0], projection='polar')
theta = np.linspace(0, 2*np.pi, 24)
uv_index = 5 + 3 * np.sin(theta) + np.random.rand(24)
ax_uv.plot(theta, uv_index, 'orange', linewidth=2)
ax_uv.fill(theta, uv_index, alpha=0.3, color='orange')
ax_uv.set_title('UV Index βοΈ', pad=20)
ax_uv.set_ylim(0, 11)
# π Weather summary
ax_summary = fig.add_subplot(gs[2, 1:])
ax_summary.axis('off')
summary_text = f"""
π‘οΈ Max Temp: {temp.max():.1f}Β°C
π‘οΈ Min Temp: {temp.min():.1f}Β°C
π§ Avg Humidity: {humidity.mean():.1f}%
π¨ Max Wind: {wind_speed.max():.1f} km/h
βοΈ Peak UV: {uv_index.max():.1f}
Weather Status: {"π Sunny" if temp.mean() > 25 else "βοΈ Cloudy"}
"""
ax_summary.text(0.1, 0.5, summary_text, fontsize=14,
transform=ax_summary.transAxes, verticalalignment='center',
bbox=dict(boxstyle="round,pad=0.5", facecolor="lightblue", alpha=0.5))
plt.suptitle('Weather Monitoring Dashboard π€οΈ', fontsize=20, fontweight='bold')
plt.tight_layout()
plt.show()
π Advanced Concepts
π§ββοΈ Advanced Styling with Custom Themes
When youβre ready to level up, try creating professional themes:
# π― Create a custom publication-ready style
def create_publication_style():
plt.rcParams.update({
# π¨ Figure settings
'figure.figsize': (10, 6),
'figure.dpi': 300,
'savefig.dpi': 300,
'savefig.bbox': 'tight',
# π Font settings
'font.family': 'sans-serif',
'font.sans-serif': ['Arial', 'DejaVu Sans'],
'font.size': 12,
'axes.titlesize': 16,
'axes.labelsize': 14,
'xtick.labelsize': 12,
'ytick.labelsize': 12,
'legend.fontsize': 12,
# π¨ Color settings
'axes.prop_cycle': plt.cycler('color',
['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728',
'#9467bd', '#8c564b', '#e377c2', '#7f7f7f']),
# π Grid and spines
'axes.grid': True,
'grid.alpha': 0.3,
'axes.spines.top': False,
'axes.spines.right': False,
'axes.linewidth': 1.5,
# β¨ Special effects
'figure.facecolor': 'white',
'axes.facecolor': 'white',
'axes.edgecolor': '#333333',
})
# πͺ Apply the custom style
create_publication_style()
# π― Demo with styled plot
fig, ax = plt.subplots()
x = np.linspace(0, 10, 100)
for i in range(4):
ax.plot(x, np.sin(x + i), label=f'Wave {i+1} π', linewidth=2.5)
ax.set_title('Professional Publication Style π')
ax.set_xlabel('Time (s)')
ax.set_ylabel('Amplitude')
ax.legend(frameon=True, shadow=True)
plt.show()
ποΈ Complex Layouts with Nested Subplots
For the brave developers:
# π Advanced nested subplot layout
fig = plt.figure(figsize=(16, 12))
# π Create main grid
outer_grid = GridSpec(2, 2, figure=fig, hspace=0.3, wspace=0.3)
# π― Top-left: Multiple time series
inner_grid1 = GridSpec(3, 1, figure=fig,
left=0.05, right=0.45, top=0.95, bottom=0.55)
for i in range(3):
ax = fig.add_subplot(inner_grid1[i, 0])
ax.plot(np.random.randn(100).cumsum())
ax.set_title(f'Signal {i+1} π‘')
# π Top-right: Heatmap
ax_heat = fig.add_subplot(outer_grid[0, 1])
data = np.random.randn(20, 20)
im = ax_heat.imshow(data, cmap='viridis', aspect='auto')
ax_heat.set_title('Correlation Heatmap π₯')
plt.colorbar(im, ax=ax_heat)
# π Bottom: Large comparison chart
ax_bottom = fig.add_subplot(outer_grid[1, :])
categories = ['Alpha', 'Beta', 'Gamma', 'Delta', 'Epsilon']
values1 = np.random.rand(5) * 100
values2 = np.random.rand(5) * 100
x = np.arange(len(categories))
width = 0.35
bars1 = ax_bottom.bar(x - width/2, values1, width, label='Group A π
°οΈ')
bars2 = ax_bottom.bar(x + width/2, values2, width, label='Group B π
±οΈ')
ax_bottom.set_xlabel('Categories')
ax_bottom.set_ylabel('Values')
ax_bottom.set_title('Comparative Analysis π')
ax_bottom.set_xticks(x)
ax_bottom.set_xticklabels(categories)
ax_bottom.legend()
plt.suptitle('Advanced Nested Layout Demo π¨', fontsize=20)
plt.show()
β οΈ Common Pitfalls and Solutions
π± Pitfall 1: Overlapping Subplots
# β Wrong way - subplots overlap!
fig, axes = plt.subplots(2, 2, figsize=(8, 6))
for ax in axes.flat:
ax.plot(np.random.randn(100))
ax.set_title('This title might overlap! π°')
plt.show()
# β
Correct way - use tight_layout!
fig, axes = plt.subplots(2, 2, figsize=(8, 6))
for ax in axes.flat:
ax.plot(np.random.randn(100))
ax.set_title('Clean layout! π―')
plt.tight_layout() # Magic spacing fix! β¨
plt.show()
π€― Pitfall 2: Inconsistent Scales
# β Dangerous - different scales make comparison hard!
fig, (ax1, ax2) = plt.subplots(1, 2)
ax1.plot([1, 2, 3], [10, 20, 30])
ax2.plot([1, 2, 3], [1000, 2000, 3000])
# β
Safe - share y-axis for fair comparison!
fig, (ax1, ax2) = plt.subplots(1, 2, sharey=True)
ax1.plot([1, 2, 3], [10, 20, 30])
ax2.plot([1, 2, 3], [15, 25, 35])
plt.show()
π οΈ Best Practices
- π― Plan Your Layout: Sketch before coding - know your story!
- π Consistent Styling: Use the same colors and fonts throughout
- π‘οΈ White Space: Donβt overcrowd - let your plots breathe
- π¨ Color Wisely: Consider colorblind-friendly palettes
- β¨ Label Everything: Axes, titles, and legends are essential
π§ͺ Hands-On Exercise
π― Challenge: Build a Data Science Portfolio Dashboard
Create a comprehensive dashboard showing:
π Requirements:
- β Model performance metrics (accuracy, precision, recall)
- π·οΈ Feature importance visualization
- π€ Confusion matrix heatmap
- π Training history over time
- π¨ Custom color scheme matching your style!
π Bonus Points:
- Add interactive elements
- Implement responsive layout
- Create reusable plotting functions
π‘ Solution
π Click to see solution
# π― Data Science Portfolio Dashboard
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.gridspec import GridSpec
import seaborn as sns
# π¨ Set custom style
plt.style.use('seaborn-v0_8-whitegrid')
custom_colors = ['#3498db', '#e74c3c', '#2ecc71', '#f39c12', '#9b59b6']
plt.rcParams['axes.prop_cycle'] = plt.cycler(color=custom_colors)
# π Generate sample ML metrics
epochs = np.arange(1, 51)
train_loss = 1.0 / (1 + 0.1 * epochs) + 0.05 * np.random.randn(50)
val_loss = train_loss + 0.1 + 0.05 * np.random.randn(50)
accuracy = 1 - train_loss + 0.1 * np.random.randn(50)
# ποΈ Create dashboard
fig = plt.figure(figsize=(18, 12))
gs = GridSpec(3, 3, figure=fig, hspace=0.3, wspace=0.3)
# π Training history
ax_history = fig.add_subplot(gs[0, :2])
ax_history.plot(epochs, train_loss, label='Training Loss π', linewidth=2)
ax_history.plot(epochs, val_loss, label='Validation Loss π', linewidth=2)
ax_history.plot(epochs, accuracy, label='Accuracy π―', linewidth=2)
ax_history.set_title('Model Training History π', fontsize=16, fontweight='bold')
ax_history.set_xlabel('Epoch')
ax_history.set_ylabel('Metric Value')
ax_history.legend()
ax_history.grid(True, alpha=0.3)
# π Performance metrics
ax_metrics = fig.add_subplot(gs[0, 2])
metrics = ['Accuracy', 'Precision', 'Recall', 'F1-Score']
values = [0.92, 0.89, 0.94, 0.91]
colors_metrics = ['#3498db', '#2ecc71', '#f39c12', '#e74c3c']
bars = ax_metrics.bar(metrics, values, color=colors_metrics)
ax_metrics.set_title('Model Performance π', fontsize=14)
ax_metrics.set_ylim(0, 1)
ax_metrics.set_ylabel('Score')
# Add value labels on bars
for bar, value in zip(bars, values):
height = bar.get_height()
ax_metrics.text(bar.get_x() + bar.get_width()/2., height + 0.02,
f'{value:.2f}', ha='center', va='bottom')
# π₯ Confusion matrix
ax_conf = fig.add_subplot(gs[1, :2])
conf_matrix = np.array([[85, 5, 2], [3, 92, 5], [1, 4, 88]])
sns.heatmap(conf_matrix, annot=True, fmt='d', cmap='Blues',
cbar=True, ax=ax_conf)
ax_conf.set_title('Confusion Matrix π²', fontsize=14)
ax_conf.set_xlabel('Predicted')
ax_conf.set_ylabel('Actual')
# π Feature importance
ax_feat = fig.add_subplot(gs[1, 2])
features = ['Feature A', 'Feature B', 'Feature C', 'Feature D', 'Feature E']
importance = [0.25, 0.20, 0.18, 0.15, 0.12]
ax_feat.barh(features, importance, color='#9b59b6')
ax_feat.set_title('Feature Importance π', fontsize=14)
ax_feat.set_xlabel('Importance Score')
# π ROC curve
ax_roc = fig.add_subplot(gs[2, 0])
fpr = np.linspace(0, 1, 100)
tpr = np.sqrt(fpr) + 0.1 * np.random.randn(100)
tpr = np.clip(tpr, 0, 1)
ax_roc.plot(fpr, tpr, 'b-', linewidth=2, label='Model (AUC=0.89)')
ax_roc.plot([0, 1], [0, 1], 'k--', alpha=0.5, label='Random')
ax_roc.set_title('ROC Curve π', fontsize=14)
ax_roc.set_xlabel('False Positive Rate')
ax_roc.set_ylabel('True Positive Rate')
ax_roc.legend()
ax_roc.grid(True, alpha=0.3)
# π― Class distribution
ax_dist = fig.add_subplot(gs[2, 1])
classes = ['Class A', 'Class B', 'Class C']
counts = [150, 180, 120]
wedges, texts, autotexts = ax_dist.pie(counts, labels=classes,
autopct='%1.1f%%', startangle=90)
ax_dist.set_title('Dataset Distribution π₯§', fontsize=14)
# π Model summary
ax_summary = fig.add_subplot(gs[2, 2])
ax_summary.axis('off')
summary_text = """
π€ Model: RandomForest
π Dataset: 10,000 samples
π― Task: Multi-class Classification
β±οΈ Training Time: 45.3 minutes
πΎ Model Size: 12.5 MB
Status: β
Production Ready!
"""
ax_summary.text(0.1, 0.5, summary_text, fontsize=12,
transform=ax_summary.transAxes, verticalalignment='center',
bbox=dict(boxstyle="round,pad=0.5", facecolor="#ecf0f1"))
plt.suptitle('Machine Learning Model Dashboard π', fontsize=24, fontweight='bold')
plt.tight_layout()
plt.show()
π Key Takeaways
Youβve learned so much! Hereβs what you can now do:
- β Create complex subplot layouts with confidence πͺ
- β Apply professional styling to your visualizations π‘οΈ
- β Build dashboard-style figures for real projects π―
- β Debug layout issues like a pro π
- β Design beautiful data stories with Matplotlib! π
Remember: Great visualizations are about clarity, not complexity! Keep your audience in mind. π€
π€ Next Steps
Congratulations! π Youβve mastered advanced subplots and styling!
Hereβs what to do next:
- π» Practice with the dashboard exercise above
- ποΈ Build a visualization portfolio with your own data
- π Move on to our next tutorial: Seaborn for Statistical Plots
- π Share your beautiful visualizations with the data science community!
Remember: Every data visualization expert was once a beginner. Keep plotting, keep styling, and most importantly, have fun telling data stories! π
Happy plotting! ππβ¨