Introduction to Matplotlib

By: MIN Sothearith

Data Visualization in Python

What is Matplotlib?

Matplotlib is the foundational plotting library for Python.

  • Created in 2003 by John Hunter
  • Inspired by MATLAB plotting
  • Most widely used visualization library
  • Foundation for many other plotting libraries (Seaborn, Pandas plotting)

Why Learn Matplotlib?

Matplotlib gives you complete control over every aspect of your visualizations - essential for publication-quality figures and custom visualizations.

Installation & Setup

Install Matplotlib via pip:

pip install matplotlib numpy pandas

Standard import convention:

Show code
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd 

print(f"Matplotlib version: {plt.matplotlib.__version__}")
Matplotlib version: 3.10.3

Note

matplotlib.pyplot is imported as plt (standard convention)

Anatomy of a Figure

Show anatomy diagram
import matplotlib.pyplot as plt
import numpy as np

from matplotlib.patches import Circle
from matplotlib.patheffects import withStroke
from matplotlib.ticker import AutoMinorLocator, MultipleLocator

royal_blue = [0, 20/256, 82/256]


# make the figure

np.random.seed(19680801)

X = np.linspace(0.5, 3.5, 100)
Y1 = 3+np.cos(X)
Y2 = 1+np.cos(1+X/0.75)/2
Y3 = np.random.uniform(Y1, Y2, len(X))

fig = plt.figure(figsize=(7.5, 7.5))
ax = fig.add_axes([0.2, 0.17, 0.68, 0.7], aspect=1)

ax.xaxis.set_major_locator(MultipleLocator(1.000))
ax.xaxis.set_minor_locator(AutoMinorLocator(4))
ax.yaxis.set_major_locator(MultipleLocator(1.000))
ax.yaxis.set_minor_locator(AutoMinorLocator(4))
ax.xaxis.set_minor_formatter("{x:.2f}")

ax.set_xlim(0, 4)
ax.set_ylim(0, 4)

ax.tick_params(which='major', width=1.0, length=10, labelsize=14)
ax.tick_params(which='minor', width=1.0, length=5, labelsize=10,
               labelcolor='0.25')

ax.grid(linestyle="--", linewidth=0.5, color='.25', zorder=-10)

ax.plot(X, Y1, c='C0', lw=2.5, label="Blue signal", zorder=10)
ax.plot(X, Y2, c='C1', lw=2.5, label="Orange signal")
ax.plot(X[::3], Y3[::3], linewidth=0, markersize=9,
        marker='s', markerfacecolor='none', markeredgecolor='C4',
        markeredgewidth=2.5)

ax.set_title("Anatomy of a figure", fontsize=20, verticalalignment='bottom')
ax.set_xlabel("x Axis label", fontsize=14)
ax.set_ylabel("y Axis label", fontsize=14)
ax.legend(loc="upper right", fontsize=14)


# Annotate the figure

def annotate(x, y, text, code):
    # Circle marker
    c = Circle((x, y), radius=0.15, clip_on=False, zorder=10, linewidth=2.5,
               edgecolor=royal_blue + [0.6], facecolor='none',
               path_effects=[withStroke(linewidth=7, foreground='white')])
    ax.add_artist(c)

    # use path_effects as a background for the texts
    # draw the path_effects and the colored text separately so that the
    # path_effects cannot clip other texts
    for path_effects in [[withStroke(linewidth=7, foreground='white')], []]:
        color = 'white' if path_effects else royal_blue
        ax.text(x, y-0.2, text, zorder=100,
                ha='center', va='top', weight='bold', color=color,
                style='italic', fontfamily='monospace',
                path_effects=path_effects)

        color = 'white' if path_effects else 'black'
        ax.text(x, y-0.33, code, zorder=100,
                ha='center', va='top', weight='normal', color=color,
                fontfamily='monospace', fontsize='medium',
                path_effects=path_effects)


annotate(3.5, -0.13, "Minor tick label", "ax.xaxis.set_minor_formatter")
annotate(-0.03, 1.0, "Major tick", "ax.yaxis.set_major_locator")
annotate(0.00, 3.75, "Minor tick", "ax.yaxis.set_minor_locator")
annotate(-0.15, 3.00, "Major tick label", "ax.yaxis.set_major_formatter")
annotate(1.68, -0.39, "xlabel", "ax.set_xlabel")
annotate(-0.38, 1.67, "ylabel", "ax.set_ylabel")
annotate(1.52, 4.15, "Title", "ax.set_title")
annotate(1.75, 2.80, "Line", "ax.plot")
annotate(2.25, 1.54, "Markers", "ax.scatter")
annotate(3.00, 3.00, "Grid", "ax.grid")
annotate(3.60, 3.58, "Legend", "ax.legend")
annotate(2.5, 0.55, "Axes", "fig.subplots")
annotate(4, 4.5, "Figure", "plt.figure")
annotate(0.65, 0.01, "x Axis", "ax.xaxis")
annotate(0, 0.36, "y Axis", "ax.yaxis")
annotate(4.0, 0.7, "Spine", "ax.spines")

# frame around figure
fig.patch.set(linewidth=4, edgecolor='0.5')
plt.show()

Understanding this hierarchy is crucial for effective plotting.

Two Interfaces: OO vs pyplot

Object-Oriented (Recommended)

fig, ax = plt.subplots()
ax.plot(x, y)

pyplot (MATLAB-style)

plt.plot(x, y)

We’ll focus on Object-Oriented because it’s: - More explicit and clear - Better for complex figures - Industry standard

Your First Plot

Creating a simple line plot:

Show simple plot
x = [1, 2, 3, 4, 5]
y = [2, 4, 6, 8, 10]

fig, ax = plt.subplots(figsize=(14, 9))
ax.plot(x, y, linewidth=3)
ax.set_xlabel('X Values', fontsize=16)
ax.set_ylabel('Y Values', fontsize=16)
ax.set_title('My First Plot', fontsize=20)
ax.tick_params(labelsize=14)
plt.show()

The plt.subplots() function creates both a Figure and Axes object, giving you full control over your visualization.

Setting Figure Size

Control canvas dimensions with figsize parameter:

Show sized plot
fig, ax = plt.subplots(figsize=(14, 9))

x = np.linspace(0, 10, 100)
y = np.sin(x)

ax.plot(x, y, linewidth=3)
ax.set_title('Larger Figure - 14x9 inches', fontsize=20)
ax.set_xlabel('X', fontsize=16)
ax.set_ylabel('sin(x)', fontsize=16)
ax.tick_params(labelsize=14)
ax.grid(True, alpha=0.3)
plt.show()

Default size is (6.4, 4.8) inches. Use np.linspace() to create evenly spaced points.

Customizing Line Appearance

Control every aspect of your lines:

Show customized line
fig, ax = plt.subplots(figsize=(14, 9))

x = np.linspace(0, 10, 100)
y = np.sin(x)

ax.plot(x, y, 
        color='red',
        linestyle='--',
        linewidth=3,
        marker='o',
        markersize=6,
        markerfacecolor='blue',
        markeredgecolor='black',
        label='Sine Wave')

ax.set_xlabel('X', fontsize=16)
ax.set_ylabel('Y', fontsize=16)
ax.set_title('Customized Line Plot', fontsize=20)
ax.tick_params(labelsize=14)
ax.legend(fontsize=14)
ax.grid(True, alpha=0.3)
plt.show()

Customize colors, line styles, markers, and more to create distinctive visualizations.

Line Style Shortcuts

Use convenient shorthand notation:

Show shortcuts
fig, ax = plt.subplots(figsize=(14, 9))

x = np.linspace(0, 10, 100)

ax.plot(x, np.sin(x), 'r-', linewidth=3, label="'r-' = red solid")
ax.plot(x, np.sin(x) + 1, 'b--', linewidth=3, label="'b--' = blue dashed")
ax.plot(x, np.sin(x) + 2, 'go:', linewidth=3, markersize=8, label="'go:' = green dots with circles")
ax.plot(x, np.sin(x) + 3, 'k^-.', linewidth=3, markersize=8, label="'k^-.' = black dash-dot triangles")

ax.legend(fontsize=14)
ax.set_title('Line Style Shortcuts', fontsize=20)
ax.tick_params(labelsize=14)
ax.grid(True, alpha=0.3)
plt.show()

Format: [color][marker][linestyle] - e.g., 'ro-' = red circles with solid line

Multiple Lines

Easily plot multiple datasets together:

Show multiple lines
fig, ax = plt.subplots(figsize=(14, 9))

x = np.linspace(0, 2*np.pi, 100)

ax.plot(x, np.sin(x), label='sin(x)', linewidth=3)
ax.plot(x, np.cos(x), label='cos(x)', linewidth=3)
ax.plot(x, np.sin(x) * np.cos(x), label='sin(x) × cos(x)', linewidth=3)

ax.set_xlabel('X (radians)', fontsize=16)
ax.set_ylabel('Y', fontsize=16)
ax.set_title('Multiple Functions', fontsize=20)
ax.tick_params(labelsize=14)
ax.legend(fontsize=14)
ax.grid(True, alpha=0.3)
plt.show()

Each ax.plot() call adds another line to the same axes, perfect for comparisons.

Multiple Subplots: Grid Layout

Create complex layouts with multiple plots:

Show 2x2 grid
fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(14, 12))

x = np.linspace(0, 2*np.pi, 100)

axes[0, 0].plot(x, np.sin(x), linewidth=3)
axes[0, 0].set_title('sin(x)', fontsize=16)
axes[0, 0].tick_params(labelsize=12)
axes[0, 0].grid(True, alpha=0.3)

axes[0, 1].plot(x, np.cos(x), 'r', linewidth=3)
axes[0, 1].set_title('cos(x)', fontsize=16)
axes[0, 1].tick_params(labelsize=12)
axes[0, 1].grid(True, alpha=0.3)

axes[1, 0].plot(x, np.tan(x), linewidth=3)
axes[1, 0].set_title('tan(x)', fontsize=16)
axes[1, 0].set_ylim(-5, 5)
axes[1, 0].tick_params(labelsize=12)
axes[1, 0].grid(True, alpha=0.3)

axes[1, 1].plot(x, np.sin(x)**2, 'g', linewidth=3)
axes[1, 1].set_title('sin²(x)', fontsize=16)
axes[1, 1].tick_params(labelsize=12)
axes[1, 1].grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

Access individual plots with axes[row, col]. Always use plt.tight_layout() to prevent overlapping.

Subplots: Row and Column Layouts

Single row or column arrangements:

Show 1x3 layout
fig, axes = plt.subplots(1, 3, figsize=(16, 6))

x = np.linspace(0, 10, 100)

axes[0].plot(x, x**2, linewidth=3)
axes[0].set_title('Quadratic', fontsize=16)
axes[0].tick_params(labelsize=12)
axes[0].grid(True, alpha=0.3)

axes[1].plot(x, x**3, 'r', linewidth=3)
axes[1].set_title('Cubic', fontsize=16)
axes[1].tick_params(labelsize=12)
axes[1].grid(True, alpha=0.3)

axes[2].plot(x, 2**x, 'g', linewidth=3)
axes[2].set_title('Exponential', fontsize=16)
axes[2].tick_params(labelsize=12)
axes[2].grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

With single row/column, axes is 1D array - use axes[i] instead of axes[i, j].

Shared Axes

Share x or y axes for easier comparison:

Show shared axes
fig, axes = plt.subplots(2, 2, figsize=(14, 12), 
                         sharex=True, sharey=True)

x = np.linspace(0, 10, 100)

axes[0, 0].plot(x, x, linewidth=3)
axes[0, 0].set_title('Linear', fontsize=16)
axes[0, 0].tick_params(labelsize=12)
axes[0, 0].grid(True, alpha=0.3)

axes[0, 1].plot(x, x**2, linewidth=3)
axes[0, 1].set_title('Quadratic', fontsize=16)
axes[0, 1].tick_params(labelsize=12)
axes[0, 1].grid(True, alpha=0.3)

axes[1, 0].plot(x, x**3, linewidth=3)
axes[1, 0].set_title('Cubic', fontsize=16)
axes[1, 0].tick_params(labelsize=12)
axes[1, 0].grid(True, alpha=0.3)

axes[1, 1].plot(x, np.sqrt(x), linewidth=3)
axes[1, 1].set_title('Square Root', fontsize=16)
axes[1, 1].tick_params(labelsize=12)
axes[1, 1].grid(True, alpha=0.3)

axes[1, 0].set_xlabel('X', fontsize=14)
axes[1, 1].set_xlabel('X', fontsize=14)
axes[0, 0].set_ylabel('Y', fontsize=14)
axes[1, 0].set_ylabel('Y', fontsize=14)

plt.tight_layout()
plt.show()

Shared axes maintain consistent scales across subplots, reducing visual clutter.

GridSpec: Advanced Layouts

Create custom subplot arrangements:

Show GridSpec layout
import matplotlib.gridspec as gridspec

fig = plt.figure(figsize=(14, 12))
gs = gridspec.GridSpec(3, 3, figure=fig)

ax1 = fig.add_subplot(gs[0, :])
ax2 = fig.add_subplot(gs[1, :-1])
ax3 = fig.add_subplot(gs[1:, -1])
ax4 = fig.add_subplot(gs[-1, 0])
ax5 = fig.add_subplot(gs[-1, 1])

x = np.linspace(0, 10, 100)

ax1.plot(x, np.sin(x), linewidth=3)
ax1.set_title('Wide plot spanning all columns', fontsize=16)
ax1.tick_params(labelsize=12)
ax1.grid(True, alpha=0.3)

ax2.plot(x, np.cos(x), 'r', linewidth=3)
ax2.set_title('Medium plot', fontsize=16)
ax2.tick_params(labelsize=12)
ax2.grid(True, alpha=0.3)

ax3.plot(x, np.tan(x), 'g', linewidth=3)
ax3.set_title('Tall plot', fontsize=16)
ax3.set_ylim(-5, 5)
ax3.tick_params(labelsize=12)
ax3.grid(True, alpha=0.3)

ax4.plot(x, x**2, 'm', linewidth=3)
ax4.set_title('Small 1', fontsize=14)
ax4.tick_params(labelsize=10)
ax4.grid(True, alpha=0.3)

ax5.plot(x, x**3, 'c', linewidth=3)
ax5.set_title('Small 2', fontsize=14)
ax5.tick_params(labelsize=10)
ax5.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

GridSpec allows plots to span multiple cells - perfect for dashboards and complex layouts.

Scatter Plots

Visualize relationships between variables:

Show scatter plot
fig, ax = plt.subplots(figsize=(14, 9))

np.random.seed(42)
x = np.random.randn(100)
y = 2 * x + np.random.randn(100) * 0.5

ax.scatter(x, y, 
          color='blue',
          alpha=0.6,
          s=100,
          edgecolors='black',
          linewidth=1)

ax.set_xlabel('X Values', fontsize=16)
ax.set_ylabel('Y Values', fontsize=16)
ax.set_title('Scatter Plot Showing Correlation', fontsize=20)
ax.tick_params(labelsize=14)
ax.grid(True, alpha=0.3)
plt.show()

Scatter plots reveal correlations and distributions. Use alpha for overlapping points.

Scatter with Color Mapping

Add a third dimension using colors:

Show colored scatter
fig, ax = plt.subplots(figsize=(14, 10))

np.random.seed(42)
x = np.random.randn(100)
y = np.random.randn(100)
colors = x + y
sizes = 1000 * np.abs(np.random.randn(100))

scatter = ax.scatter(x, y, 
                    c=colors,
                    s=sizes,
                    alpha=0.6,
                    cmap='viridis',
                    edgecolors='black',
                    linewidth=1)

cbar = plt.colorbar(scatter, ax=ax)
cbar.set_label('X + Y Values', fontsize=14)
cbar.ax.tick_params(labelsize=12)

ax.set_xlabel('X', fontsize=16)
ax.set_ylabel('Y', fontsize=16)
ax.set_title('Scatter with Color and Size Mapping', fontsize=20)
ax.tick_params(labelsize=14)
ax.grid(True, alpha=0.3)
plt.show()

Color mapping reveals patterns that aren’t visible in 2D alone. The colorbar provides a scale reference.

Bar Plots

Compare categorical data:

Show bar plot
fig, ax = plt.subplots(figsize=(14, 9))

categories = ['Product A', 'Product B', 'Product C', 'Product D', 'Product E']
values = [23, 45, 56, 78, 32]

bars = ax.bar(categories, values, 
              color='skyblue',
              edgecolor='navy',
              linewidth=2)

max_idx = values.index(max(values))
bars[max_idx].set_color('orange')

ax.set_ylabel('Sales (thousands)', fontsize=16)
ax.set_title('Product Sales Comparison', fontsize=20)
ax.tick_params(labelsize=14)
ax.grid(axis='y', alpha=0.3)

for bar in bars:
    height = bar.get_height()
    ax.text(bar.get_x() + bar.get_width()/2., height,
            f'{height}',
            ha='center', va='bottom', fontsize=14)

plt.show()

Bar plots excel at categorical comparisons. Add value labels for clarity, highlight significant bars.

Grouped Bar Plots

Compare multiple groups side by side:

Show grouped bars
fig, ax = plt.subplots(figsize=(14, 9))

categories = ['Q1', 'Q2', 'Q3', 'Q4']
sales_2023 = [45, 52, 48, 60]
sales_2024 = [50, 58, 55, 65]

x = np.arange(len(categories))
width = 0.35

bars1 = ax.bar(x - width/2, sales_2023, width, 
               label='2023', color='skyblue', edgecolor='black', linewidth=1.5)
bars2 = ax.bar(x + width/2, sales_2024, width, 
               label='2024', color='lightcoral', edgecolor='black', linewidth=1.5)

ax.set_xlabel('Quarter', fontsize=16)
ax.set_ylabel('Sales (thousands)', fontsize=16)
ax.set_title('Quarterly Sales: 2023 vs 2024', fontsize=20)
ax.set_xticks(x)
ax.set_xticklabels(categories)
ax.tick_params(labelsize=14)
ax.legend(fontsize=14)
ax.grid(axis='y', alpha=0.3)
plt.show()

Grouped bars show trends over time or across categories. Offset bars using width/2.

Stacked Bar Plots

Show composition and totals:

Show stacked bars
fig, ax = plt.subplots(figsize=(14, 9))

categories = ['Q1', 'Q2', 'Q3', 'Q4']
online_sales = [30, 35, 32, 40]
store_sales = [15, 17, 16, 20]

ax.bar(categories, online_sales, label='Online', color='steelblue', edgecolor='black')
ax.bar(categories, store_sales, bottom=online_sales, 
       label='In-Store', color='coral', edgecolor='black')

ax.set_ylabel('Sales (thousands)', fontsize=16)
ax.set_title('Sales by Channel (Stacked)', fontsize=20)
ax.tick_params(labelsize=14)
ax.legend(fontsize=14)
ax.grid(axis='y', alpha=0.3)
plt.show()

Stacked bars show both individual components and total values simultaneously.

Histograms

Visualize data distributions:

Show histogram
fig, ax = plt.subplots(figsize=(14, 9))

np.random.seed(42)
data = np.random.randn(1000)

n, bins, patches = ax.hist(data, 
                           bins=30,
                           color='steelblue',
                           edgecolor='black',
                           alpha=0.7,
                           linewidth=1.5)

ax.set_xlabel('Value', fontsize=16)
ax.set_ylabel('Frequency', fontsize=16)
ax.set_title('Distribution of Random Data', fontsize=20)
ax.tick_params(labelsize=14)
ax.axvline(data.mean(), color='red', linestyle='--', 
           linewidth=3, label=f'Mean: {data.mean():.2f}')
ax.legend(fontsize=14)
ax.grid(axis='y', alpha=0.3)
plt.show()

Histograms reveal distribution shapes, central tendency, and spread. Adjust bin count for clarity.

Pie Charts

Display proportions and percentages:

Show pie chart
fig, ax = plt.subplots(figsize=(12, 10))

sizes = [35, 25, 20, 15, 5]
labels = ['Product A', 'Product B', 'Product C', 'Product D', 'Others']
colors = ['#ff9999', '#66b3ff', '#99ff99', '#ffcc99', '#ff99cc']
explode = (0.1, 0, 0, 0, 0)

wedges, texts, autotexts = ax.pie(sizes, 
       explode=explode,
       labels=labels, 
       colors=colors,
       autopct='%1.1f%%',
       shadow=True,
       startangle=90,
       textprops={'fontsize': 14})

for autotext in autotexts:
    autotext.set_color('white')
    autotext.set_fontsize(14)
    autotext.set_weight('bold')

ax.set_title('Market Share by Product', fontsize=20)
plt.show()

Pie charts work best with few categories (5-7 max). Explode slices to emphasize important segments.

Heatmaps

Visualize 2D data with color intensity:

Show heatmap
fig, ax = plt.subplots(figsize=(14, 10))

np.random.seed(42)
data = np.random.rand(10, 12)

im = ax.imshow(data, cmap='YlOrRd', aspect='auto')

cbar = plt.colorbar(im, ax=ax)
cbar.set_label('Values', fontsize=14)
cbar.ax.tick_params(labelsize=12)

ax.set_xticks(np.arange(12))
ax.set_yticks(np.arange(10))
ax.set_xticklabels(['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 
                    'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], fontsize=12)
ax.set_yticklabels([f'Product {i+1}' for i in range(10)], fontsize=12)

plt.setp(ax.get_xticklabels(), rotation=45, ha="right")

ax.set_title('Monthly Sales Heatmap', fontsize=20)
plt.tight_layout()
plt.show()

Heatmaps excel at showing patterns in matrix data. Color intensity represents magnitude.

Customizing Ticks

Precise control over axis ticks:

Show custom ticks
fig, ax = plt.subplots(figsize=(14, 9))

x = np.linspace(0, 4*np.pi, 100)
y = np.sin(x)

ax.plot(x, y, linewidth=3)

ax.set_xticks([0, np.pi, 2*np.pi, 3*np.pi, 4*np.pi])
ax.set_xticklabels(['0', 'π', '2π', '3π', '4π'])

ax.tick_params(axis='both', 
               labelsize=16,
               length=8,
               width=2,
               colors='darkblue')

ax.set_xlabel('X (radians)', fontsize=16)
ax.set_ylabel('Y', fontsize=16)
ax.set_title('Custom Tick Labels', fontsize=20)
ax.grid(True, alpha=0.3)
plt.show()

Custom ticks improve readability for special scales (angles, logarithms, dates).

Logarithmic Scales

Handle wide value ranges:

Show log scale
fig, axes = plt.subplots(1, 2, figsize=(16, 8))

x = np.linspace(0.1, 10, 100)
y = np.exp(x)

axes[0].plot(x, y, linewidth=3)
axes[0].set_title('Linear Scale', fontsize=18)
axes[0].set_xlabel('X', fontsize=14)
axes[0].set_ylabel('Y = exp(x)', fontsize=14)
axes[0].tick_params(labelsize=12)
axes[0].grid(True, alpha=0.3)

axes[1].plot(x, y, linewidth=3)
axes[1].set_yscale('log')
axes[1].set_title('Logarithmic Y-Scale', fontsize=18)
axes[1].set_xlabel('X', fontsize=14)
axes[1].set_ylabel('Y = exp(x) [log scale]', fontsize=14)
axes[1].tick_params(labelsize=12)
axes[1].grid(True, alpha=0.3, which='both')

plt.tight_layout()
plt.show()

Log scales reveal patterns in exponential data and compress large ranges.

Twin Axes

Plot two different scales:

Show twin axes
fig, ax1 = plt.subplots(figsize=(14, 9))

x = np.arange(0, 10, 0.1)
y1 = np.sin(x)
y2 = np.exp(x / 5)

color = 'tab:blue'
ax1.set_xlabel('X', fontsize=16)
ax1.set_ylabel('sin(x)', color=color, fontsize=16)
ax1.plot(x, y1, color=color, linewidth=3)
ax1.tick_params(axis='y', labelcolor=color, labelsize=14)
ax1.tick_params(axis='x', labelsize=14)

ax2 = ax1.twinx()

color = 'tab:red'
ax2.set_ylabel('exp(x/5)', color=color, fontsize=16)
ax2.plot(x, y2, color=color, linewidth=3)
ax2.tick_params(axis='y', labelcolor=color, labelsize=14)

ax1.set_title('Two Different Y-Axes', fontsize=20)
ax1.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

Twin axes enable comparing metrics with vastly different scales on the same plot.

Annotations and Arrows

Highlight important features:

Show annotations
fig, ax = plt.subplots(figsize=(14, 9))

x = np.linspace(0, 10, 100)
y = np.sin(x)

ax.plot(x, y, 'b-', linewidth=3)

max_idx = np.argmax(y)
ax.annotate('Maximum', 
            xy=(x[max_idx], y[max_idx]),
            xytext=(x[max_idx] + 1, y[max_idx] + 0.3),
            arrowprops=dict(facecolor='red', shrink=0.05, width=3),
            fontsize=16,
            bbox=dict(boxstyle='round', facecolor='wheat'))

ax.text(5, -0.8, 'This is a sine wave', 
        fontsize=16, ha='center',
        bbox=dict(boxstyle='round', facecolor='lightblue'))

ax.set_xlabel('X', fontsize=16)
ax.set_ylabel('Y', fontsize=16)
ax.set_title('Plot with Annotations', fontsize=20)
ax.tick_params(labelsize=14)
ax.grid(True, alpha=0.3)
plt.show()

Annotations guide reader attention to critical points and patterns in your data.

Filling Between Lines

Show areas between curves:

Show fill between
fig, ax = plt.subplots(figsize=(14, 9))

x = np.linspace(0, 10, 100)
y1 = np.sin(x)
y2 = np.cos(x)

ax.plot(x, y1, 'r-', linewidth=3, label='sin(x)')
ax.plot(x, y2, 'b-', linewidth=3, label='cos(x)')

ax.fill_between(x, y1, y2, 
                where=(y1 >= y2),
                alpha=0.3, 
                color='green',
                label='sin(x) > cos(x)')

ax.fill_between(x, y1, y2, 
                where=(y1 < y2),
                alpha=0.3, 
                color='orange',
                label='sin(x) < cos(x)')

ax.set_xlabel('X', fontsize=16)
ax.set_ylabel('Y', fontsize=16)
ax.set_title('Filled Area Between Curves', fontsize=20)
ax.tick_params(labelsize=14)
ax.legend(fontsize=14)
ax.grid(True, alpha=0.3)
plt.show()

Fill regions highlight differences and relationships between multiple datasets.

Error Bars

Display measurement uncertainty:

Show error bars
fig, ax = plt.subplots(figsize=(14, 9))

x = np.arange(0, 10, 1)
y = np.exp(x / 5)
y_err = 0.1 * y

ax.errorbar(x, y, 
            yerr=y_err,
            fmt='o-',
            linewidth=3,
            markersize=10,
            capsize=7,
            capthick=2,
            ecolor='red',
            label='Data with error')

ax.set_xlabel('X', fontsize=16)
ax.set_ylabel('Y', fontsize=16)
ax.set_title('Plot with Error Bars', fontsize=20)
ax.tick_params(labelsize=14)
ax.legend(fontsize=14)
ax.grid(True, alpha=0.3)
plt.show()

Error bars communicate data quality and measurement precision - essential for scientific plots.

Contour Plots

Represent 3D data in 2D:

Show contour plot
fig, ax = plt.subplots(figsize=(14, 10))

x = np.linspace(-3, 3, 100)
y = np.linspace(-3, 3, 100)
X, Y = np.meshgrid(x, y)
Z = np.sin(np.sqrt(X**2 + Y**2))

contour = ax.contour(X, Y, Z, levels=15, cmap='RdYlBu', linewidths=2)
ax.clabel(contour, inline=True, fontsize=10)

contourf = ax.contourf(X, Y, Z, levels=15, cmap='RdYlBu', alpha=0.6)

cbar = plt.colorbar(contourf, ax=ax)
cbar.set_label('Z values', fontsize=14)
cbar.ax.tick_params(labelsize=12)

ax.set_xlabel('X', fontsize=16)
ax.set_ylabel('Y', fontsize=16)
ax.set_title('Contour Plot', fontsize=20)
ax.tick_params(labelsize=14)
plt.show()

Contour plots show elevation and gradients - widely used in geography, physics, and optimization.

3D Surface Plots

Visualize functions of two variables:

Show 3D surface
from mpl_toolkits.mplot3d import Axes3D

fig = plt.figure(figsize=(14, 11))
ax = fig.add_subplot(111, projection='3d')

x = np.linspace(-5, 5, 50)
y = np.linspace(-5, 5, 50)
X, Y = np.meshgrid(x, y)
Z = np.sin(np.sqrt(X**2 + Y**2))

surf = ax.plot_surface(X, Y, Z, 
                       cmap='viridis',
                       edgecolor='none',
                       alpha=0.8)

fig.colorbar(surf, ax=ax, shrink=0.5)

ax.set_xlabel('X', fontsize=14)
ax.set_ylabel('Y', fontsize=14)
ax.set_zlabel('Z', fontsize=14)
ax.set_title('3D Surface Plot', fontsize=18)
ax.tick_params(labelsize=11)
plt.show()

3D plots provide intuitive understanding of multivariate functions and surfaces.

Styling with Style Sheets

Apply professional themes instantly:

Show styled plot
plt.style.use('seaborn-v0_8-darkgrid')

fig, ax = plt.subplots(figsize=(14, 9))

x = np.linspace(0, 10, 100)
ax.plot(x, np.sin(x), linewidth=3, label='sin(x)')
ax.plot(x, np.cos(x), linewidth=3, label='cos(x)')

ax.set_xlabel('X', fontsize=16)
ax.set_ylabel('Y', fontsize=16)
ax.set_title('Using Seaborn Style', fontsize=20)
ax.tick_params(labelsize=14)
ax.legend(fontsize=14)
plt.show()

plt.style.use('default')

Style sheets ensure consistent, professional appearance across all plots.

Saving Figures

Export in multiple formats:

Show save example
fig, ax = plt.subplots(figsize=(14, 9))

x = np.linspace(0, 10, 100)
ax.plot(x, np.sin(x), linewidth=3)
ax.set_xlabel('X', fontsize=16)
ax.set_ylabel('Y', fontsize=16)
ax.set_title('Figure to Save', fontsize=20)
ax.tick_params(labelsize=14)
ax.grid(True, alpha=0.3)

fig.savefig('my_plot.png', dpi=300, bbox_inches='tight')
fig.savefig('my_plot.pdf', bbox_inches='tight')
fig.savefig('my_plot.svg', bbox_inches='tight')

print("✓ Figures saved successfully!")
plt.show()
✓ Figures saved successfully!

Always use dpi=300 for publication quality, bbox_inches='tight' removes whitespace.

Real-World Example: Sales Dashboard

Putting it all together:

Show complete dashboard
np.random.seed(42)
dates = pd.date_range('2023-01-01', periods=365, freq='D')
sales = 1000 + np.cumsum(np.random.randn(365) * 10)
profit = sales * 0.2 + np.random.randn(365) * 20
customers = 50 + np.cumsum(np.random.randn(365) * 2)

fig = plt.figure(figsize=(18, 14))
gs = gridspec.GridSpec(3, 2, height_ratios=[2, 1, 1], hspace=0.3, wspace=0.3)

# Main sales plot
ax1 = fig.add_subplot(gs[0, :])
ax1.plot(dates, sales, linewidth=3, color='steelblue', label='Daily Sales')
ax1.fill_between(dates, sales, alpha=0.3, color='steelblue')
ax1.set_title('Sales Dashboard 2023', fontsize=20, fontweight='bold')
ax1.set_ylabel('Sales ($)', fontsize=14)
ax1.tick_params(labelsize=12)
ax1.legend(loc='upper left', fontsize=12)
ax1.grid(True, alpha=0.3)

# Profit trend
ax2 = fig.add_subplot(gs[1, 0])
ax2.plot(dates, profit, linewidth=2, color='green', label='Daily Profit')
ax2.axhline(y=profit.mean(), color='red', linestyle='--', linewidth=2, label='Average')
ax2.set_ylabel('Profit ($)', fontsize=12)
ax2.set_title('Profit Trend', fontsize=14)
ax2.tick_params(labelsize=10)
ax2.legend(fontsize=10)
ax2.grid(True, alpha=0.3)

# Customer count
ax3 = fig.add_subplot(gs[1, 1])
ax3.plot(dates, customers, linewidth=2, color='coral')
ax3.set_ylabel('Customers', fontsize=12)
ax3.set_title('Customer Count', fontsize=14)
ax3.tick_params(labelsize=10)
ax3.grid(True, alpha=0.3)

# Monthly summary
ax4 = fig.add_subplot(gs[2, 0])
monthly_sales = [sales[i*30:(i+1)*30].sum() for i in range(12)]
months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 
          'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
ax4.bar(months, monthly_sales, color='skyblue', edgecolor='navy')
ax4.set_ylabel('Monthly Sales ($)', fontsize=12)
ax4.set_title('Monthly Sales Summary', fontsize=14)
ax4.tick_params(axis='x', rotation=45, labelsize=10)
ax4.tick_params(axis='y', labelsize=10)
ax4.grid(axis='y', alpha=0.3)

# Profit distribution
ax5 = fig.add_subplot(gs[2, 1])
ax5.hist(profit, bins=30, color='lightgreen', edgecolor='darkgreen', alpha=0.7)
ax5.set_xlabel('Profit ($)', fontsize=12)
ax5.set_ylabel('Frequency', fontsize=12)
ax5.set_title('Profit Distribution', fontsize=14)
ax5.tick_params(labelsize=10)
ax5.grid(axis='y', alpha=0.3)

plt.show()

This dashboard combines multiple plot types to tell a complete data story.

Best Practices

Essential Guidelines

  1. Always label axes - Never leave unlabeled
  2. Choose appropriate plot types - Match visualization to data
  3. Keep it simple - Avoid unnecessary complexity
  4. Use colorblind-friendly palettes - Ensure accessibility
  5. Add legends when needed - Help readers interpret
  6. Include grids - Improve readability
  7. Save high-resolution - Use dpi=300 minimum
  8. Be consistent - Maintain style across related plots

Quick Reference

Plot Type Method Best For
Line ax.plot() Trends, time series
Scatter ax.scatter() Relationships, correlations
Bar ax.bar() Categorical comparisons
Histogram ax.hist() Distributions
Pie ax.pie() Proportions (few categories)
Heatmap ax.imshow() 2D data, matrices
Contour ax.contour() 3D data in 2D
Error bars ax.errorbar() Uncertainty

Thank You!