np.gradient() — A Simple Illustrated Guide

In Python, the numpy.gradient() function approximates the gradient of an N-dimensional array. It uses the second-order accurate central differences in the interior points and either first or second-order accurate one-sided differences at the boundaries for gradient approximation. The returned gradient hence has the same shape as the input array.Β 

Here is the argument table of numpy.gradient().

ArgumentAcceptDescription
farray_likeAn N-dimensional input array containing samples of a scalar function.
varargslist of scalar or array, optionalSpacing between f values. Default unitary spacing for all dimensions.
edge_order{1, 2}, optionalGradient is calculated using N-th order real differences at the boundaries. Default: 1.
axisNone or int or tuple of ints, optionalGradient is calculated only along the given axis or axes. The default (axis = None) is to calculate the gradient for all the axes of the input array. axis may be negative, in which case it counts from the last to the first axis.

If it sounds great to you, please continue reading, and you will fully understand the numpy.gradient() function through Python NumPy code snippets and vivid visualization.

  • First, I will introduce its underlying concepts, numpy.gradient() syntax and arguments. 
  • Then, you will learn some basic examples of this function.
  • Finally, I will address two top questions about numpy.gradient(), including np.gradient edge_order and np.gradient axis.

You can find all codes in this tutorial here.

Besides, I explained the difference between numpy.diff() and numpy.gradient() in another exciting guide to numpy.diff() method here.

Underlying Concepts: Gradient and Finite Difference

For this part, if you are familiar with gradient and finite difference, feel free to skip it and head over to its syntax and arguments!

πŸŽ“ Definition gradient: In vector calculus, the gradient of a scalar-valued differentiable function f of several variables is the vector field whose value at a point p is the vector whose components are the partial derivatives of f at p. (Wikipedia)

For example, the blue arrows in the following graph depict the gradient of the function f(x,y) = βˆ’(cos2x + cos2y)^2 as a projected vector field on the bottom plane.

Intuitively, you can consider gradient as an indicator of the fastest increase or decrease direction at a point. Computationally, the gradient is a vector containing all partial derivatives at a point.

Since the numpy.gradient() function uses the finite difference to approximate gradient under the hood, we also need to understand some basics of finite difference. 

πŸŽ“ Definition Finite Difference: A finite difference is a mathematical expression of the form f(x + b) βˆ’ f(x + a). If a finite difference is divided by b βˆ’ a, one gets a difference quotient. (Wikipedia)

Don’t panic! Here are my hand-written explanation and deduction for first and second-order forward, backward, and central differences. These formulas are used by numpy.gradient under the hood.

Syntax and Arguments

Here is the syntax of numpy.gradient():

# Syntax
numpy.gradient(f[, *varargs[, axis=None[, edge_order=1]]])

Here is the argument table of numpy.gradient():

Later, I will delve more into the arguments, edge_order and axis

As for the argument varargs, you can leave it right now and resort to it when you have non-unitary spacing dimensions πŸ™‚

The output of numpy.gradient() function is a list of ndarrays (or a single ndarray if there is only one dimension) corresponding to the derivatives of input f with respect to each dimension. Each derivative has the same shape as input f.

Basic Examples

Seen pictorially, here is an illustration of the gradient computation in a one-dimensional array.

Here is a one-dimensional array code example:

import numpy as np

one_dim = np.array([1, 2, 4, 8, 16], dtype=float)
gradient = np.gradient(one_dim)
print(gradient)
'''
# * Underlying Gradient Calculation:
# Default edge_order = 1
gradient[0] = (one_dim[1] - one_dim[0])/1 = (2. - 1.)/1 = 1. 

# Interior points
gradient[1] = (one_dim[2] - one_dim[0])/2 = (4. - 1.)/2 = 1.5
gradient[2] = (one_dim[3] - one_dim[1])/2 = (8. - 2.)/2 = 3.
gradient[3] = (one_dim[4] - one_dim[2])/2 = (16. - 4.)/2 = 6.

# Default edge_order = 1
gradient[4] = (one_dim[4] - one_dim[3])/1 = (16. - 8.)/1 = 8. 
'''

Output:

np.gradient() edge_order

In our basic example, we did not pass any parameters to the numpy.gradient() function.

In this section, I will show you how to deploy the argument edge_order and set different order differences for boundary elements.

Just to refresh your memory, here is the argument table of numpy.gradient():

We can set the argument edge_order to be 1 or 2. Its default value is 1. 

First, our previous basic example uses its default value, 1. 

import numpy as np

# edge_order = 1
one_dim = np.array([1, 2, 4, 8, 16], dtype=float)
gradient = np.gradient(one_dim, edge_order=1)
print(gradient)
'''
# * Underlying Gradient Calculation:
# Default edge_order = 1
gradient[0] = (one_dim[1] - one_dim[0])/1 = (2. - 1.)/1 = 1. 

# Interior points
gradient[1] = (one_dim[2] - one_dim[0])/2 = (4. - 1.)/2 = 1.5
gradient[2] = (one_dim[3] - one_dim[1])/2 = (8. - 2.)/2 = 3.
gradient[3] = (one_dim[4] - one_dim[2])/2 = (16. - 4.)/2 = 6.

# Default edge_order = 1
gradient[4] = (one_dim[4] - one_dim[3])/1 = (16. - 8.)/1 = 8. 
'''

Output:

Second, we can set the edge_order to be 2 and calculate the second-order differences for the boundary elements.

import numpy as np
# edge_order = 2
one_dim = np.array([1, 2, 4, 8, 16], dtype=float)
gradient = np.gradient(one_dim, edge_order=2)
print(f'edge_order = 2 -> {gradient}')
'''
# * Underlying Gradient Calculation:
# edge_order = 2
gradient[0] = (4*one_dim[0+1] - one_dim[0+2*1] - 3*one_dim[0])/(2*1) 
            = (4*2. - 4. + 3*1.)/2 = 0.5 

# Interior points
gradient[1] = (one_dim[2] - one_dim[0])/2 = (4. - 1.)/2 = 1.5
gradient[2] = (one_dim[3] - one_dim[1])/2 = (8. - 2.)/2 = 3.
gradient[3] = (one_dim[4] - one_dim[2])/2 = (16. - 4.)/2 = 6.

# edge_order = 2
gradient[4] = (3*one_dim[4] + one_dim[4-2*1] - 4*one_dim[4-1])/(2*1) 
            = (3*16. + 4. - 4*8.)/2 
            = 10. 
'''

Output:

For the rationale behind the second-order forward and backward difference formulas, please take a look at my previous hand-written deduction. I understand they do look quite strange but there is a logic behind πŸ™‚

np.gradient() axis 

In this part, I will show you how to deploy the argument axis and calculate (actually approximate) the gradient for the dimension(s) you want with a 2d array example case.

Just to refresh your memory, here is the argument table of numpy.gradient():

When we have an input with more than one dimension, we can set axis argument as None or int or tuple of ints to approximate the gradient along the corresponding axis or axes. 

Let’s take a two-dimensional array as an example case.

First, let’s see what the default value, None, will do.

import numpy as np

# axis = None (Default)
two_dim = np.array([[1, 2, 4, 8, 16],
                    [2, 5, 8, 10, 20]], dtype=float)
gradient = np.gradient(two_dim, axis=None)
# Same as:
# gradient = np.gradient(two_dim)
print(f'axis = None (Default): \n\n{gradient}')
print('\n', type(gradient))

Output:

As we can see, if axis = None, numpy.gradient() function will output gradient for all axes of the input array.

In this case, we can also pass an integer to axis argument.

import numpy as np

# axis = int
two_dim = np.array([[1, 2, 4, 8, 16],
                    [2, 5, 8, 10, 20]], dtype=float)
row_gradient = np.gradient(two_dim, axis=0)
col_gradient = np.gradient(two_dim, axis=1)

# Same as:
# row_gradient = np.gradient(two_dim, axis=-2)
# col_gradient = np.gradient(two_dim, axis=-1)

print(f'axis = 0 or -2: \n\n{row_gradient}')
print('-'*85)
print(f'axis = 1 or -1: \n\n{col_gradient}')

Output:

Last, we can try passing a tuple of ints to the axis argument.

import numpy as np

# axis = a tuple of ints
two_dim = np.array([[1, 2, 4, 8, 16],
                    [2, 5, 8, 10, 20]], dtype=float)
gradient = np.gradient(two_dim, axis=[0, 1])

print(f'axis = [0,1]: \n\n{gradient}')

Output:

Summary

That’s it for our np.gradient() article. 

We learned about its underlying concepts, syntax, arguments, and basic examples. 

We also worked on the top two questions about the np.gradient() function, ranging from np.gradient edge_order and np.gradient axis

Hope you enjoy all this and happy coding!