Monday 17 August 2020

NumPy - I

Today I received a mail from a friend complaining that there are no new articles. While writing a new post on the blog had been running on my mind for the last few months, for lack of motivation, I was dilly dallying and resorted to the easy way out: procrastinating. VoilĂ , a shot in the arm and we are back in business

In this post, we will attempt to unveil some important aspects of NumPy. NumPy as we know today was released as NumPy 1.0 in 2006. As of today when we write this article, the version is 1.19.0. The Python versions supported by this release are 3.6-3.8. NumPy is the fundamental package for scientific computing in Python. At the centre of the NumPy package, a Python library, is the ndarray object or a n-dimensional arrays of  homogeneous elements

We will be using Jupyter notebook for all the work in this article. The code is in blue font and output is in green font below the code. The version details are given below:

import sys
print("Python version:", sys.version)

import numpy as np
print("NumPy version:", np.__version__)

Python version: 3.8.3 (default, Jul  2 2020, 17:30:36) [MSC v.1916 64 bit (AMD64)]
NumPy version: 1.18.5

Let's start with creating a few NumPy arrays and then calling them immediately:

numpy_array1 = np.array([1, 2, 3, 4, 5])
numpy_array1

array([1, 2, 3, 4, 5])

numpy_array2 = np.array([True, False, True, False, True], dtype = bool)
numpy_array2
 

array([ True, False,  True, False,  True])

numpy_array3 = np.array([1.1, 2.2, 3.3, 4.4, 5.5])
numpy_array3

array([1.1, 2.2, 3.3, 4.4, 5.5])

numpy_array4 = np.array([1, 2, 3, 4, 5], dtype = np.uint8) ## dtype is Unsigned integer (0 to 255)
numpy_array4

array([1, 2, 3, 4, 5], dtype=uint8)

numpy_array5 = np.array(['NumPy',"is","the",'fundamental',"package",'for',"scientific",'computing'])
numpy_array5

array(['NumPy', 'is', 'the', 'fundamental', 'package', 'for',
       'scientific', 'computing'], dtype='<U11')

We can also create NumPy arrays from list:

python_list = [6, 7, 8, 9, 10]
numpy_array6 = np.array(python_list)
numpy_array6

array([ 6,  7,  8,  9, 10])

Few more examples of NumPy arrays creation are shown below:

String = "1.1 2.2 3.3 4.4 5.5"
numpy_array7 = np.fromstring(String, dtype = np.double, sep = " ")
numpy_array7

array([1.1, 2.2, 3.3, 4.4, 5.5])

numpy_array8 = np.zeros((5,2), dtype = int) ## initializes with zeros
numpy_array8

array([[0, 0],
       [0, 0],
       [0, 0],
       [0, 0],
       [0, 0]])

numpy_array9 = np.eye(3, dtype = float) ## initializes with zeros but with ones along dialgonal
numpy_array9

array([[1., 0., 0.],
       [0., 1., 0.],
       [0., 0., 1.]])


numpy_array10 = np.full((5, 4), 10) ## completes array with value for given shape
numpy_array10

array([[10, 10, 10, 10],
       [10, 10, 10, 10],
       [10, 10, 10, 10],
       [10, 10, 10, 10],
       [10, 10, 10, 10]])

To check the properties of arrays, we can use the following commands:

numpy_array11 = np.array([[ 1, 2, 3, 4,  5,  6,  7,  8],
                          [ 6, 7, 8, 9, 10, 11, 12, 13]])
numpy_array11

array([[ 1,  2,  3,  4,  5,  6,  7,  8],
       [ 6,  7,  8,  9, 10, 11, 12, 13]])

numpy_array11.shape  ## shape

(2, 8)

numpy_array11.size  ## size

16

numpy_array11.ndim ## number of dimensions

2

numpy_array11.itemsize ## size of each item in byte
s

4

numpy_array11.nbytes ##size in bytes of all elements


64

numpy_array11.dtype ## type of elements


dtype('int32')

len(numpy_array11) ## length of array


2

Operations on NumPy arrays are shown in the next few commands:

numpy_array_sum = numpy_array1 + numpy_array2 # Addition
numpy_array_sum


array([2, 2, 4, 4, 6])

numpy_array_difference = numpy_array1 - numpy_array2 # Subtraction
numpy_array_difference

array([0, 2, 2, 4, 4])

numpy_array_product = numpy_array1 * numpy_array2 # Multiplication
numpy_array_product


array([1, 0, 3, 0, 5])

numpy_array_quotient = numpy_array1 / numpy_array1 # Division
numpy_array_quotient


array([1., 1., 1., 1., 1.])

numpy_array_square_root = numpy_array1 ** 0.5 # square root
numpy_array_square_root


array([1.        , 1.41421356, 1.73205081, 2.        , 2.23606798])


numpy_array_raised_power = numpy_array1 ** 3 # Exponentiation
numpy_array_raised_power


array([  1,   8,  27,  64, 125], dtype=int32)


numpy_array_sin = np.sin(numpy_array1) # array value treated as radians
numpy_array_sin


array([ 0.84147098,  0.90929743,  0.14112001, -0.7568025 , -0.95892427])


numpy_array_log = np.log(numpy_array1) # natural logarithm
numpy_array_log

array([0.        , 0.69314718, 1.09861229, 1.38629436, 1.60943791])

Some of the constants that we may encounter in our line of work:

np.e

2.718281828459045

np.pi

3.141592653589793

np.inf

inf

np.Inf

inf

np.NINF

-inf

np.NAN

nan

np.NZERO

-0.0

np.PZERO

0.0

To compare two NumPy arrays, we can use the following commands:

np.equal(numpy_array2, numpy_array1)  #Element wise comparison

array([ True, False, False, False, False])


numpy_array1 == numpy_array2  #Element wise comparison


array([ True, False, False, False, False])


np.array_equal(numpy_array1, numpy_array1)  # Array Comparison

True

np.array_equal(numpy_array1, numpy_array2)  # Array Comparison


False

Broadcasting in NumPy: For operations between arrays, a comparison is first made of their shapes element-wise. If the shapes are the same, then, no broadcasting is applied. But, if the sizes are not the same, then, for the operation to succeed, the size of  trailing axes for both arrays in an operation must either be the same size or one of them must be one. Else, a ValueError: operands could not be broadcast together with shapes ... exception is thrown, indicating that the arrays have incompatible shapes and broadcasting could not be applied

numpy_array12 = np.arange(1,16).reshape(3,5)
numpy_array12


array([[ 1,  2,  3,  4,  5],
       [ 6,  7,  8,  9, 10],
       [11, 12, 13, 14, 15]])


numpy_array12 + numpy_array12 # same shape so, no problem


array([[ 2,  4,  6,  8, 10],
       [12, 14, 16, 18, 20],
       [22, 24, 26, 28, 30]])


numpy_array13 = np.array([1])
numpy_array13


array([1])


numpy_array12 + numpy_array13 # broadcasting is valid as size of trailing axis of numpy_array13 is 1

array([[ 2,  3,  4,  5,  6],
       [ 7,  8,  9, 10, 11],
       [12, 13, 14, 15, 16]])


numpy_array14 = np.array([1, 2, 3, 4, 5])
numpy_array14


array([1, 2, 3, 4, 5])

numpy_array12 + numpy_array14 # broadcasting is valid as size of trailing axes of both arrays is 5 and is same

array([[ 2,  4,  6,  8, 10],
       [ 7,  9, 11, 13, 15],
       [12, 14, 16, 18, 20]])

numpy_array15 = np.array([1, 2])
numpy_array15

array([1, 2])

numpy_array12 + numpy_array15  # error will be thrown

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-32-ff4233b2f95d> in <module>
----> 1 numpy_array12 + numpy_array15  # error will be thrown

ValueError: operands could not be broadcast together with shapes (3,5) (2,) 
 
With this concept of Broadcasting in NumPy, we come to the end the first post on NumPy