The derivative of a function tells you how fast it is changing at each point. `numpy.gradient` computes this numerically for any array of sampled values — no symbolic math needed. You'd use it when you have a recorded signal and want to know its rate of change: turning position data into velocity, finding where a curve is steepest, detecting edges in an image, or computing slope across a terrain grid. It uses central differences for interior points (looking one step left and right) and one-sided differences at the array edges, giving a result the same shape as the input. ### Gradient of a 1D Array The simplest case is a 1D array where each element is equally spaced. `numpy.gradient` returns an array of the same length containing the estimated derivative at every point. A good way to see this is to take the gradient of `sin(x)` — the result should closely match `cos(x)`, which is the true derivative.
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(0, 2 * np.pi, 200)
y = np.sin(x)
dy = np.gradient(y, x) # derivative of sin(x) ≈ cos(x)
plt.figure(figsize=(8, 4))
plt.plot(x, y, label="sin(x)", linewidth=2)
plt.plot(x, dy, label="gradient (≈ cos(x))", linewidth=2, linestyle="--")
plt.axhline(0, color="gray", linewidth=0.8)
plt.title("Gradient of sin(x)")
plt.xlabel("x")
plt.ylabel("Value")
plt.legend()
plt.tight_layout()
plt.show()- `np.gradient(y, x)` takes the array of values `y` and the corresponding x-coordinates `x` so the derivative is computed in the correct units. Without `x`, it assumes a step size of 1. - The dashed gradient line closely tracks `cos(x)`, confirming the numerical derivative is accurate in the interior. The match is slightly off at the endpoints where one-sided differences are less accurate. ### Specifying the Spacing By default `numpy.gradient` assumes each step is 1 unit apart. If your samples are spaced differently — e.g., readings every 0.1 seconds — you must tell the function, otherwise the derivative values will be wrong by a scale factor. You can pass either the uniform step size as a scalar, or a full array of coordinates.
import numpy as np
t = np.array([0.0, 0.1, 0.2, 0.3, 0.4, 0.5])
position = np.array([0.0, 0.05, 0.20, 0.45, 0.80, 1.25]) # metres
# Wrong: assumes step=1 (off by factor of 10)
velocity_wrong = np.gradient(position)
# Correct: pass the actual time step
velocity = np.gradient(position, t)
print("Without spacing:", velocity_wrong.round(3))
print("With spacing: ", velocity.round(3))- Passing `t` as the second argument tells `numpy.gradient` the actual time values between samples so it divides by the correct `Δt`. - You can also pass the scalar `0.1` instead of the full `t` array when the spacing is uniform — the result is identical. - `velocity_wrong` is exactly 10× smaller than `velocity`, illustrating how forgetting the spacing silently produces incorrect results. ### Gradient of a 2D Array For a 2D array `numpy.gradient` returns a list of two arrays — one for each axis. The first is the gradient along axis 0 (rows, the y-direction) and the second along axis 1 (columns, the x-direction). Together they describe how the surface changes in every direction.
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-2, 2, 80)
y = np.linspace(-2, 2, 80)
X, Y = np.meshgrid(x, y)
Z = np.exp(-(X**2 + Y**2)) # 2D Gaussian bump
gy, gx = np.gradient(Z, y, x)
fig, axes = plt.subplots(1, 3, figsize=(13, 4))
titles = ["Surface Z", "∂Z/∂x (x-gradient)", "∂Z/∂y (y-gradient)"]
data = [Z, gx, gy]
for ax, d, title in zip(axes, data, titles):
im = ax.imshow(d, extent=[-2, 2, -2, 2], origin="lower", cmap="RdBu_r")
ax.set_title(title)
ax.set_xlabel("x")
ax.set_ylabel("y")
plt.colorbar(im, ax=ax, shrink=0.8)
plt.tight_layout()
plt.show()- `np.gradient(Z, y, x)` returns `[gy, gx]` — the first array corresponds to axis 0 (y-direction) and the second to axis 1 (x-direction), matching the `(row, col)` convention of 2D arrays. - The x-gradient is negative on the right half and positive on the left, reflecting the downward slope of the Gaussian away from its peak in the x-direction. - `imshow(..., origin="lower")` places the y-axis origin at the bottom, matching the mathematical convention. ### Application: Edge Detection with Gradient Magnitude Edges in an image are places where pixel values change sharply. The gradient magnitude — `sqrt(gx² + gy²)` — is large wherever the surface changes steeply, in any direction. This is the foundation of the Sobel and Canny edge detectors used in computer vision.
import numpy as np
import matplotlib.pyplot as plt
# Simulate a simple "image": a bright square on a dark background
img = np.zeros((100, 100))
img[30:70, 30:70] = 1.0
gy, gx = np.gradient(img)
magnitude = np.sqrt(gx**2 + gy**2)
fig, axes = plt.subplots(1, 2, figsize=(9, 4))
axes[0].imshow(img, cmap="gray", origin="lower")
axes[0].set_title("Original image")
axes[1].imshow(magnitude, cmap="hot", origin="lower")
axes[1].set_title("Gradient magnitude (edges)")
for ax in axes:
ax.set_xlabel("x")
ax.set_ylabel("y")
plt.tight_layout()
plt.show()- `np.zeros((100, 100))` creates a black canvas; assigning `1.0` to a slice paints a white square. - `magnitude = np.sqrt(gx**2 + gy**2)` combines the horizontal and vertical gradients into a single scalar that is large wherever the image changes, regardless of direction. - The `"hot"` colormap maps high values to bright white/yellow and zero to black, making the detected edges easy to see. `numpy.gradient` is a general-purpose tool for any time you need rates of change from sampled data. For 1D signals, pair it with [signal filtering and smoothing](/tutorials/signal-filtering-and-smoothing-with-scipy) first — a smoother input produces a less noisy gradient. Next, learn about [finding peaks with SciPy](/tutorials/find-peaks-with-scipy) to locate maxima in the gradient itself.