*Image is from morioh.com*

### Hello and welcome

Glad to see that you're still on the journey with me. Now , all we are doing in these series is to built up our knowledge of *numpy* gradually , so we can apply it in solving some fun and engaging task.

Now , back to the basic operations on *NumPy* , in the previous post , we explained how addition and subtraction works in *NumPy* , but that is not all there is to it(other funny but loving behaviors shall be explained in this blog post), in this post , we shall continue with these operations , starting with multiplication.

### 3. Multiplication

Now, when it comes to multiplication of vectors , we have different types of multiplications, we can multiply an array by a constant or scalar , and this will be done to all the elements in the array, as shown below

```
1 import numpy as np # imports the numpy library as the alias np
2 x1=np.array([2,4,3,6])
3 print(2*x1) # multiplies each element by 2
# The result becomes
"""
[ 4 8 6 12]
"""
```

Arrays of equal shapes can also be multiplied. In such a situation , the multiplication is done element-wise , i.e elements having the same index/indices are multiplied. Check out the illustrations below.

```
1 y1=np.array([3,4,1])
2 y2=np.array([-1,9,-3])
3 y3=np.array([[3,4],[2,4]])
4 y4=np.array([[-2,3],[6,1]])
5 print(y1*y1) # performs element wise multiplication on y1 and y2
6 print(y3*y4) # Notice that y3 and y4 have the same shapes , hence the multiplication is possible
#The output is as shown below
"""
[-3 36 -3]
[[-6 12]
[12 4]]
"""
```

Notice that if one wants to achieve **matrix ** multiplication , then this method of multiplication is not the way to go (This will be covered in the next post on linear algebra).

### 3. Division

This follows the same principle as multiplication , that is the operation is done element-wise

```
1 y1=np.array([3,4,1])
2 y2=np.array([-1,9,-3])
3 y3=np.array([[3,4],[2,4]])
4 y4=np.array([[-2,3],[6,1]])
5 print(y1/y1)
6 print(y3/y4)
7 print(y1//y1) # performs element wise integer division
8 print(y3//y4) # performs element wise integer division
9 print(y1%y1) # performs the modulo operation(returns the remainder)
10 print(y3%y4) # performs the modulo operation(returns the remainder)
#The output is as shown below
"""
[-3. 0.44444444 -0.33333333]
[[-1.5 1.33333333]
[ 0.33333333 4. ]]
[-3 0 -1]
[[-2 1]
[ 0 4]]
[ 0 4 -2]
[[-1 1]
[ 2 0]]
"""
```

## Broadcasting

Now , some operations that are not valid in mathematics are valid in *NumPy *. Broadcasting is one of such operations. It is the act of performing operations on arrays that don't conform in shapes for such operations.
One good example is adding an array of shape *(1,)* to an array of shape *(4,)* . This operation turns out to be possible in *NumPy*.

```
1 z1=np.array([3,5,3,1])
2 z2=np.array([3))
3 print(z2*z1)
#The output is
"""
[9 15 9 3]
"""
```

In the program above , what happened is the array ** z2** is

*broadcasted*to give

*[3 3 3 3]*to conform with the shape of

**since this is what is possible. Note that**

*z1**broadcasting*doesn't work for all array shapes.

```
1 y5=np.array([[3,4],[6,2]])
2 y6=np.array([3,4])
3 print(y5+y6)
#The output is '
"""
[[ 7 8]
[ 9 6]]
"""
```

In the above snippet , *y6**broadcasted* to give *[[3,4],[3,4]]*, then these results are added to give the final output.

```
1 y5=np.array([[3,4],[6,2]])
2 y7=np.array([3,4,3])
3 print(y5+y7)
#This gives an error
ValueError Traceback (most recent call last)
<ipython-input-23-703742001c5a> in <module>
----> 1 print(y3+y6)
ValueError: operands could not be broadcast together with shapes (2,2) (3,)
```

## Universal functions

These are functions in *NumPy *that can operate on all elements of an array at once .
The following are universal functions in *NumPy*

### Trigonometric functions

```
1 x=np.array([4,5,3,5,3])
2 print("sin(x): ",np.sin(x)) # Returns the sine of each item in x
3 print("cos(x): ",np.cos(x)) # Returns the cosine of each item in x
4 print("tan(x): ",np.tan(x)) # Returns the tangent of each item in x
#The output is
"""
sin(x): [-0.7568025 -0.95892427 0.14112001 -0.95892427 0.14112001]
cosx: [-0.65364362 0.28366219 -0.9899925 0.28366219 -0.9899925 ]
tanx: [ 1.15782128 -3.38051501 -0.14254654 -3.38051501 -0.14254654]
"""
```

### Exponential and Logarithmic functions

```
1 x=np.array([4,5,3,5,3])
2 print(np.e**x) # e is the Naperian constant, with a value of 2. 71828..., hence each value of x is taken as the power of e
3 print(np.exp(x)) #Gives the same output as line 2
#Note that np.e produces a constant but np.exp is a function
4 print(np.log(x)) # returns the natural logarithm of each element
5 print(np.log10(x)) # returns the log to base 10 of each element of x
```

There are a lot of other universal functions , and will be discussed while applied in this series.

## Conclusion

Looking at these two articles , you can tell that *NumPy* can be applied in a host of areas , from science , engineering , e.t.c . It is rich with functions that performs very fast computation , which otherwise would have taken more time if we try to re-invent the wheels.
In the next post , i will be discussing ** Indexing and slicing**,

**,**

*Stacking and splitting***,**

*masking***Statistics**

*Linear algebra and****.

Remember, the power of practicing can not be over emphasized.

Have fun with ** NumPy** and i will see you in the next post.

This post is part of a series of blog post of the numpy library , based on the course

Practical Machine Learning Course from The Port Harcourt School of AI (pmlcourse).