Axes Method - contour¶
The ax.contour() method draws contour lines for scalar fields.
Mental Model
contour() draws lines of constant value through a 2D scalar field, like elevation contours on a topographic map. You provide X, Y grids and a Z value at each point; Matplotlib interpolates to find where Z equals each contour level. Close-together lines mean steep gradients; widely spaced lines mean flat regions.
Contour = Horizontal Slice of a Surface
A contour line at level \(c\) is the set of all points where \(f(x, y) = c\) — geometrically, it is a horizontal slice through the 3D surface \(z = f(x, y)\). This is the bridge between 2D and 3D: every contour plot is a top-down view of level slices through the surface. The spacing between contour lines encodes the gradient: closely spaced lines mean the function is changing rapidly (steep slope), widely spaced lines mean it is nearly flat.
Basic Usage¶
Simple Contour Plot¶
```python import matplotlib.pyplot as plt import numpy as np
def main(): f = lambda x, y: np.sin(x) ** 10 + np.cos(10 + y * x) * np.cos(x) x = np.linspace(0, 5, 50) y = np.linspace(0, 5, 40) X, Y = np.meshgrid(x, y) Z = f(X, Y)
fig, ax = plt.subplots(figsize=(12, 4))
ax.contour(X, Y, Z, levels=3, colors='black')
plt.show()
if name == "main": main() ```
Creating Meshgrid¶
Understanding np.meshgrid¶
```python import numpy as np
x = np.linspace(0, 5, 50) # 50 points from 0 to 5 y = np.linspace(0, 5, 40) # 40 points from 0 to 5 X, Y = np.meshgrid(x, y)
print(f"x shape: {x.shape}") # (50,) print(f"y shape: {y.shape}") # (40,) print(f"X shape: {X.shape}") # (40, 50) print(f"Y shape: {Y.shape}") # (40, 50) ```
Computing Z Values¶
```python import numpy as np
f = lambda x, y: np.sin(x) ** 10 + np.cos(10 + y * x) * np.cos(x)
x = np.linspace(0, 5, 50) y = np.linspace(0, 5, 40) X, Y = np.meshgrid(x, y) Z = f(X, Y)
print(f"Z shape: {Z.shape}") # (40, 50) print(f"Z min: {Z.min():.3f}") print(f"Z max: {Z.max():.3f}") ```
Levels Parameter¶
Number of Levels¶
```python import matplotlib.pyplot as plt import numpy as np
f = lambda x, y: np.sin(x) ** 10 + np.cos(10 + y * x) * np.cos(x) x = np.linspace(0, 5, 50) y = np.linspace(0, 5, 40) X, Y = np.meshgrid(x, y) Z = f(X, Y)
fig, axes = plt.subplots(1, 4, figsize=(16, 4)) level_counts = [3, 7, 15, 30]
for ax, n_levels in zip(axes, level_counts): ax.contour(X, Y, Z, levels=n_levels, colors='black') ax.set_title(f'levels={n_levels}') ax.set_aspect('equal')
plt.tight_layout() plt.show() ```
Specific Level Values¶
```python import matplotlib.pyplot as plt import numpy as np
f = lambda x, y: np.sin(x) ** 10 + np.cos(10 + y * x) * np.cos(x) x = np.linspace(0, 5, 50) y = np.linspace(0, 5, 40) X, Y = np.meshgrid(x, y) Z = f(X, Y)
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
Evenly spaced levels¶
axes[0].contour(X, Y, Z, levels=np.linspace(-1, 1, 11), colors='black') axes[0].set_title('levels=np.linspace(-1, 1, 11)')
Custom specific levels¶
axes[1].contour(X, Y, Z, levels=[-0.5, 0, 0.5], colors='black') axes[1].set_title('levels=[-0.5, 0, 0.5]')
for ax in axes: ax.set_aspect('equal') plt.tight_layout() plt.show() ```
Colors and Colormaps¶
Single Color¶
```python import matplotlib.pyplot as plt import numpy as np
f = lambda x, y: np.sin(x) ** 10 + np.cos(10 + y * x) * np.cos(x) x = np.linspace(0, 5, 50) y = np.linspace(0, 5, 40) X, Y = np.meshgrid(x, y) Z = f(X, Y)
fig, axes = plt.subplots(1, 3, figsize=(15, 4)) colors_list = ['black', 'blue', 'red']
for ax, color in zip(axes, colors_list): ax.contour(X, Y, Z, levels=10, colors=color) ax.set_title(f"colors='{color}'") ax.set_aspect('equal')
plt.tight_layout() plt.show() ```
Using Colormaps¶
```python import matplotlib.pyplot as plt import numpy as np
f = lambda x, y: np.sin(x) ** 10 + np.cos(10 + y * x) * np.cos(x) x = np.linspace(0, 5, 50) y = np.linspace(0, 5, 40) X, Y = np.meshgrid(x, y) Z = f(X, Y)
fig, axes = plt.subplots(1, 3, figsize=(15, 4)) cmaps = ['viridis', 'plasma', 'coolwarm']
for ax, cmap in zip(axes, cmaps): cs = ax.contour(X, Y, Z, levels=15, cmap=cmap) ax.set_title(f"cmap='{cmap}'") ax.set_aspect('equal') fig.colorbar(cs, ax=ax, shrink=0.8)
plt.tight_layout() plt.show() ```
Line Styles¶
Linewidths¶
```python import matplotlib.pyplot as plt import numpy as np
f = lambda x, y: np.sin(x) ** 10 + np.cos(10 + y * x) * np.cos(x) x = np.linspace(0, 5, 50) y = np.linspace(0, 5, 40) X, Y = np.meshgrid(x, y) Z = f(X, Y)
fig, axes = plt.subplots(1, 3, figsize=(15, 4)) linewidths = [0.5, 1.5, 3]
for ax, lw in zip(axes, linewidths): ax.contour(X, Y, Z, levels=10, colors='black', linewidths=lw) ax.set_title(f'linewidths={lw}') ax.set_aspect('equal')
plt.tight_layout() plt.show() ```
Linestyles¶
```python import matplotlib.pyplot as plt import numpy as np
f = lambda x, y: np.sin(x) ** 10 + np.cos(10 + y * x) * np.cos(x) x = np.linspace(0, 5, 50) y = np.linspace(0, 5, 40) X, Y = np.meshgrid(x, y) Z = f(X, Y)
fig, axes = plt.subplots(1, 3, figsize=(15, 4)) linestyles = ['solid', 'dashed', 'dotted']
for ax, ls in zip(axes, linestyles): ax.contour(X, Y, Z, levels=10, colors='black', linestyles=ls) ax.set_title(f"linestyles='{ls}'") ax.set_aspect('equal')
plt.tight_layout() plt.show() ```
Filled Contours¶
contourf¶
```python import matplotlib.pyplot as plt import numpy as np
f = lambda x, y: np.sin(x) ** 10 + np.cos(10 + y * x) * np.cos(x) x = np.linspace(0, 5, 50) y = np.linspace(0, 5, 40) X, Y = np.meshgrid(x, y) Z = f(X, Y)
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
Line contour¶
axes[0].contour(X, Y, Z, levels=15, cmap='viridis') axes[0].set_title('contour (lines)')
Filled contour¶
cs = axes[1].contourf(X, Y, Z, levels=15, cmap='viridis') axes[1].set_title('contourf (filled)') fig.colorbar(cs, ax=axes[1], shrink=0.8)
for ax in axes: ax.set_aspect('equal') plt.tight_layout() plt.show() ```
Combined Lines and Fill¶
```python import matplotlib.pyplot as plt import numpy as np
f = lambda x, y: np.sin(x) ** 10 + np.cos(10 + y * x) * np.cos(x) x = np.linspace(0, 5, 50) y = np.linspace(0, 5, 40) X, Y = np.meshgrid(x, y) Z = f(X, Y)
fig, ax = plt.subplots(figsize=(10, 6))
Filled contours¶
cf = ax.contourf(X, Y, Z, levels=15, cmap='viridis', alpha=0.8)
Line contours on top¶
cs = ax.contour(X, Y, Z, levels=15, colors='black', linewidths=0.5)
fig.colorbar(cf, ax=ax) ax.set_title('Filled Contours with Lines') ax.set_aspect('equal') plt.tight_layout() plt.show() ```
Common Functions¶
Circle/Ellipse¶
```python import matplotlib.pyplot as plt import numpy as np
x = np.linspace(-3, 3, 100) y = np.linspace(-3, 3, 100) X, Y = np.meshgrid(x, y) Z = X2 + Y2 # Circle
fig, ax = plt.subplots(figsize=(6, 6)) cs = ax.contour(X, Y, Z, levels=[1, 2, 4, 6, 8], colors='black') ax.clabel(cs, inline=True, fontsize=10) ax.set_title('Circles: \(x^2 + y^2 = c\)') ax.set_aspect('equal') plt.show() ```
Saddle Point¶
```python import matplotlib.pyplot as plt import numpy as np
x = np.linspace(-3, 3, 100) y = np.linspace(-3, 3, 100) X, Y = np.meshgrid(x, y) Z = X2 - Y2 # Hyperbolic paraboloid
fig, ax = plt.subplots(figsize=(8, 6)) cs = ax.contour(X, Y, Z, levels=15, cmap='RdBu') fig.colorbar(cs, ax=ax) ax.set_title('Saddle: \(z = x^2 - y^2\)') ax.set_aspect('equal') plt.show() ```
Gaussian¶
```python import matplotlib.pyplot as plt import numpy as np
x = np.linspace(-3, 3, 100) y = np.linspace(-3, 3, 100) X, Y = np.meshgrid(x, y) Z = np.exp(-(X2 + Y2)) # Gaussian
fig, ax = plt.subplots(figsize=(8, 6)) cs = ax.contourf(X, Y, Z, levels=20, cmap='hot') ax.contour(X, Y, Z, levels=10, colors='black', linewidths=0.5) fig.colorbar(cs, ax=ax) ax.set_title('Gaussian: \(z = e^{-(x^2 + y^2)}\)') ax.set_aspect('equal') plt.show() ```
Practical Example¶
Topographic Map Style¶
```python import matplotlib.pyplot as plt import numpy as np
Create terrain-like surface¶
np.random.seed(42) x = np.linspace(0, 10, 100) y = np.linspace(0, 10, 100) X, Y = np.meshgrid(x, y)
Z = (np.sin(X) * np.cos(Y) + 0.5 * np.sin(2X) * np.cos(2Y) + 0.25 * np.sin(4X) * np.cos(4Y))
fig, ax = plt.subplots(figsize=(10, 8))
Filled contours for elevation colors¶
cf = ax.contourf(X, Y, Z, levels=20, cmap='terrain')
Contour lines¶
cs = ax.contour(X, Y, Z, levels=10, colors='black', linewidths=0.5) ax.clabel(cs, inline=True, fontsize=8, fmt='%.1f')
fig.colorbar(cf, ax=ax, label='Elevation') ax.set_title('Topographic Contour Map') ax.set_xlabel('X') ax.set_ylabel('Y') ax.set_aspect('equal') plt.tight_layout() plt.show() ```
Reading Contour Shapes¶
Contours reveal the geometry of a function. Learn to recognize common patterns:
| Contour shape | What it means | Example function |
|---|---|---|
| Concentric circles | Radial symmetry | Gaussian, \(x^2 + y^2\) |
| Ellipses | Stretched/scaled axes | \(ax^2 + by^2\) |
| Hyperbolas (X-shaped) | Saddle point | \(x^2 - y^2\) |
| Parallel lines | Linear function | \(ax + by\) |
| Wavy lines | Sinusoidal behavior | \(\sin(x) + \cos(y)\) |
Close contour lines = steep gradient. Wide spacing = flat region. This is why contour plots are sometimes called "topographic maps" of functions.
Exercises¶
Exercise 1.
Create contour plots of \(z = \sin(x) + \cos(y)\) over \([-2\pi, 2\pi] \times [-2\pi, 2\pi]\) with 15 levels. Use the coolwarm colormap and add a colorbar. Set equal aspect ratio.
Solution to Exercise 1
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(-2 * np.pi, 2 * np.pi, 200)
y = np.linspace(-2 * np.pi, 2 * np.pi, 200)
X, Y = np.meshgrid(x, y)
Z = np.sin(X) + np.cos(Y)
fig, ax = plt.subplots(figsize=(8, 6))
cs = ax.contour(X, Y, Z, levels=15, cmap='coolwarm')
plt.colorbar(cs, ax=ax)
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_title(r'$z = \sin(x) + \cos(y)$')
ax.set_aspect('equal')
plt.show()
Exercise 2.
Plot contour lines for the function \(z = x \cdot e^{-x^2 - y^2}\) over \([-2, 2] \times [-2, 2]\) using both positive and negative levels. Use solid lines for positive contours and dashed lines for negative contours by plotting two separate contour calls with different linestyles.
Solution to Exercise 2
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(-2, 2, 200)
y = np.linspace(-2, 2, 200)
X, Y = np.meshgrid(x, y)
Z = X * np.exp(-X**2 - Y**2)
fig, ax = plt.subplots(figsize=(8, 6))
pos_levels = np.linspace(0.01, Z.max(), 8)
neg_levels = np.linspace(Z.min(), -0.01, 8)
ax.contour(X, Y, Z, levels=pos_levels, colors='blue', linestyles='solid')
ax.contour(X, Y, Z, levels=neg_levels, colors='red', linestyles='dashed')
ax.contour(X, Y, Z, levels=[0], colors='black', linewidths=2)
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_title(r'$z = x \cdot e^{-x^2 - y^2}$')
ax.set_aspect('equal')
plt.show()
Exercise 3.
Overlay contour lines on a scatter plot. Generate 500 random (x, y) points from a bivariate normal distribution and plot them as a scatter. Then compute the theoretical density on a grid using scipy.stats.multivariate_normal and overlay contour lines showing the density levels.
Solution to Exercise 3
import matplotlib.pyplot as plt
import numpy as np
from scipy.stats import multivariate_normal
np.random.seed(42)
mean = [0, 0]
cov = [[1, 0.5], [0.5, 1]]
points = np.random.multivariate_normal(mean, cov, 500)
x = np.linspace(-4, 4, 200)
y = np.linspace(-4, 4, 200)
X, Y = np.meshgrid(x, y)
pos = np.dstack((X, Y))
rv = multivariate_normal(mean, cov)
Z = rv.pdf(pos)
fig, ax = plt.subplots(figsize=(8, 6))
ax.scatter(points[:, 0], points[:, 1], alpha=0.3, s=10, color='steelblue')
cs = ax.contour(X, Y, Z, levels=6, colors='red', linewidths=1.5)
ax.clabel(cs, inline=True, fontsize=8, fmt='%.3f')
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_title('Scatter with Density Contours')
ax.set_aspect('equal')
plt.show()