Numpy Tutorial with Jupyter notebook

{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Need for Numpy :**\n",
    "\n",
    "If we have two lists and simply want to add the elements, we would have to iterate over each element of both lists and add them. Just using \"+\" operator will concatenate the lists.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[1, 2, 3, 4, 5, 6, 7, 8]\n"
     ]
    }
   ],
   "source": [
    "a = [1, 2, 3, 4]\n",
    "b = [5, 6, 7, 8]\n",
    "\n",
    "print (a+b)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[6, 8, 10, 12]\n"
     ]
    }
   ],
   "source": [
    "r = []\n",
    "for x,y in zip(a,b):\n",
    "    r.append(x+y)\n",
    "\n",
    "print (r)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Numpy :**\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([0., 2., 4.])"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Array Creation\n",
    "\n",
    "#New Arrays can be created by using array function. \n",
    "#This function takes list as an argument and create N-Dimensional array based on the arguments. \n",
    "\n",
    "# One Dimensional Array\n",
    "ar1 = np.array([1,2,3])\n",
    "\n",
    "# Two Dimensional Array\n",
    "ar2 = np.array([[1,2,3], [4,5,6]])\n",
    "\n",
    "# Other methods to create new Arrays \n",
    "# There are several other ways to create Arrays :-\n",
    "\n",
    "# arange([start], [stop], [step]) \n",
    "# This is similar to Python range function and creates evenly spaced arrays.\n",
    "\n",
    "np.arange(5)\n",
    "# OUT : array([0, 1, 2, 3, 4])\n",
    "\n",
    "np.arange(2,10,2) # Excludes the stop position element\n",
    "# OUT : array([2, 4, 6, 8]) \n",
    "\n",
    "# linspace([start], [stop], [num]) \n",
    "# Creates array by number of points.\n",
    "\n",
    "np.linspace(0,6,3)\n",
    "# Creates 3 evenly spaced elements between start and stop point. \n",
    "# Note, stop point value is included. \n",
    "# OUT : array([0., 3., 6.])\n",
    "\n",
    "np.linspace(0,6,3,endpoint=False) # Excludes the stop position element\n",
    "#OUT : array([0., 2., 4.])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "numpy.ndarray"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Type of array\n",
    "a = np.array([1, 2, 3, 4, 5])\n",
    "\n",
    "type(a)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "dtype('int64')"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Data-type : Every element inside this array will be of this type\n",
    "\n",
    "a.dtype"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "dtype('float64')"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Another array with floating members\n",
    "\n",
    "f = np.array([1.2, 33.5, 6.4, 7.8, 8.6])\n",
    "f.dtype"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([10,  2,  3,  4,  5])"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "a[0] = 10\n",
    "a"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([6, 2, 3, 4, 5])"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Decimal portion will be truncated because all elements has to be of same type\n",
    "\n",
    "a[0] = 6.88\n",
    "a"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Number of dimension\n",
    "\n",
    "a.ndim"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(5,)"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Returns a tuple showing number of elements across each dimension\n",
    "\n",
    "a.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "5"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Total number of elements\n",
    "\n",
    "a.size"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "8\n",
      "8\n"
     ]
    }
   ],
   "source": [
    "# Bytes per element\n",
    "\n",
    "print(a.itemsize)\n",
    "print(f.itemsize)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "40"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Bytes used by data portion of array\n",
    "\n",
    "a.nbytes"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Array is :\n",
      " [[1 2 3 4]\n",
      " [5 6 7 8]]\n",
      "\n",
      "\n",
      "Size is :  8\n",
      "Dimension is :  2\n",
      "Shape is :  (2, 4)\n"
     ]
    }
   ],
   "source": [
    "# Multi-Dimensional Arrays\n",
    "\n",
    "# A 2-D array is basically a list of list\n",
    "# A 3-D array will be a list of list of list\n",
    "\n",
    "a = np.array([[1, 2, 3, 4],\n",
    "             [5, 6, 7, 8]])\n",
    "\n",
    "print(\"Array is :\\n\",a)\n",
    "print(\"\\n\")\n",
    "print(\"Size is : \",a.size)\n",
    "print(\"Dimension is : \",a.ndim)\n",
    "print(\"Shape is : \",a.shape)\n",
    "\n",
    "# NOTE : \n",
    "# Dimension 0 is row, dimension 1 is column. So, in this case, we have a tuple (2,4) indicating 2 rows and \n",
    "# 4 elements in each row"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Following picture illustartes the dimension/axis for multi-dimensional array :-\n",
    "\n",
    "![p2PGi.png](https://i.stack.imgur.com/p2PGi.png)\n",
    "\n",
    "Source : http://physics.cornell.edu/"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Element in first row and fourth column is  4\n",
      "Element in second row and first column is  5\n",
      "Elements in second row [5 6 7 8]\n",
      "Array after changing first element of second row :\n",
      " [[ 1  2  3  4]\n",
      " [10  6  7  8]]\n",
      "Array after changing second row :\n",
      " [[ 1  2  3  4]\n",
      " [10 11 12 13]]\n"
     ]
    }
   ],
   "source": [
    "# Retrieving/Setting individual element from a 2-D array :-\n",
    "\n",
    "# Syntax : Array[row,column]\n",
    "\n",
    "print(\"Element in first row and fourth column is \",a[0,3])\n",
    "print(\"Element in second row and first column is \",a[1,0])\n",
    "\n",
    "# Retrieving all elements of a row :-\n",
    "# If you specify only first parameter (row index), then all elements of that row are returned\n",
    "\n",
    "print(\"Elements in second row\", a[1])\n",
    "\n",
    "# Setting an element \n",
    "a[1,0] = 10\n",
    "print(\"Array after changing first element of second row :\\n\", a)\n",
    "\n",
    "a[1] = [10, 11 ,12 ,13]\n",
    "print(\"Array after changing second row :\\n\", a)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Array Slicing :-**\n",
    "\n",
    "An array is sliced with the following syntax, which extracts the sequence based on lower and upper bound\n",
    "\n",
    "*Array[start:stop:step]*\n",
    "\n",
    "Note, that the lower (start) bound element is included but the upper(stop) bound element is not included. Step value defines the stride.\n",
    "\n",
    "Just like Python lists, the array is represented with indices from both directions. Sequence can be extracted with the above syntax and combinations of positive/negative indices. \n",
    "\n",
    "NOTE : If boundaries are ommited, it is considered as starting (or ending) of a list."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Second, third and fourth element : a[1:4] :- [11 12 13]\n",
      "\n",
      "Same elements can be extracted with following notations as well ...\n",
      "a[-5:-2] :-  [11 12 13]\n",
      "a[-5:4] :-  [11 12 13]\n",
      "a[1:-2] :-  [11 12 13]\n",
      "\n",
      "Extract first three elements ...\n",
      "a[:3] :-  [10 11 12]\n",
      "\n",
      "Extract last three elements ...\n",
      "a[-3:] :-  [13 14 15]\n",
      "a[3:] :-  [13 14 15]\n",
      "\n",
      "Extract every other element ...\n",
      "a[::2] :-  [10 12 14]\n"
     ]
    }
   ],
   "source": [
    "a = np.array([10, 11, 12, 13, 14, 15])\n",
    "\"\"\"\n",
    " +---+---+---+---+---+---+\n",
    " | 10| 11| 12| 13| 14| 15|\n",
    " +---+---+---+---+---+---+\n",
    "   0   1   2   3   4   5   \n",
    "  -6  -5  -4  -3  -2  -1\n",
    "\"\"\"    \n",
    "    \n",
    "# Extract the second, third and fourth element\n",
    "\n",
    "print(\"Second, third and fourth element : a[1:4] :-\", a[1:4])\n",
    "print(\"\\nSame elements can be extracted with following notations as well ...\")\n",
    "print(\"a[-5:-2] :- \", a[-5:-2])\n",
    "print(\"a[-5:4] :- \", a[-5:4])\n",
    "print(\"a[1:-2] :- \", a[1:-2])\n",
    "\n",
    "# Extract first three elements\n",
    "print(\"\\nExtract first three elements ...\")\n",
    "print(\"a[:3] :- \", a[:3])\n",
    "\n",
    "# Extract last three elements\n",
    "print(\"\\nExtract last three elements ...\")\n",
    "print(\"a[-3:] :- \", a[-3:])\n",
    "print(\"a[3:] :- \", a[3:])\n",
    "\n",
    "# Extract every other element\n",
    "print(\"\\nExtract every other element ...\")\n",
    "print(\"a[::2] :- \", a[::2])\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[10 11  2  3  4  5]\n"
     ]
    }
   ],
   "source": [
    "# Inserting values \n",
    "\n",
    "a[2:] = [2, 3, 4, 5]\n",
    "\n",
    "print(a)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Original Array\n",
      "[[ 0  1  2  3  4]\n",
      " [ 5  6  7  8  9]\n",
      " [10 11 12 13 14]\n",
      " [15 16 17 18 19]\n",
      " [20 21 22 23 24]]\n",
      "\n",
      "\n",
      "[20 21 22 23 24]\n",
      "\n",
      "\n",
      "[[ 1  3]\n",
      " [ 6  8]\n",
      " [11 13]\n",
      " [16 18]\n",
      " [21 23]]\n",
      "\n",
      "\n",
      "[[ 5  7]\n",
      " [15 17]]\n"
     ]
    }
   ],
   "source": [
    "# More on slicing with an example using 2-D data\n",
    "\n",
    "a = np.arange(25).reshape(5,5)\n",
    "print(\"Original Array\")\n",
    "print(a)\n",
    "print(\"\\n\")\n",
    "print(a[4])\n",
    "print(\"\\n\")\n",
    "print(a[:,1::2])\n",
    "print(\"\\n\")\n",
    "print(a[1::2,:3:2])\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[-1  2  3  4  5]\n"
     ]
    }
   ],
   "source": [
    "a = np.array([1, 2, 3, 4, 5])\n",
    "b = a[:3]\n",
    "\n",
    "b[0] = -1\n",
    "print(a)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [],
   "source": [
    "c = a.copy()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Fancy Indexing :-**\n",
    "\n",
    "To understand this better, let's s"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[71 21 92  4 88 21 11 72 43 33 62 17 73 32 34]\n",
      "Element at index 1 is 21\n",
      "Element at index 5 is 21\n",
      "Element at index 13 is 32\n",
      "Elements at indexes [1, 5, 13] are [21 21 32]\n"
     ]
    }
   ],
   "source": [
    "# Fancy Indexing - Index Based : (1-D)\n",
    "\n",
    "# To understand this better, let's say we have an array (1-D for simplicity) with some random numbers\n",
    "\n",
    "a = np.random.randint(1, 100, 15)  # Will create an array of 15 elements between 1 & 100\n",
    "\n",
    "print (a)\n",
    "\n",
    "# Now, say we need to extract the element(s) at index 1, 5 and 13. One way is to extract each item individually\n",
    "\n",
    "print(\"Element at index {} is {}\".format(1, a[1]))\n",
    "print(\"Element at index {} is {}\".format(5, a[5]))\n",
    "print(\"Element at index {} is {}\".format(13, a[13]))\n",
    "\n",
    "# However, Numpy offers another approach where we could just a pass of list of indexes for which we want to\n",
    "# retrieve elements\n",
    "\n",
    "index = [1, 5, 13]\n",
    "\n",
    "print(\"Elements at indexes {} are {}\".format(index, a[index]))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Original array is [ 1  3  4  5  6  8 10  3  1]\n",
      "Mask array is [False False  True False  True  True  True False False]\n",
      "Even numbers are \n",
      "[ 4  6  8 10]\n"
     ]
    }
   ],
   "source": [
    "# Fancy Indexing - Boolean Array Indexing (1-D)\n",
    "\n",
    "# Consider we have an array of odd and even numbers. \n",
    "# Our task is to find out even elements\n",
    "\n",
    "a = np.array([1, 3, 4, 5, 6, 8, 10, 3, 1])\n",
    "\n",
    "# Numpy allows elements to be retrieved by a boolean array i.e. elements will be returned for True element\n",
    "\n",
    "mask = (a % 2 == 0)\n",
    "\n",
    "print(\"Original array is {}\".format(a))\n",
    "print(\"Mask array is {}\".format(mask))\n",
    "print(\"Even numbers are \")\n",
    "print(a[mask]) "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Original array is \n",
      " [[ 0  1  2  3  4]\n",
      " [ 5  6  7  8  9]\n",
      " [10 11 12 13 14]\n",
      " [15 16 17 18 19]\n",
      " [20 21 22 23 24]]\n",
      "\n",
      "\n",
      "Contents at index (or row) 3 is [15 16 17 18 19]\n",
      "Contents at index (or row) 2 is [10 11 12 13 14]\n"
     ]
    }
   ],
   "source": [
    "# Fancy Indexing - Index Based : (2-D)\n",
    "\n",
    "# Fancy indexing can also be performed on a 2-D array\n",
    "\n",
    "a = np.arange(25).reshape(5,5)\n",
    "\n",
    "# If we have a 2-D array, passing a single index will return the entire row\n",
    "\n",
    "print(\"Original array is \\n {}\".format(a))\n",
    "\n",
    "print(\"\\n\")\n",
    "print(\"Contents at index (or row) {} is {}\".format(3,a[3]))\n",
    "print(\"Contents at index (or row) {} is {}\".format(2,a[2]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "a[3,2] : This will print the element at 3rd row and 2nd column :\n",
      "17\n",
      "\n",
      "\n",
      "a[[3,2]] : This will print the contents for 3rd and 2nd row :\n",
      "[[15 16 17 18 19]\n",
      " [10 11 12 13 14]]\n",
      "\n",
      "\n",
      "a[[0,2,4],[0,3,0]] : This will print elements at (0,0), (2,3) & (4,0) :\n",
      "[ 0 13 20]\n",
      "\n",
      "\n",
      "a[[0,2,4]][:] : This will print all elements at row 0, 2 & 4 :\n",
      "[[ 0  1  2  3  4]\n",
      " [10 11 12 13 14]\n",
      " [20 21 22 23 24]]\n",
      "[[ 0  1  2  3  4]\n",
      " [10 11 12 13 14]\n",
      " [20 21 22 23 24]]\n",
      "\n",
      "\n",
      "a[[0,2,4]][:,[0,1,3]] : This will print elements at row 0, 2 & 4 AND columns 0, 1 & 3 :\n",
      "[[ 0  1  3]\n",
      " [10 11 13]\n",
      " [20 21 23]]\n"
     ]
    }
   ],
   "source": [
    "# If we pass multiple indexes (as a list), we get the data for these entire rows\n",
    "\n",
    "print(\"a[3,2] : This will print the element at 3rd row and 2nd column :\")\n",
    "print(a[3,2])\n",
    "print(\"\\n\")\n",
    "\n",
    "print(\"a[[3,2]] : This will print the contents for 3rd and 2nd row :\")\n",
    "print(a[[3,2]])\n",
    "print(\"\\n\")\n",
    "\n",
    "print(\"a[[0,2,4],[0,3,0]] : This will print elements at (0,0), (2,3) & (4,0) :\")\n",
    "print(a[[0,2,4],[0,3,0]])\n",
    "print(\"\\n\")\n",
    "\n",
    "print(\"a[[0,2,4]][:] : This will print all elements at row 0, 2 & 4 :\")\n",
    "print(a[[0,2,4]][:])\n",
    "print(a[[0,2,4]])\n",
    "print(\"\\n\")\n",
    "\n",
    "print(\"a[[0,2,4]][:,[0,1,3]] : This will print elements at row 0, 2 & 4 AND columns 0, 1 & 3 :\")\n",
    "print(a[[0,2,4]][:,[0,1,3]])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[ 0  1  2  3  4]\n",
      " [ 5  6  7  8  9]\n",
      " [10 11 12 13 14]\n",
      " [15 16 17 18 19]\n",
      " [20 21 22 23 24]]\n"
     ]
    }
   ],
   "source": [
    "print(a)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Original array is \n",
      " [[ 0  1  2  3  4]\n",
      " [ 5  6  7  8  9]\n",
      " [10 11 12 13 14]\n",
      " [15 16 17 18 19]\n",
      " [20 21 22 23 24]] \n",
      "\n",
      "Mask array is \n",
      " [[False  True False  True False]\n",
      " [ True False  True False  True]\n",
      " [False  True False  True False]\n",
      " [ True False  True False  True]\n",
      " [False  True False  True False]] \n",
      "\n",
      "Odd numbers are \n",
      "[ 1  3  5  7  9 11 13 15 17 19 21 23]\n"
     ]
    }
   ],
   "source": [
    "# Boolean Array Indexing (2-D)\n",
    "\n",
    "# Boolean Array indexing will work similar to 1-D. Let's use the similar example to find all odd elements\n",
    "\n",
    "mask = (a % 2 != 0)\n",
    "\n",
    "print(\"Original array is \\n {} \\n\".format(a))\n",
    "print(\"Mask array is \\n {} \\n\".format(mask))\n",
    "print(\"Odd numbers are \")\n",
    "print(a[mask]) \n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Vectorization :**\n",
    "\n",
    "In high-level languages the term vectorization refers to use of pre-compiled, optimized code written in language like C to perform mathematical operations over sequence of data. Basically, this is done without writing \"for\" loop in Python.\n",
    "\n",
    "Python native list allows elements to be of different data types as opposed to Numpy array where the elements have to be of same data type. This property allows mathematical operations to be delecated to pre-compiled code written in C to gain performance improvements.\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Array a is \n",
      " [[ 0  1  2  3]\n",
      " [ 4  5  6  7]\n",
      " [ 8  9 10 11]\n",
      " [12 13 14 15]] \n",
      "\n",
      " Array b is \n",
      " [[ 0  1  2  3]\n",
      " [ 4  5  6  7]\n",
      " [ 8  9 10 11]\n",
      " [12 13 14 15]] \n",
      "\n",
      "a + 2 : Adding 2 to each element :-  \n",
      " [[ 2  3  4  5]\n",
      " [ 6  7  8  9]\n",
      " [10 11 12 13]\n",
      " [14 15 16 17]] \n",
      "\n",
      "a - 2 : Subtracting 2 from each element :-  \n",
      " [[-2 -1  0  1]\n",
      " [ 2  3  4  5]\n",
      " [ 6  7  8  9]\n",
      " [10 11 12 13]] \n",
      "\n",
      "a / 2 : Dividing 2 from each element :-  \n",
      " [[0.  0.5 1.  1.5]\n",
      " [2.  2.5 3.  3.5]\n",
      " [4.  4.5 5.  5.5]\n",
      " [6.  6.5 7.  7.5]] \n",
      "\n",
      "a * 2 : Multiplying 2 from each element :-  \n",
      " [[ 0  2  4  6]\n",
      " [ 8 10 12 14]\n",
      " [16 18 20 22]\n",
      " [24 26 28 30]] \n",
      "\n"
     ]
    }
   ],
   "source": [
    "a = np.arange(16).reshape(4,4)\n",
    "b = np.arange(16).reshape(4,4)\n",
    "\n",
    "print(\"Array a is \\n\",a,\"\\n\\n\",\"Array b is \\n\",b, \"\\n\")\n",
    "\n",
    "# Element wise operation\n",
    "\n",
    "print(\"a + 2 : Adding 2 to each element :- \",\"\\n\",a+2, \"\\n\")\n",
    "print(\"a - 2 : Subtracting 2 from each element :- \",\"\\n\",a-2, \"\\n\")\n",
    "print(\"a / 2 : Dividing 2 from each element :- \",\"\\n\",a/2, \"\\n\")\n",
    "print(\"a * 2 : Multiplying 2 from each element :- \",\"\\n\",a*2, \"\\n\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "a + b : Adding each element of array 'a' to element of 'b' :-  \n",
      " [[ 0  2  4  6]\n",
      " [ 8 10 12 14]\n",
      " [16 18 20 22]\n",
      " [24 26 28 30]] \n",
      "\n",
      "a - b : Subtracting each element of array 'a' to element of 'b' :-  \n",
      " [[0 0 0 0]\n",
      " [0 0 0 0]\n",
      " [0 0 0 0]\n",
      " [0 0 0 0]] \n",
      "\n",
      "a * b : Multiplying each element of array 'a' to element of 'b' :-  \n",
      " [[  0   1   4   9]\n",
      " [ 16  25  36  49]\n",
      " [ 64  81 100 121]\n",
      " [144 169 196 225]] \n",
      "\n"
     ]
    }
   ],
   "source": [
    "# Array based operations :-\n",
    "\n",
    "print(\"a + b : Adding each element of array 'a' to element of 'b' :- \",\"\\n\",a+b, \"\\n\")\n",
    "print(\"a - b : Subtracting each element of array 'a' to element of 'b' :- \",\"\\n\",a-b, \"\\n\")\n",
    "print(\"a * b : Multiplying each element of array 'a' to element of 'b' :- \",\"\\n\",a*b, \"\\n\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "120\n",
      "[24 28 32 36]\n",
      "[ 6 22 38 54]\n"
     ]
    }
   ],
   "source": [
    "# Sequence based operations\n",
    "\n",
    "# These functions are called usoc or universal functions which operate on each element\n",
    "\n",
    "print(np.sum(a)) # Add each element in array\n",
    "\n",
    "print(np.sum(a,axis=0)) # Add each element across axis-0 (rows) - vertical direction \n",
    "\n",
    "print(np.sum(a,axis=1)) # Add each element across axis-1 (columns)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Broadcasting :-**\n",
    "\n",
    "When we perform arithemetic operations on two arrays, the operations are performed element wise. One condition to perform such operations is that the arrays should be of same shape.\n",
    "\n",
    "Broadcasting is a technique used by Numpy to perform operations on arrays of different shapes. \n",
    "\n",
    "\n",
    "Subject to certain constraints, the smaller array is “broadcast” across the larger array so that they have compatible shapes. Broadcasting provides a means of vectorizing array operations so that looping occurs in C instead of Python. It does this without making needless copies of data and usually leads to efficient algorithm implementations. [From Numpy.org]\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[3 4 5]\n"
     ]
    }
   ],
   "source": [
    "a = np.array([1, 2, 3])\n",
    "b = np.array([2, 2, 2])\n",
    "\n",
    "print(a+b)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Example 1 :- \n",
    "\n",
    "In the below example, we are performing operations **between an array and scalar**. Scalar can be considered as another  array (dimensionless). If you look at the results, they are identical to the example above (where b is an 1*3 array with 2 as an element).\n",
    "\n",
    "In this case, the smaller array (scalar) is broadcasted across the larger array i.e. the smaller array is duplicated to match the dimensions and size of larger array.\n",
    "\n",
    "![Broadcasting1](https://numpy.org/devdocs/_images/theory.broadcast_1.gif)\n",
    "(Image Source : Numpy.org)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[3 4 5]\n"
     ]
    }
   ],
   "source": [
    "a = np.array([1, 2, 3])\n",
    "b = 2\n",
    "\n",
    "print(a+b)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Example 2 :-\n",
    "\n",
    "In this example, a 1-D array is added to a 2-D array.\n",
    "\n",
    "![Broadcasting2](https://numpy.org/devdocs/_images/theory.broadcast_2.gif)\n",
    "(Image Source : Numpy.org)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[ 0  1  2]\n",
      " [10 11 12]\n",
      " [20 21 22]\n",
      " [30 31 32]]\n"
     ]
    }
   ],
   "source": [
    "a = np.array([[0,  0,  0],\n",
    "            [10, 10,  10],\n",
    "            [20, 20,  20],\n",
    "            [30, 30,  30]])\n",
    "\n",
    "b = np.array([[0, 1, 2]])\n",
    "\n",
    "print(a+b)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Broadcasting Rules :-**\n",
    "\n",
    "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.\n",
    "\n",
    "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.\n",
    "\n",
    "Rule 3: If in any dimension the sizes disagree and neither is equal to 1, an error is raised.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Array a is :-\n",
      "[[1. 1. 1. 1.]\n",
      " [1. 1. 1. 1.]]\n",
      "\n",
      "Array b is :-\n",
      "[0 1 2 3]\n",
      "\n",
      "Shape of array a is :-\n",
      "(2, 4)\n",
      "\n",
      "Shape of array b is :-\n",
      "(4,)\n",
      "\n",
      " a+b is :-\n",
      "[[1. 2. 3. 4.]\n",
      " [1. 2. 3. 4.]]\n",
      "\n",
      "Array a after broadcasting is (before addition) :-\n",
      "[[1. 1. 1. 1.]\n",
      " [1. 1. 1. 1.]]\n",
      "\n",
      "Array b after broadcasting is (before addition) :-\n",
      "[[0 1 2 3]\n",
      " [0 1 2 3]]\n"
     ]
    }
   ],
   "source": [
    "# Example 1\n",
    "\n",
    "a = np.ones((2,4))\n",
    "b = np.arange(4)\n",
    "\n",
    "print(\"Array a is :-\")\n",
    "print(a)\n",
    "print(\"\\nArray b is :-\")\n",
    "print(b)\n",
    "\n",
    "print(\"\\nShape of array a is :-\")\n",
    "print(a.shape) # => (2,4)\n",
    "print(\"\\nShape of array b is :-\")\n",
    "print(b.shape) # => (4,)\n",
    "\n",
    "# Apply rule 1 on the array with fewer dimensions i.e. pad with ones on left side\n",
    "\n",
    "# a.shape => (2,4)\n",
    "# b.shape => (1,4)\n",
    "\n",
    "# Apply rule 2 : Stretch the dimension for array b accross dimension with 1's\n",
    "\n",
    "# a.shape => (2,4)\n",
    "# b.shape => (2,4)\n",
    "\n",
    "# Apply rule 3 : Dimensions matches\n",
    "print(\"\\n a+b is :-\")\n",
    "print(a+b)\n",
    "\n",
    "# View broadcasted arrays\n",
    "\n",
    "x, y = np.broadcast_arrays(a, b)\n",
    "\n",
    "print(\"\\nArray a after broadcasting is (before addition) :-\")\n",
    "print(x)\n",
    "print(\"\\nArray b after broadcasting is (before addition) :-\")\n",
    "print(y)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Array a is :-\n",
      "[[0]\n",
      " [1]\n",
      " [2]\n",
      " [3]]\n",
      "\n",
      "Array b is :-\n",
      "[0 1 2 3]\n",
      "\n",
      "Shape of array a is :-\n",
      "(4, 1)\n",
      "\n",
      "Shape of array b is :-\n",
      "(4,)\n",
      "\n",
      " a+b is :-\n",
      "[[0 1 2 3]\n",
      " [1 2 3 4]\n",
      " [2 3 4 5]\n",
      " [3 4 5 6]]\n",
      "\n",
      "Array a after broadcasting is (before addition) :-\n",
      "[[0 0 0 0]\n",
      " [1 1 1 1]\n",
      " [2 2 2 2]\n",
      " [3 3 3 3]]\n",
      "\n",
      "Array b after broadcasting is (before addition) :-\n",
      "[[0 1 2 3]\n",
      " [0 1 2 3]\n",
      " [0 1 2 3]\n",
      " [0 1 2 3]]\n"
     ]
    }
   ],
   "source": [
    "# Example 2\n",
    "\n",
    "a = np.arange(4).reshape(4,1)\n",
    "b = np.arange(4)\n",
    "\n",
    "print(\"Array a is :-\")\n",
    "print(a)\n",
    "print(\"\\nArray b is :-\")\n",
    "print(b)\n",
    "\n",
    "print(\"\\nShape of array a is :-\")\n",
    "print(a.shape) # => (4,1)\n",
    "print(\"\\nShape of array b is :-\")\n",
    "print(b.shape) # => (4,)\n",
    "\n",
    "# Apply rule 1 on the array with fewer dimensions i.e. pad with ones on left side\n",
    "\n",
    "# a.shape => (4,1)\n",
    "# b.shape => (1,4)\n",
    "\n",
    "# Apply rule 2 : Stretch the dimension for array b accross dimension with 1's\n",
    "\n",
    "# a.shape => (4,4)\n",
    "# b.shape => (4,4)\n",
    "\n",
    "# Apply rule 3 : Dimensions matches\n",
    "print(\"\\n a+b is :-\")\n",
    "print(a+b)\n",
    "\n",
    "# View broadcasted arrays\n",
    "\n",
    "x, y = np.broadcast_arrays(a, b)\n",
    "\n",
    "print(\"\\nArray a after broadcasting is (before addition) :-\")\n",
    "print(x)\n",
    "print(\"\\nArray b after broadcasting is (before addition) :-\")\n",
    "print(y)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Array a is :-\n",
      "[[1. 1. 1.]\n",
      " [1. 1. 1.]\n",
      " [1. 1. 1.]\n",
      " [1. 1. 1.]]\n",
      "\n",
      "Array b is :-\n",
      "[0 1 2 3]\n",
      "\n",
      "Shape of array a is :-\n",
      "(4, 3)\n",
      "\n",
      "Shape of array b is :-\n",
      "(4,)\n",
      "\n",
      " a+b is :-\n",
      "shape mismatch: objects cannot be broadcast to a single shape\n"
     ]
    }
   ],
   "source": [
    "# Example 3\n",
    "\n",
    "a = np.ones((4,3))\n",
    "b = np.arange(4)\n",
    "\n",
    "print(\"Array a is :-\")\n",
    "print(a)\n",
    "print(\"\\nArray b is :-\")\n",
    "print(b)\n",
    "\n",
    "print(\"\\nShape of array a is :-\")\n",
    "print(a.shape) # => (4,3)\n",
    "print(\"\\nShape of array b is :-\")\n",
    "print(b.shape) # => (4,)\n",
    "\n",
    "# Apply rule 1 on the array with fewer dimensions i.e. pad with ones on left side\n",
    "\n",
    "# a.shape => (4,3)\n",
    "# b.shape => (1,4)\n",
    "\n",
    "# Apply rule 2 : Stretch the dimension for array b accross dimension with 1's\n",
    "\n",
    "# a.shape => (4,3)\n",
    "# b.shape => (4,4)\n",
    "\n",
    "# Apply rule 3 : Dimensions DOES NOT matches\n",
    "print(\"\\n a+b is :-\")\n",
    "#print(a+b)\n",
    "\n",
    "# View broadcasted arrays\n",
    "try:\n",
    "    x, y = np.broadcast_arrays(a, b)\n",
    "    print(\"\\nArray a after broadcasting is (before addition) :-\")\n",
    "    print(x)\n",
    "    print(\"\\nArray b after broadcasting is (before addition) :-\")\n",
    "    print(y)\n",
    "except Exception as e:\n",
    "    print(e)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Shape Operations**\n",
    "\n",
    "reshape(array, newshape) \n",
    "\n",
    "Takes an array as an argument with newshape (integer or tuple). The newshape should be compatible with existing shape.\n",
    "\n",
    "ravel(array) \n",
    "\n",
    "Takes an array as an argument and returns a flattened contiguous array (1-D).\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[0 1 2]\n",
      " [3 4 5]]\n",
      "[0 0 0 0 1 1 1 1 2 2 2 2 3 3 3 3]\n"
     ]
    }
   ],
   "source": [
    "a = np.arange(6).reshape((2,3))\n",
    "print(a)\n",
    "\n",
    "b = np.ravel(x)\n",
    "print(b)    "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Need for Numpy :**\n",
    "\n",
    "If we have two lists and simply want to add the elements, we would have to iterate over each element of both lists and add them. Just using \"+\" operator will concatenate the lists.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[1, 2, 3, 4, 5, 6, 7, 8]\n"
     ]
    }
   ],
   "source": [
    "a = [1, 2, 3, 4]\n",
    "b = [5, 6, 7, 8]\n",
    "\n",
    "print (a+b)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[6, 8, 10, 12]\n"
     ]
    }
   ],
   "source": [
    "r = []\n",
    "for x,y in zip(a,b):\n",
    "    r.append(x+y)\n",
    "\n",
    "print (r)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Numpy :**\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([0., 2., 4.])"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Array Creation\n",
    "\n",
    "#New Arrays can be created by using array function. \n",
    "#This function takes list as an argument and create N-Dimensional array based on the arguments. \n",
    "\n",
    "# One Dimensional Array\n",
    "ar1 = np.array([1,2,3])\n",
    "\n",
    "# Two Dimensional Array\n",
    "ar2 = np.array([[1,2,3], [4,5,6]])\n",
    "\n",
    "# Other methods to create new Arrays \n",
    "# There are several other ways to create Arrays :-\n",
    "\n",
    "# arange([start], [stop], [step]) \n",
    "# This is similar to Python range function and creates evenly spaced arrays.\n",
    "\n",
    "np.arange(5)\n",
    "# OUT : array([0, 1, 2, 3, 4])\n",
    "\n",
    "np.arange(2,10,2) # Excludes the stop position element\n",
    "# OUT : array([2, 4, 6, 8]) \n",
    "\n",
    "# linspace([start], [stop], [num]) \n",
    "# Creates array by number of points.\n",
    "\n",
    "np.linspace(0,6,3)\n",
    "# Creates 3 evenly spaced elements between start and stop point. \n",
    "# Note, stop point value is included. \n",
    "# OUT : array([0., 3., 6.])\n",
    "\n",
    "np.linspace(0,6,3,endpoint=False) # Excludes the stop position element\n",
    "#OUT : array([0., 2., 4.])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "numpy.ndarray"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Type of array\n",
    "a = np.array([1, 2, 3, 4, 5])\n",
    "\n",
    "type(a)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "dtype('int64')"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Data-type : Every element inside this array will be of this type\n",
    "\n",
    "a.dtype"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "dtype('float64')"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Another array with floating members\n",
    "\n",
    "f = np.array([1.2, 33.5, 6.4, 7.8, 8.6])\n",
    "f.dtype"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([10,  2,  3,  4,  5])"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "a[0] = 10\n",
    "a"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([6, 2, 3, 4, 5])"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Decimal portion will be truncated because all elements has to be of same type\n",
    "\n",
    "a[0] = 6.88\n",
    "a"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Number of dimension\n",
    "\n",
    "a.ndim"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(5,)"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Returns a tuple showing number of elements across each dimension\n",
    "\n",
    "a.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "5"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Total number of elements\n",
    "\n",
    "a.size"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "8\n",
      "8\n"
     ]
    }
   ],
   "source": [
    "# Bytes per element\n",
    "\n",
    "print(a.itemsize)\n",
    "print(f.itemsize)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "40"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Bytes used by data portion of array\n",
    "\n",
    "a.nbytes"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Array is :\n",
      " [[1 2 3 4]\n",
      " [5 6 7 8]]\n",
      "\n",
      "\n",
      "Size is :  8\n",
      "Dimension is :  2\n",
      "Shape is :  (2, 4)\n"
     ]
    }
   ],
   "source": [
    "# Multi-Dimensional Arrays\n",
    "\n",
    "# A 2-D array is basically a list of list\n",
    "# A 3-D array will be a list of list of list\n",
    "\n",
    "a = np.array([[1, 2, 3, 4],\n",
    "             [5, 6, 7, 8]])\n",
    "\n",
    "print(\"Array is :\\n\",a)\n",
    "print(\"\\n\")\n",
    "print(\"Size is : \",a.size)\n",
    "print(\"Dimension is : \",a.ndim)\n",
    "print(\"Shape is : \",a.shape)\n",
    "\n",
    "# NOTE : \n",
    "# Dimension 0 is row, dimension 1 is column. So, in this case, we have a tuple (2,4) indicating 2 rows and \n",
    "# 4 elements in each row"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Following picture illustartes the dimension/axis for multi-dimensional array :-\n",
    "\n",
    "![p2PGi.png](https://i.stack.imgur.com/p2PGi.png)\n",
    "\n",
    "Source : http://physics.cornell.edu/"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Element in first row and fourth column is  4\n",
      "Element in second row and first column is  5\n",
      "Elements in second row [5 6 7 8]\n",
      "Array after changing first element of second row :\n",
      " [[ 1  2  3  4]\n",
      " [10  6  7  8]]\n",
      "Array after changing second row :\n",
      " [[ 1  2  3  4]\n",
      " [10 11 12 13]]\n"
     ]
    }
   ],
   "source": [
    "# Retrieving/Setting individual element from a 2-D array :-\n",
    "\n",
    "# Syntax : Array[row,column]\n",
    "\n",
    "print(\"Element in first row and fourth column is \",a[0,3])\n",
    "print(\"Element in second row and first column is \",a[1,0])\n",
    "\n",
    "# Retrieving all elements of a row :-\n",
    "# If you specify only first parameter (row index), then all elements of that row are returned\n",
    "\n",
    "print(\"Elements in second row\", a[1])\n",
    "\n",
    "# Setting an element \n",
    "a[1,0] = 10\n",
    "print(\"Array after changing first element of second row :\\n\", a)\n",
    "\n",
    "a[1] = [10, 11 ,12 ,13]\n",
    "print(\"Array after changing second row :\\n\", a)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Array Slicing :-**\n",
    "\n",
    "An array is sliced with the following syntax, which extracts the sequence based on lower and upper bound\n",
    "\n",
    "*Array[start:stop:step]*\n",
    "\n",
    "Note, that the lower (start) bound element is included but the upper(stop) bound element is not included. Step value defines the stride.\n",
    "\n",
    "Just like Python lists, the array is represented with indices from both directions. Sequence can be extracted with the above syntax and combinations of positive/negative indices. \n",
    "\n",
    "NOTE : If boundaries are ommited, it is considered as starting (or ending) of a list."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Second, third and fourth element : a[1:4] :- [11 12 13]\n",
      "\n",
      "Same elements can be extracted with following notations as well ...\n",
      "a[-5:-2] :-  [11 12 13]\n",
      "a[-5:4] :-  [11 12 13]\n",
      "a[1:-2] :-  [11 12 13]\n",
      "\n",
      "Extract first three elements ...\n",
      "a[:3] :-  [10 11 12]\n",
      "\n",
      "Extract last three elements ...\n",
      "a[-3:] :-  [13 14 15]\n",
      "a[3:] :-  [13 14 15]\n",
      "\n",
      "Extract every other element ...\n",
      "a[::2] :-  [10 12 14]\n"
     ]
    }
   ],
   "source": [
    "a = np.array([10, 11, 12, 13, 14, 15])\n",
    "\"\"\"\n",
    " +---+---+---+---+---+---+\n",
    " | 10| 11| 12| 13| 14| 15|\n",
    " +---+---+---+---+---+---+\n",
    "   0   1   2   3   4   5   \n",
    "  -6  -5  -4  -3  -2  -1\n",
    "\"\"\"    \n",
    "    \n",
    "# Extract the second, third and fourth element\n",
    "\n",
    "print(\"Second, third and fourth element : a[1:4] :-\", a[1:4])\n",
    "print(\"\\nSame elements can be extracted with following notations as well ...\")\n",
    "print(\"a[-5:-2] :- \", a[-5:-2])\n",
    "print(\"a[-5:4] :- \", a[-5:4])\n",
    "print(\"a[1:-2] :- \", a[1:-2])\n",
    "\n",
    "# Extract first three elements\n",
    "print(\"\\nExtract first three elements ...\")\n",
    "print(\"a[:3] :- \", a[:3])\n",
    "\n",
    "# Extract last three elements\n",
    "print(\"\\nExtract last three elements ...\")\n",
    "print(\"a[-3:] :- \", a[-3:])\n",
    "print(\"a[3:] :- \", a[3:])\n",
    "\n",
    "# Extract every other element\n",
    "print(\"\\nExtract every other element ...\")\n",
    "print(\"a[::2] :- \", a[::2])\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[10 11  2  3  4  5]\n"
     ]
    }
   ],
   "source": [
    "# Inserting values \n",
    "\n",
    "a[2:] = [2, 3, 4, 5]\n",
    "\n",
    "print(a)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Original Array\n",
      "[[ 0  1  2  3  4]\n",
      " [ 5  6  7  8  9]\n",
      " [10 11 12 13 14]\n",
      " [15 16 17 18 19]\n",
      " [20 21 22 23 24]]\n",
      "\n",
      "\n",
      "[20 21 22 23 24]\n",
      "\n",
      "\n",
      "[[ 1  3]\n",
      " [ 6  8]\n",
      " [11 13]\n",
      " [16 18]\n",
      " [21 23]]\n",
      "\n",
      "\n",
      "[[ 5  7]\n",
      " [15 17]]\n"
     ]
    }
   ],
   "source": [
    "# More on slicing with an example using 2-D data\n",
    "\n",
    "a = np.arange(25).reshape(5,5)\n",
    "print(\"Original Array\")\n",
    "print(a)\n",
    "print(\"\\n\")\n",
    "print(a[4])\n",
    "print(\"\\n\")\n",
    "print(a[:,1::2])\n",
    "print(\"\\n\")\n",
    "print(a[1::2,:3:2])\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[-1  2  3  4  5]\n"
     ]
    }
   ],
   "source": [
    "a = np.array([1, 2, 3, 4, 5])\n",
    "b = a[:3]\n",
    "\n",
    "b[0] = -1\n",
    "print(a)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [],
   "source": [
    "c = a.copy()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Fancy Indexing :-**\n",
    "\n",
    "To understand this better, let's s"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[71 21 92  4 88 21 11 72 43 33 62 17 73 32 34]\n",
      "Element at index 1 is 21\n",
      "Element at index 5 is 21\n",
      "Element at index 13 is 32\n",
      "Elements at indexes [1, 5, 13] are [21 21 32]\n"
     ]
    }
   ],
   "source": [
    "# Fancy Indexing - Index Based : (1-D)\n",
    "\n",
    "# To understand this better, let's say we have an array (1-D for simplicity) with some random numbers\n",
    "\n",
    "a = np.random.randint(1, 100, 15)  # Will create an array of 15 elements between 1 & 100\n",
    "\n",
    "print (a)\n",
    "\n",
    "# Now, say we need to extract the element(s) at index 1, 5 and 13. One way is to extract each item individually\n",
    "\n",
    "print(\"Element at index {} is {}\".format(1, a[1]))\n",
    "print(\"Element at index {} is {}\".format(5, a[5]))\n",
    "print(\"Element at index {} is {}\".format(13, a[13]))\n",
    "\n",
    "# However, Numpy offers another approach where we could just a pass of list of indexes for which we want to\n",
    "# retrieve elements\n",
    "\n",
    "index = [1, 5, 13]\n",
    "\n",
    "print(\"Elements at indexes {} are {}\".format(index, a[index]))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Original array is [ 1  3  4  5  6  8 10  3  1]\n",
      "Mask array is [False False  True False  True  True  True False False]\n",
      "Even numbers are \n",
      "[ 4  6  8 10]\n"
     ]
    }
   ],
   "source": [
    "# Fancy Indexing - Boolean Array Indexing (1-D)\n",
    "\n",
    "# Consider we have an array of odd and even numbers. \n",
    "# Our task is to find out even elements\n",
    "\n",
    "a = np.array([1, 3, 4, 5, 6, 8, 10, 3, 1])\n",
    "\n",
    "# Numpy allows elements to be retrieved by a boolean array i.e. elements will be returned for True element\n",
    "\n",
    "mask = (a % 2 == 0)\n",
    "\n",
    "print(\"Original array is {}\".format(a))\n",
    "print(\"Mask array is {}\".format(mask))\n",
    "print(\"Even numbers are \")\n",
    "print(a[mask]) "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Original array is \n",
      " [[ 0  1  2  3  4]\n",
      " [ 5  6  7  8  9]\n",
      " [10 11 12 13 14]\n",
      " [15 16 17 18 19]\n",
      " [20 21 22 23 24]]\n",
      "\n",
      "\n",
      "Contents at index (or row) 3 is [15 16 17 18 19]\n",
      "Contents at index (or row) 2 is [10 11 12 13 14]\n"
     ]
    }
   ],
   "source": [
    "# Fancy Indexing - Index Based : (2-D)\n",
    "\n",
    "# Fancy indexing can also be performed on a 2-D array\n",
    "\n",
    "a = np.arange(25).reshape(5,5)\n",
    "\n",
    "# If we have a 2-D array, passing a single index will return the entire row\n",
    "\n",
    "print(\"Original array is \\n {}\".format(a))\n",
    "\n",
    "print(\"\\n\")\n",
    "print(\"Contents at index (or row) {} is {}\".format(3,a[3]))\n",
    "print(\"Contents at index (or row) {} is {}\".format(2,a[2]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "a[3,2] : This will print the element at 3rd row and 2nd column :\n",
      "17\n",
      "\n",
      "\n",
      "a[[3,2]] : This will print the contents for 3rd and 2nd row :\n",
      "[[15 16 17 18 19]\n",
      " [10 11 12 13 14]]\n",
      "\n",
      "\n",
      "a[[0,2,4],[0,3,0]] : This will print elements at (0,0), (2,3) & (4,0) :\n",
      "[ 0 13 20]\n",
      "\n",
      "\n",
      "a[[0,2,4]][:] : This will print all elements at row 0, 2 & 4 :\n",
      "[[ 0  1  2  3  4]\n",
      " [10 11 12 13 14]\n",
      " [20 21 22 23 24]]\n",
      "[[ 0  1  2  3  4]\n",
      " [10 11 12 13 14]\n",
      " [20 21 22 23 24]]\n",
      "\n",
      "\n",
      "a[[0,2,4]][:,[0,1,3]] : This will print elements at row 0, 2 & 4 AND columns 0, 1 & 3 :\n",
      "[[ 0  1  3]\n",
      " [10 11 13]\n",
      " [20 21 23]]\n"
     ]
    }
   ],
   "source": [
    "# If we pass multiple indexes (as a list), we get the data for these entire rows\n",
    "\n",
    "print(\"a[3,2] : This will print the element at 3rd row and 2nd column :\")\n",
    "print(a[3,2])\n",
    "print(\"\\n\")\n",
    "\n",
    "print(\"a[[3,2]] : This will print the contents for 3rd and 2nd row :\")\n",
    "print(a[[3,2]])\n",
    "print(\"\\n\")\n",
    "\n",
    "print(\"a[[0,2,4],[0,3,0]] : This will print elements at (0,0), (2,3) & (4,0) :\")\n",
    "print(a[[0,2,4],[0,3,0]])\n",
    "print(\"\\n\")\n",
    "\n",
    "print(\"a[[0,2,4]][:] : This will print all elements at row 0, 2 & 4 :\")\n",
    "print(a[[0,2,4]][:])\n",
    "print(a[[0,2,4]])\n",
    "print(\"\\n\")\n",
    "\n",
    "print(\"a[[0,2,4]][:,[0,1,3]] : This will print elements at row 0, 2 & 4 AND columns 0, 1 & 3 :\")\n",
    "print(a[[0,2,4]][:,[0,1,3]])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[ 0  1  2  3  4]\n",
      " [ 5  6  7  8  9]\n",
      " [10 11 12 13 14]\n",
      " [15 16 17 18 19]\n",
      " [20 21 22 23 24]]\n"
     ]
    }
   ],
   "source": [
    "print(a)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Original array is \n",
      " [[ 0  1  2  3  4]\n",
      " [ 5  6  7  8  9]\n",
      " [10 11 12 13 14]\n",
      " [15 16 17 18 19]\n",
      " [20 21 22 23 24]] \n",
      "\n",
      "Mask array is \n",
      " [[False  True False  True False]\n",
      " [ True False  True False  True]\n",
      " [False  True False  True False]\n",
      " [ True False  True False  True]\n",
      " [False  True False  True False]] \n",
      "\n",
      "Odd numbers are \n",
      "[ 1  3  5  7  9 11 13 15 17 19 21 23]\n"
     ]
    }
   ],
   "source": [
    "# Boolean Array Indexing (2-D)\n",
    "\n",
    "# Boolean Array indexing will work similar to 1-D. Let's use the similar example to find all odd elements\n",
    "\n",
    "mask = (a % 2 != 0)\n",
    "\n",
    "print(\"Original array is \\n {} \\n\".format(a))\n",
    "print(\"Mask array is \\n {} \\n\".format(mask))\n",
    "print(\"Odd numbers are \")\n",
    "print(a[mask]) \n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Vectorization :**\n",
    "\n",
    "In high-level languages the term vectorization refers to use of pre-compiled, optimized code written in language like C to perform mathematical operations over sequence of data. Basically, this is done without writing \"for\" loop in Python.\n",
    "\n",
    "Python native list allows elements to be of different data types as opposed to Numpy array where the elements have to be of same data type. This property allows mathematical operations to be delecated to pre-compiled code written in C to gain performance improvements.\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Array a is \n",
      " [[ 0  1  2  3]\n",
      " [ 4  5  6  7]\n",
      " [ 8  9 10 11]\n",
      " [12 13 14 15]] \n",
      "\n",
      " Array b is \n",
      " [[ 0  1  2  3]\n",
      " [ 4  5  6  7]\n",
      " [ 8  9 10 11]\n",
      " [12 13 14 15]] \n",
      "\n",
      "a + 2 : Adding 2 to each element :-  \n",
      " [[ 2  3  4  5]\n",
      " [ 6  7  8  9]\n",
      " [10 11 12 13]\n",
      " [14 15 16 17]] \n",
      "\n",
      "a - 2 : Subtracting 2 from each element :-  \n",
      " [[-2 -1  0  1]\n",
      " [ 2  3  4  5]\n",
      " [ 6  7  8  9]\n",
      " [10 11 12 13]] \n",
      "\n",
      "a / 2 : Dividing 2 from each element :-  \n",
      " [[0.  0.5 1.  1.5]\n",
      " [2.  2.5 3.  3.5]\n",
      " [4.  4.5 5.  5.5]\n",
      " [6.  6.5 7.  7.5]] \n",
      "\n",
      "a * 2 : Multiplying 2 from each element :-  \n",
      " [[ 0  2  4  6]\n",
      " [ 8 10 12 14]\n",
      " [16 18 20 22]\n",
      " [24 26 28 30]] \n",
      "\n"
     ]
    }
   ],
   "source": [
    "a = np.arange(16).reshape(4,4)\n",
    "b = np.arange(16).reshape(4,4)\n",
    "\n",
    "print(\"Array a is \\n\",a,\"\\n\\n\",\"Array b is \\n\",b, \"\\n\")\n",
    "\n",
    "# Element wise operation\n",
    "\n",
    "print(\"a + 2 : Adding 2 to each element :- \",\"\\n\",a+2, \"\\n\")\n",
    "print(\"a - 2 : Subtracting 2 from each element :- \",\"\\n\",a-2, \"\\n\")\n",
    "print(\"a / 2 : Dividing 2 from each element :- \",\"\\n\",a/2, \"\\n\")\n",
    "print(\"a * 2 : Multiplying 2 from each element :- \",\"\\n\",a*2, \"\\n\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "a + b : Adding each element of array 'a' to element of 'b' :-  \n",
      " [[ 0  2  4  6]\n",
      " [ 8 10 12 14]\n",
      " [16 18 20 22]\n",
      " [24 26 28 30]] \n",
      "\n",
      "a - b : Subtracting each element of array 'a' to element of 'b' :-  \n",
      " [[0 0 0 0]\n",
      " [0 0 0 0]\n",
      " [0 0 0 0]\n",
      " [0 0 0 0]] \n",
      "\n",
      "a * b : Multiplying each element of array 'a' to element of 'b' :-  \n",
      " [[  0   1   4   9]\n",
      " [ 16  25  36  49]\n",
      " [ 64  81 100 121]\n",
      " [144 169 196 225]] \n",
      "\n"
     ]
    }
   ],
   "source": [
    "# Array based operations :-\n",
    "\n",
    "print(\"a + b : Adding each element of array 'a' to element of 'b' :- \",\"\\n\",a+b, \"\\n\")\n",
    "print(\"a - b : Subtracting each element of array 'a' to element of 'b' :- \",\"\\n\",a-b, \"\\n\")\n",
    "print(\"a * b : Multiplying each element of array 'a' to element of 'b' :- \",\"\\n\",a*b, \"\\n\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "120\n",
      "[24 28 32 36]\n",
      "[ 6 22 38 54]\n"
     ]
    }
   ],
   "source": [
    "# Sequence based operations\n",
    "\n",
    "# These functions are called usoc or universal functions which operate on each element\n",
    "\n",
    "print(np.sum(a)) # Add each element in array\n",
    "\n",
    "print(np.sum(a,axis=0)) # Add each element across axis-0 (rows) - vertical direction \n",
    "\n",
    "print(np.sum(a,axis=1)) # Add each element across axis-1 (columns)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Broadcasting :-**\n",
    "\n",
    "When we perform arithemetic operations on two arrays, the operations are performed element wise. One condition to perform such operations is that the arrays should be of same shape.\n",
    "\n",
    "Broadcasting is a technique used by Numpy to perform operations on arrays of different shapes. \n",
    "\n",
    "\n",
    "Subject to certain constraints, the smaller array is “broadcast” across the larger array so that they have compatible shapes. Broadcasting provides a means of vectorizing array operations so that looping occurs in C instead of Python. It does this without making needless copies of data and usually leads to efficient algorithm implementations. [From Numpy.org]\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[3 4 5]\n"
     ]
    }
   ],
   "source": [
    "a = np.array([1, 2, 3])\n",
    "b = np.array([2, 2, 2])\n",
    "\n",
    "print(a+b)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Example 1 :- \n",
    "\n",
    "In the below example, we are performing operations **between an array and scalar**. Scalar can be considered as another  array (dimensionless). If you look at the results, they are identical to the example above (where b is an 1*3 array with 2 as an element).\n",
    "\n",
    "In this case, the smaller array (scalar) is broadcasted across the larger array i.e. the smaller array is duplicated to match the dimensions and size of larger array.\n",
    "\n",
    "![Broadcasting1](https://numpy.org/devdocs/_images/theory.broadcast_1.gif)\n",
    "(Image Source : Numpy.org)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[3 4 5]\n"
     ]
    }
   ],
   "source": [
    "a = np.array([1, 2, 3])\n",
    "b = 2\n",
    "\n",
    "print(a+b)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Example 2 :-\n",
    "\n",
    "In this example, a 1-D array is added to a 2-D array.\n",
    "\n",
    "![Broadcasting2](https://numpy.org/devdocs/_images/theory.broadcast_2.gif)\n",
    "(Image Source : Numpy.org)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[ 0  1  2]\n",
      " [10 11 12]\n",
      " [20 21 22]\n",
      " [30 31 32]]\n"
     ]
    }
   ],
   "source": [
    "a = np.array([[0,  0,  0],\n",
    "            [10, 10,  10],\n",
    "            [20, 20,  20],\n",
    "            [30, 30,  30]])\n",
    "\n",
    "b = np.array([[0, 1, 2]])\n",
    "\n",
    "print(a+b)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Broadcasting Rules :-**\n",
    "\n",
    "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.\n",
    "\n",
    "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.\n",
    "\n",
    "Rule 3: If in any dimension the sizes disagree and neither is equal to 1, an error is raised.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Array a is :-\n",
      "[[1. 1. 1. 1.]\n",
      " [1. 1. 1. 1.]]\n",
      "\n",
      "Array b is :-\n",
      "[0 1 2 3]\n",
      "\n",
      "Shape of array a is :-\n",
      "(2, 4)\n",
      "\n",
      "Shape of array b is :-\n",
      "(4,)\n",
      "\n",
      " a+b is :-\n",
      "[[1. 2. 3. 4.]\n",
      " [1. 2. 3. 4.]]\n",
      "\n",
      "Array a after broadcasting is (before addition) :-\n",
      "[[1. 1. 1. 1.]\n",
      " [1. 1. 1. 1.]]\n",
      "\n",
      "Array b after broadcasting is (before addition) :-\n",
      "[[0 1 2 3]\n",
      " [0 1 2 3]]\n"
     ]
    }
   ],
   "source": [
    "# Example 1\n",
    "\n",
    "a = np.ones((2,4))\n",
    "b = np.arange(4)\n",
    "\n",
    "print(\"Array a is :-\")\n",
    "print(a)\n",
    "print(\"\\nArray b is :-\")\n",
    "print(b)\n",
    "\n",
    "print(\"\\nShape of array a is :-\")\n",
    "print(a.shape) # => (2,4)\n",
    "print(\"\\nShape of array b is :-\")\n",
    "print(b.shape) # => (4,)\n",
    "\n",
    "# Apply rule 1 on the array with fewer dimensions i.e. pad with ones on left side\n",
    "\n",
    "# a.shape => (2,4)\n",
    "# b.shape => (1,4)\n",
    "\n",
    "# Apply rule 2 : Stretch the dimension for array b accross dimension with 1's\n",
    "\n",
    "# a.shape => (2,4)\n",
    "# b.shape => (2,4)\n",
    "\n",
    "# Apply rule 3 : Dimensions matches\n",
    "print(\"\\n a+b is :-\")\n",
    "print(a+b)\n",
    "\n",
    "# View broadcasted arrays\n",
    "\n",
    "x, y = np.broadcast_arrays(a, b)\n",
    "\n",
    "print(\"\\nArray a after broadcasting is (before addition) :-\")\n",
    "print(x)\n",
    "print(\"\\nArray b after broadcasting is (before addition) :-\")\n",
    "print(y)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Array a is :-\n",
      "[[0]\n",
      " [1]\n",
      " [2]\n",
      " [3]]\n",
      "\n",
      "Array b is :-\n",
      "[0 1 2 3]\n",
      "\n",
      "Shape of array a is :-\n",
      "(4, 1)\n",
      "\n",
      "Shape of array b is :-\n",
      "(4,)\n",
      "\n",
      " a+b is :-\n",
      "[[0 1 2 3]\n",
      " [1 2 3 4]\n",
      " [2 3 4 5]\n",
      " [3 4 5 6]]\n",
      "\n",
      "Array a after broadcasting is (before addition) :-\n",
      "[[0 0 0 0]\n",
      " [1 1 1 1]\n",
      " [2 2 2 2]\n",
      " [3 3 3 3]]\n",
      "\n",
      "Array b after broadcasting is (before addition) :-\n",
      "[[0 1 2 3]\n",
      " [0 1 2 3]\n",
      " [0 1 2 3]\n",
      " [0 1 2 3]]\n"
     ]
    }
   ],
   "source": [
    "# Example 2\n",
    "\n",
    "a = np.arange(4).reshape(4,1)\n",
    "b = np.arange(4)\n",
    "\n",
    "print(\"Array a is :-\")\n",
    "print(a)\n",
    "print(\"\\nArray b is :-\")\n",
    "print(b)\n",
    "\n",
    "print(\"\\nShape of array a is :-\")\n",
    "print(a.shape) # => (4,1)\n",
    "print(\"\\nShape of array b is :-\")\n",
    "print(b.shape) # => (4,)\n",
    "\n",
    "# Apply rule 1 on the array with fewer dimensions i.e. pad with ones on left side\n",
    "\n",
    "# a.shape => (4,1)\n",
    "# b.shape => (1,4)\n",
    "\n",
    "# Apply rule 2 : Stretch the dimension for array b accross dimension with 1's\n",
    "\n",
    "# a.shape => (4,4)\n",
    "# b.shape => (4,4)\n",
    "\n",
    "# Apply rule 3 : Dimensions matches\n",
    "print(\"\\n a+b is :-\")\n",
    "print(a+b)\n",
    "\n",
    "# View broadcasted arrays\n",
    "\n",
    "x, y = np.broadcast_arrays(a, b)\n",
    "\n",
    "print(\"\\nArray a after broadcasting is (before addition) :-\")\n",
    "print(x)\n",
    "print(\"\\nArray b after broadcasting is (before addition) :-\")\n",
    "print(y)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Array a is :-\n",
      "[[1. 1. 1.]\n",
      " [1. 1. 1.]\n",
      " [1. 1. 1.]\n",
      " [1. 1. 1.]]\n",
      "\n",
      "Array b is :-\n",
      "[0 1 2 3]\n",
      "\n",
      "Shape of array a is :-\n",
      "(4, 3)\n",
      "\n",
      "Shape of array b is :-\n",
      "(4,)\n",
      "\n",
      " a+b is :-\n",
      "shape mismatch: objects cannot be broadcast to a single shape\n"
     ]
    }
   ],
   "source": [
    "# Example 3\n",
    "\n",
    "a = np.ones((4,3))\n",
    "b = np.arange(4)\n",
    "\n",
    "print(\"Array a is :-\")\n",
    "print(a)\n",
    "print(\"\\nArray b is :-\")\n",
    "print(b)\n",
    "\n",
    "print(\"\\nShape of array a is :-\")\n",
    "print(a.shape) # => (4,3)\n",
    "print(\"\\nShape of array b is :-\")\n",
    "print(b.shape) # => (4,)\n",
    "\n",
    "# Apply rule 1 on the array with fewer dimensions i.e. pad with ones on left side\n",
    "\n",
    "# a.shape => (4,3)\n",
    "# b.shape => (1,4)\n",
    "\n",
    "# Apply rule 2 : Stretch the dimension for array b accross dimension with 1's\n",
    "\n",
    "# a.shape => (4,3)\n",
    "# b.shape => (4,4)\n",
    "\n",
    "# Apply rule 3 : Dimensions DOES NOT matches\n",
    "print(\"\\n a+b is :-\")\n",
    "#print(a+b)\n",
    "\n",
    "# View broadcasted arrays\n",
    "try:\n",
    "    x, y = np.broadcast_arrays(a, b)\n",
    "    print(\"\\nArray a after broadcasting is (before addition) :-\")\n",
    "    print(x)\n",
    "    print(\"\\nArray b after broadcasting is (before addition) :-\")\n",
    "    print(y)\n",
    "except Exception as e:\n",
    "    print(e)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Shape Operations**\n",
    "\n",
    "reshape(array, newshape) \n",
    "\n",
    "Takes an array as an argument with newshape (integer or tuple). The newshape should be compatible with existing shape.\n",
    "\n",
    "ravel(array) \n",
    "\n",
    "Takes an array as an argument and returns a flattened contiguous array (1-D).\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[0 1 2]\n",
      " [3 4 5]]\n",
      "[0 0 0 0 1 1 1 1 2 2 2 2 3 3 3 3]\n"
     ]
    }
   ],
   "source": [
    "a = np.arange(6).reshape((2,3))\n",
    "print(a)\n",
    "\n",
    "b = np.ravel(x)\n",
    "print(b)    "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}

Comments