# Universal Functions in NumPy Hi Enthusiastic Learners! In this article we learn about many Universal Functions or Built-In functions of NumPy. Universal Functions plays a very crucial role in getting best performance out of NumPy and if you want to know advantages and effects on performance while using Universal Functions (UFuncs), you should go through our article Why use Universal Functions or Built in Functions in NumPy ?

And to learn basics of NumPy you can go through these 2 detailed articles, they will help you get a good understanding of creating & traversing NumPy arrays.

• Arithmetic Universal Functions
• Trigonometric Universal  Functions
• Exponent & Logarithmic Universal Functions

## Arithmetic Universal Functions¶

The biggest advantage of using UFuncs for Arithmetic Operations is that they all look same as that of our standard Mathematical operators, that is, you can simply use ‘`+`‘, ‘`-`‘, ‘`/`‘ & ‘`*`‘ for their Mathematical meaningful operations – Addition, Subtraction, Division & Multiplication respectively.

Let’s create an Array and try out these operations — just remember one thing when we are adding or subtracting a scalar value to or from an Array it will implement to all elements of that array.

In :
```import numpy as np

# Our Base Array
x = np.arange(1, 20, 3)
x
```
Out:
`array([ 1,  4,  7, 10, 13, 16, 19])`
In :
```print("x + 4 = " + str(x + 4))
print("x - 4 = " + str(x - 4))
print("x / 4 = " + str(x / 4))
print("x * 4 = " + str(x * 4))
```
```x + 4 = [ 5  8 11 14 17 20 23]
x - 4 = [-3  0  3  6  9 12 15]
x / 4 = [0.25 1.   1.75 2.5  3.25 4.   4.75]
x * 4 = [ 4 16 28 40 52 64 76]
```

Note: One interesting thing to note here is that NumPy automatically selects that which data-type to choose after any operation. In above example, when we divided integer values of array we got Float or Decimal values in output. Thus, it automatically chooses the higher data-set.

You can also perform following operations:

• Negate all values of an array
• Finding modulus of all values of array (remainder of values)
• Finding power of all numbers
In :
```print("Negate all values of array")
print ("-x \t= " + str(-x))

print("\nModulus of all numbers with 4")
print("x % 4 \t= " +str(x % 4))

print("\nCalculating power of all number with 3")
print("x ** 3 \t=" + str(x ** 3))
```
```Negate all values of array
-x 	= [ -1  -4  -7 -10 -13 -16 -19]

Modulus of all numbers with 4
x % 4 	= [1 0 3 2 1 0 3]

Calculating power of all number with 3
x ** 3 	=[   1   64  343 1000 2197 4096 6859]
```

Corresponding to above Mathematical operators we also have standard NumPy functions which are internally called whenever an operator is used. List of these functions is as follows:

• `+``np.add`
• `-``np.subtract`
• `/``np.divide`
• `*``np.multiply`
• `-val``np.negative`
• `%``np. mod`
• `*` `*``np.pow`

## Trigonometric Universal  Functions¶

We can could use trigonometric functions to find both standard results as well as inverse trigonometric results too.

Let’s begin with creating an array of different angles.

In :
```angle = np.arange(0,15, 4)
angle
```
Out:
`array([ 0,  4,  8, 12])`
In :
```print("tan(angle) = " + str(np.tan(angle)))
print("\nsin(angle) = " + str(np.sin(angle)))
print("\ncos(angle) = " + str(np.cos(angle)))
```
```tan(angle) = [ 0.          1.15782128 -6.79971146 -0.63585993]

sin(angle) = [ 0.         -0.7568025   0.98935825 -0.53657292]

cos(angle) = [ 1.         -0.65364362 -0.14550003  0.84385396]
```

Let’s see Inverse Trigonometric Functions too.

First create an array on which we will be applying inverse trigonometric functions to get corresponding angles.

In :
```values = [0, 1, -1]
values
```
Out:
`[0, 1, -1]`
In :
```print("arctan(values) = " + str(np.arctan(values)))
print("\narcsin(values) = " + str(np.arcsin(values)))
print("\narccos(values) = " + str(np.arccos(values)))
```
```arctan(values) = [ 0.          0.78539816 -0.78539816]

arcsin(values) = [ 0.          1.57079633 -1.57079633]

arccos(values) = [1.57079633 0.         3.14159265]
```

NumPy also provides us with a set of Hyperbolic Trigonometric functions.

Here is an example for them.

In :
```print("tanh(angle) = " + str(np.tanh(angle)))
print("\nsinh(angle) = " + str(np.sinh(angle)))
print("\ncosh(angle) = " + str(np.cosh(angle)))
```
```tanh(angle) = [0.         0.9993293  0.99999977 1.        ]

sinh(angle) = [0.00000000e+00 2.72899172e+01 1.49047883e+03 8.13773957e+04]

cosh(angle) = [1.00000000e+00 2.73082328e+01 1.49047916e+03 8.13773957e+04]
```

One thing to note here is that for getting Hyperbolic functions all you had to do was add an ‘h’ at end of each function. So, its very easy to remember.

And similarly, you can check Inverse Hyperbolic Functions — add ‘arc’ before function name and ‘h’ at the end. That is, arctanh(), arcsinh() and arccosh().

It make things easy to remember.

## Exponent & Logarithmic Universal Functions¶

Following is the list of Exponential Functions

• exp(x) — e^x
• expm1(x) — e^x — Used when ‘x’ is very small, it provides more accuracy in comparison to exp(), however it is a little bit slower. So, try using it only when you have very small values.
• exp2() — 2^x — Used only when calculating power of scalar value ‘2’
• power(n,x) — n^x — Any number raise to power ‘x’

Let’s see them in action.

In :
```# base array
x = np.arange(1, 8, 2)
x
```
Out:
`array([1, 3, 5, 7])`
In :
```print("-- e^x --")
print(np.exp(x))

print("\n-- 2^x --")
print(np.exp2(x))

print("\n-- 5^x --")
print(np.power(5, x))
```
```-- e^x --
[   2.71828183   20.08553692  148.4131591  1096.63315843]

-- 2^x --
[  2.   8.  32. 128.]

-- 5^x --
[    5   125  3125 78125]
```

#### exp(x) VS expm1(x) — which is better for precision¶

Begin with creating an array of small values.

In :
```x_small = np.array([0.01, 0.001, 0.0001, 0.00001])
x_small
```
Out:
`array([1.e-02, 1.e-03, 1.e-04, 1.e-05])`
In :
```print("EXP() -- Standard Function")
print(np.exp(x_small))

print("\nEXPM1() -- High Precision")
print(np.expm1(x_small))
```
```EXP() -- Standard Function
[1.01005017 1.0010005  1.00010001 1.00001   ]

EXPM1() -- High Precision
[1.00501671e-02 1.00050017e-03 1.00005000e-04 1.00000500e-05]
```

As you can see we get more precision while using `expm1()`.

Following is the list of Logarithmic Functions

• log(x) — Natural Log
• log1p(x) — Natural Log with high precision. Use it when value of ‘x’ is very small.
• log2(x) — Log with Base 2
• log10(x) — Log with Base 10

Let’s see how they work.

In :
```# base array
x = np.arange(1, 8, 2)
x
```
Out:
`array([1, 3, 5, 7])`
In :
```print("-- log(x) --")
print(np.log(x))

print("\n-- log2(x) --")
print(np.log2(x))

print("\n-- log10(x) --")
print(np.log10(x))
```
```-- log(x) --
[0.         1.09861229 1.60943791 1.94591015]

-- log2(x) --
[0.         1.5849625  2.32192809 2.80735492]

-- log10(x) --
[0.         0.47712125 0.69897    0.84509804]
```

#### log(x) VS logp(x) — which is better for precision¶

Begin with creating an array of small values.

In :
```print("LOG() -- Standard Log Function")
print(np.exp(x_small))

print("\nLOG() -- High Precision")
print(np.expm1(x_small))
```
```LOG() -- Standard Log Function
[1.01005017 1.0010005  1.00010001 1.00001   ]

LOG() -- High Precision
[1.00501671e-02 1.00050017e-03 1.00005000e-04 1.00000500e-05]
```

From results it is clear that for very small number we get higher precision when we use logp() function.

In our next tutorial we will be learning Aggregation Functions in depth, as we have covered only very few over here. There are a lot more functions that we need to explore yet.

So stay tuned & Keep Learning!!

And don’t forget to check our YouTube Channel ML For Analytics.