One simply can’t stress enough upon the importance of NumPy when it comes to programmers. Welcome to the second part of the blog where we shall cover some advanced but essential topics under NumPy. For those of you, who are joining us for the first time, it would be beneficial to check out the first part of our tutorial (Click here to see more: Basics of NumPy) where we introduced our readers to NumPy and its basic operations, like creating arrays, indexing, slicing, reshaping, and generating random numbers. So, let’s get started with the second part of our tutorial and take a look at the topics that we will learn.

**Section -1: **In this section we will learn about:

- Implementing Universal Functions (Ufuncs)
- Arithmetic Functions
- Trigonometric and Inverse Trigonometric Functions
- Exponents and Logarithms

- Implementing Advanced Ufuncs

**Introduction to Universal Functions (Ufuncs).**

Ufuncs in NumPy are simple mathematical functions. These functions include standard arithmetic operations, trigonometric functions, statistical functions, etc.

Ufuncs are used to *vectorize* operations and thereby remove slow Python loops.

** Arithmetic Functions **:

NumPy’s Arithmetic Ufuncs are quite similar to Python’s native arithmetic operations and hence operations like standard addition, subtraction, multiplication and division can all be easily used:

Each of these arithmetic operations has a specific NumPy wrapper. Some of the wrappers are:

+ `np.add` – `np.subtract` – `np.negative` * `np.multiply` / `np.divide` // `np.floor_divide` ** `np.power` % `np.mod` |

Built-in NumPy wrappers for arithmetic operations:

** Trigonometric functions**:

NumPy provides many trigonometric functions and some of them are most useful for data scientists, like sin, cos, tan. These trigonometric functions work on radians. Hence, it is important to convert angles into radians. This is done by multiplying the value by pi/180.

*Note: **An array of **degrees** can be converted into **radians** by NumPy function ***np.deg2rad([angles])**

Here, **np.linspace()** function generates N equally separated numbers between the specified start and end. np.pi is equal to 3.14159… radians which in turn is equal to 180 degrees.

** Inverse Trigonometric Functions**:

NumPy also provides various Inverse trigonometric functions. Unlike trigonometric functions, these functions take trigonometric values and return radians.

** Exponents and Logarithms**:

Another extremely useful operation available in NumPy Ufuncs is exponents.

In fact, logarithms are inverse of exponentials. The `np.log`

function is used to find natural logarithm. It provides two options to compute logarithms, either for base-2 or base-10.

** Advances Ufuncs**:

NumPy also provides some interesting aggregates that can be computed directly from the object. Eg. **reduce** and **accumulate** ufuncs.

Reduce on **np.add** ufuncs returns the sum of all elements in the array. Similarly, reduce on **np.multiply** ufuncs returns the product of all elements in the array.

Similar to **reduce**, if one would like to store all intermediate results of the computation, one can use **accumulate** in ufuncs.

** Multi dimensional Aggregation functions**:

Now, let’s move to the various aggregation functions that are provided by NumPy. The aggregation functions operate on unidimensional and multi-dimensional functions. These functions are designed to deal with null and uncertain values. Some of the multi dimensional aggregation functions are:

**Section -2: **In the next section of the blog, we will learn some other interesting topics related to NumPy. Let’s start with the following:

- Broadcasting
- Rules of Broadcasting
- Examples of Broadcasting

** Broadcasting**:

In simple terms. Broadcasting is simply a set of rules for performing binary Ufuncs. The term Broadcasting refers to how NumPy treats arrays with different dimensions during arithmetic operations which leads to certain constraints.

Broadcasting is an extremely useful feature of ufuncs to operate between arrays of different sizes and shapes. Let’s look at a simple example to understand it better:

*Rules of Broadcasting:*

Broadcasting in NumPy follows a strict set of rules to determine the interaction between the two arrays:

- Rule 1: If the two arrays differ in their number of dimensions, the shape of the one with fewer dimensions is padded with ones on its leading (left) side.
- Rule 2: If the shape of the two arrays does not match in any dimension, the array with shape equal to 1 in that dimension is stretched to match the other shape.
- Rule 3: If in any dimension, the sizes disagree and neither is equal to 1, an error is raised.

*Note:**Any NumPy array which undergoes any arithmetic operations should follow these 3 Broadcasting rules.*

** Broadcasting Example 1**:

Let’s consider adding a two-dimensional array to a one-dimensional array:

The shape of the arrays is as follows:

`M.shape = (2, 3)`

`a.shape = (3,)`

If we look at rule 1, it is clear that array **a** has fewer dimensions, so we pad it on the left with ones:

`M.shape -> (2, 3)`

`a.shape -> (1, 3)`

Using rule 2, we now see that the first dimension disagrees, so this dimension is stretched to match:

`M.shape -> (2, 3)`

`a.shape -> (2, 3)`

Finally, the shapes match and it is clear that the final shape will be **(2, 3):**

M + a = array([[ 1., 2., 3.], [ 1., 2., 3.]])

** Broadcasting Example 2**:

Now let’s take a look at an example in which the two arrays are not compatible:

The shape of the arrays is as follows:

`M.shape -> (3, 2)`

`a.shape -> (3, )`

Again, rule 1 tells us that we must pad the shape of **a** with ones:

`M.shape -> (3, 2)`

`a.shape -> (1, 3)`

By rule 2, the first dimension of **a** is stretched to match **M**.

`M.shape -> (3, 2)`

`a.shape -> (3, 3)`

In the end, we see that according to rule 3, the final shapes do not match. So, these two arrays are incompatible for attempting any operations.

**Import Jovian and Commit**:

**Conclusion:**

With this, we come to the end of PArt 2 of our **Basics of NumPy** series. We have managed to cover many topics, including sequencing, indexing, random numbers, reshaping, Ufuncs and Broadcasting. We sincerely hope that you have learnt not just the basics but have got a good understanding of the intermediate level of NumPy module as well. If you have any questions for us, please do get in touch.

**References:**

** Complete Jupyter Notebook** : https://jovian.ml/v-snehith999/numpy-basics-part-2

**Basics of NumPy – Part 1: **https://hub.jovian.ml/?p=603&preview=true

**Practice NumPy** **functions**: https://nbviewer.jupyter.org/github/rougier/numpy-100/blob/master/100_Numpy_exercises.ipynb

https://jakevdp.github.io/PythonDataScienceHandbook/02.05-computation-on-arrays-broadcasting.html

**Author:**

*– Snehit Vaddi*

I am a Machine Learning enthusiast. I teach machines how to see, listen, and learn.

**Linkedin: ****https://www.linkedin.com/in/snehit-vaddi-73a814158/**

**Github: ****https://github.com/snehitvadd****i**