# NumPy Linear Algebra

Numpy provides the following functions to perform the different algebraic calculations on the input data.

NumPy package contains numpy.linalg module that provides all the functionality required for linear algebra. Some of the important functions in this module are described in the following table.

dot

Dot product of the two arrays

vdot

Dot product of the two vectors

inner

Inner product of the two arrays

matmul

Matrix product of the two arrays

determinant

Computes the determinant of the array

solve

Solves the linear matrix equation

inv

Finds the multiplicative inverse of the matrix

1. Arrays in NumPy:

NumPy’s main object is the homogeneous multidimensional array.

• It is a table of elements (usually numbers), all of the same type, indexed by a tuple of positive integers.
• In NumPy dimensions are called axes. The number of axes is rank.
• NumPy’s array class is called ndarray. It is also known by the alias array.

Example :

`[[ 1, 2, 3], [ 4, 2, 5]]Here,rank = 2 (as it is 2-dimensional or it has 2 axes)first dimension(axis) length = 2, second dimension has length = 3overall shape can be expressed as: (2, 3)# Python program to demonstrate # basic array characteristicsimport numpy as np# Creating array objectarr = np.array( [[ 1, 2, 3],                 [ 4, 2, 5]] )# Printing type of arr objectprint("Array is of type: ", type(arr))# Printing array dimensions (axes)print("No. of dimensions: ", arr.ndim)# Printing shape of arrayprint("Shape of array: ", arr.shape)# Printing size (total number of elements) of arrayprint("Size of array: ", arr.size)# Printing type of elements in arrayprint("Array stores elements of type: ", arr.dtype)`

Output :

`Array is of type:  No. of dimensions:  2Shape of array:  (2, 3)Size of array:  6Array stores elements of type:  int64`

Numpy arrays are homogeneous in nature means it is an array that contains data of a single type only. Python’s lists and tuples, which are unrestricted in the type of data they contain. The concept of vectorized operations on NumPy allows the use of more optimal and pre-compiled functions and mathematical operations on NumPy array objects and data sequences. The Output and Operations will speed-up when compared to simple non-vectorized operations.

Example 1 : Using vectorized sum method on NumPy array. We will compare the vectorized sum method along with simple non-vectorized operation i.e the iterative method to calculate the sum of numbers from 0–14,999.

`# importing the modules`

`import` `numpy as np`

`import` `timeit`

`# vectorized sum`

`print(np.sum(np.arange(15000)))`

`print("Time taken by vectorized sum : ", end =` `"")`

`%timeit np.sum(np.arange(15000))`

`# itersative sum`

`total =` `0`

`for` `item in` `range(0, 15000):`

`total +=` `item`

`a =` `total`

`print("\n"` `+` `str(a))`

`print("Time taken by iterative sum : ", end =` `"")`

`%timeit a`

Output :

The above example shows the more optimal nature of vectorized operations of NumPy when compared with non-vectorized operations. This means when computational efficiency is the key factor in a program and we should avoid using these simple operations, rather we should use NumPy vectorized functions.

Numpy ufunc | Universal functions

Universal functions in Numpy are simple mathematical functions. It is just a term that we gave to mathematical functions in the Numpy library. Numpy provides various universal functions that cover a wide variety of operations.

These functions include standard trigonometric functions, functions for arithmetic operations, handling complex numbers, statistical functions, etc. Universal functions have various characteristics which are as follows-

• These functions operates on ndarray (N-dimensional array) i.e Numpy’s array class.
• It performs fast element-wise array operations.
• It supports various features like array broadcasting, type casting etc.
• Numpy, universal functions are objects those belongs to numpy.ufunc class.
• Python functions can also be created as a universal function using frompyfunc library function.
• Some ufuncs are called automatically when the corresponding arithmetic operator is used on arrays. For example when addition of two array is performed element-wise using ‘+’ operator then np.add() is called internally.

The term broadcasting refers to the ability of NumPy to treat arrays of different shapes during arithmetic operations. Arithmetic operations on arrays are usually done on corresponding elements. If two arrays are of exactly the same shape, then these operations are smoothly performed.

# Example 1

`import numpy as np a = np.array([1,2,3,4]) b = np.array([10,20,30,40]) c = a * b print c`

Its output is as follows −

`[10   40   90   160]`

If the dimensions of two arrays are dissimilar, element-to-element operations are not possible. However, operations on arrays of non-similar shapes is still possible in NumPy, because of the broadcasting capability. The smaller array is broadcast to the size of the larger array so that they have compatible shapes.

Broadcasting is possible if the following rules are satisfied −

• Array with smaller ndim than the other is prepended with ‘1’ in its shape.
• Size in each dimension of the output shape is maximum of the input sizes in that dimension.
• An input can be used in calculation, if its size in a particular dimension matches the output size or its value is exactly 1.
• If an input has a dimension size of 1, the first data entry in that dimension is used for all calculations along that dimension.

A set of arrays is said to be broadcastable if the above rules produce a valid result and one of the following is true −

• Arrays have exactly the same shape.
• Arrays have the same number of dimensions and the length of each dimension is either a common length or 1.
• Array having too few dimensions can have its shape prepended with a dimension of length 1, so that the above stated property is true.

The following program shows an example of broadcasting.

# Example 2

`import numpy as np a = np.array([[0.0,0.0,0.0],[10.0,10.0,10.0],[20.0,20.0,20.0],[30.0,30.0,30.0]]) b = np.array([1.0,2.0,3.0])     print 'First array:' print a print '\n'     print 'Second array:' print b print '\n'     print 'First Array + Second Array' print a + b`

The output of this program would be as follows −

`First array:[[ 0. 0. 0.] [ 10. 10. 10.] [ 20. 20. 20.] [ 30. 30. 30.]]Second array:[ 1. 2. 3.]First Array + Second Array[[ 1. 2. 3.] [ 11. 12. 13.] [ 21. 22. 23.] [ 31. 32. 33.]]`

The following figure demonstrates how array b is broadcast to become compatible with a.

Boolean masking is typically the most efficient way to quantify a sub-collection in a collection. Masking in python and data science is when you want manipulated data in a collection based on some criteria. The criteria you use is typically of a true or false nature, hence the boolean part. They can also be used for indexing but it is very different as compare to index arrays.

# Understand it by taking an example

Problem Statement: Imagine a situation where we want to get all the values from the array which is less then the mean of the entire array.

Solution : Firstly creating an array (Consider 2D Arrays)

Secondly, Calculating mean

Calculate mean using mean attribute of the array a.

According to problem we need the values less than mean-

One way is to run the for loop over an array and get them.
But, using numpy masking we do this in one single line.

Thirdly, Each value of an array compared with the mean , if it is less we retain the value — Creating a mask and will do a mask everywhere, where a is less than mean.

Let’s see the Output-

Matrix a with boolean values in every spot

True — where values are less than mean.

False — values are greater than mean.

Lastly, retrieving all the values of a that satisfy mask-

All values are less than mean

# Counting entries

To count the number of True entries in a Boolean array, np.count_nonzero is useful.

We see that there are 10 array entries that are less than mean.

Another way to get at this information is to use np.sum

# Basic Datetimes

The most basic way to create datetimes is from strings in ISO 8601 date or datetime format. It is also possible to create datetimes from an integer by offset relative to the Unix epoch (00:00:00 UTC on 1 January 1970). The unit for internal storage is automatically selected from the form of the string, and can be either a date unit or a time unit. The date units are years (‘Y’), months (‘M’), weeks (‘W’), and days (‘D’), while the time units are hours (‘h’), minutes (‘m’), seconds (‘s’), milliseconds (‘ms’), and some additional SI-prefix seconds-based units. The datetime64 data type also accepts the string “NAT”, in any combination of lowercase/uppercase letters, for a “Not A Time” value.

Example

A simple ISO date:

`>>> np.datetime64('2005-02-25')numpy.datetime64('2005-02-25')`

From an integer and a date unit, 1 year since the UNIX epoch:

`>>> np.datetime64(1, 'Y')numpy.datetime64('1971')`

Using months for the unit:

`>>> np.datetime64('2005-02')numpy.datetime64('2005-02')`

Specifying just the month, but forcing a ‘days’ unit:

`>>> np.datetime64('2005-02', 'D')numpy.datetime64('2005-02-01')`

From a date and time:

`>>> np.datetime64('2005-02-25T03:30')numpy.datetime64('2005-02-25T03:30')`

NAT (not a time):

`>>> np.datetime64('nat')numpy.datetime64('NaT')`