<jupyter_start><jupyter_text>---     Department of Data ScienceCourse: Tools and Techniques for Data Science---Instructor: <NAME>, Ph.D.     Lecture 3.3 (NumPy-03)  _Basic Operations on NumPy Arrays.ipynb_    Learning agenda of this notebook- Operations on numPy arrays are done element-wise. This means that you don't explicitly have to write for-loops in order to do these operations!- A universal function, or ufunc, is a function that performs element-wise operations on data in ndarrays. You can think of them as fast vectorized wrappers for simple functions that take one or more scalar values and produce one or more scalar results.  1. Scalar Math    - On 1-D Arrays    - On 2-D Arrays2. Arithmetic operations    - On 1-D Arrays    - On 2-D Arrays3. More Mathematical Operations    - On 1-D Arrays    - On 2-D Arrays4. Aggregate Functions    - On 1-D Arrays    - On 2-D Arrays5. Comparing NumPy Arrays    - On 1-D Arrays    - On 2-D Arrays6. Bonus: Searching 1-D and 2-D NumPy Arrays using `numpy.where()`<jupyter_code># To install this library in Jupyter notebook
#import sys
#!{sys.executable} -m pip install numpy
import numpy as np
np.__version__ , np.__path__<jupyter_output><empty_output><jupyter_text>1. Scalar Math  a. Scalar Math on 1-D Arrays<jupyter_code>import numpy as np
# Create a 1-D array having 4 random integers from 1 to 9
arr = np.random.randint(1,10, size=4)
print("arr: ", arr)

# After the operation a new `ndarray` is returned
print("np.add(arr,2): ",      arr + 2)            # np.add(arr, 2))
print("np.subtract(arr,2): ", arr - 2)            # np.subtract(arr, 2))
print("np.multiply(arr,2): ", arr * 2)            # np.multiply(arr, 2))
print("np.divide(arr,2): ",   arr / 2)            # np.divide(arr, 2))
print("np.mod(arr,2): ",      arr % 2)            # np.mod(arr, 2))

arr3 = arr + 9
print(id(arr), id(arr3))<jupyter_output>arr:  [1 2 9 1]
np.add(arr,2):  [ 3  4 11  3]
np.subtract(arr,2):  [-1  0  7 -1]
np.multiply(arr,2):  [ 2  4 18  2]
np.divide(arr,2):  [0.5 1.  4.5 0.5]
np.mod(arr,2):  [1 0 1 1]
140676751307200 140676747042608<jupyter_text>b. Scalar Math on 2-D Arrays<jupyter_code>import numpy as np
# Create a 2x2 matrix with random integers  between 1 to 9
matrix = np.random.randint(1,10, size=(2, 2))
print("Original matrix:\n", matrix)

# After the operation a new `ndarray` is returned
print("np.add(matrix,2): \n",      matrix + 2)            # np.add(matrix, 2))
print("np.subtract(matrix,2): \n", matrix - 2)            # np.subtract(matrix, 2))
print("np.multiply(matrix,2): \n", matrix * 2)            # np.multiply(matrix, 2))
print("np.divide(matrix,2): \n",   matrix / 2)            # np.divide(matrix, 2))
print("np.mod(matrix,2): \n",      matrix % 2)            # np.mod(matrix, 2))<jupyter_output>Original matrix:
 [[8 6]
 [2 7]]
np.add(matrix,2): 
 [[10  8]
 [ 4  9]]
np.subtract(matrix,2): 
 [[6 4]
 [0 5]]
np.multiply(matrix,2): 
 [[16 12]
 [ 4 14]]
np.divide(matrix,2): 
 [[4.  3. ]
 [1.  3.5]]
np.mod(matrix,2): 
 [[0 0]
 [0 1]]<jupyter_text>2. Arithmetic Operations  a. Arithmetic Operations on 1-D Arrays<jupyter_code>import numpy as np
# Create two 1-D arrays each having 4 random integers from 1 to 9
arr1 = np.random.randint(1,10, size=4)
arr2 = np.random.randint(1,10, size=4)
print("arr1: ", arr1)
print("arr2: ", arr2)

# After the operation a new `ndarray` is returned
print("np.add(arr1, arr2) = ",      arr1 + arr2)     # np.add(arr1, arr2)
print("np.subtract(arr1, arr2) = ", arr1 - arr2)     # np.subtract(arr1, arr2)
print("np.multiply(arr1, arr2) = ", arr1 * arr2)     # np.multiply(arr1, arr2)
print("np.divide(arr1, arr2) = ",   arr1  / arr2)    # np.divide(arr1, arr2)
print("np.mod(arr1, arr2) = ",      arr1  % arr2)    # np.mod(arr1, arr2) # np.remainder(arr1, arr2)<jupyter_output>arr1:  [6 1 5 3]
arr2:  [5 8 2 9]
np.add(arr1, arr2) =  [11  9  7 12]
np.subtract(arr1, arr2) =  [ 1 -7  3 -6]
np.multiply(arr1, arr2) =  [30  8 10 27]
np.divide(arr1, arr2) =  [1.2        0.125      2.5        0.33333333]
np.mod(arr1, arr2) =  [1 1 1 3]<jupyter_text>b. Arithmetic Operations on 2-D Arrays<jupyter_code>import numpy as np
# Create a 2x2 matrix with random integers  between 1 to 9
matrix1 = np.random.randint(1,10, size=(2, 2))
matrix2 = np.random.randint(1, 10, size=(2, 2))
print("matrix1: \n", matrix1)
print("matrix2: \n", matrix2)

# After the operation a new `ndarray` is returned
print("np.add(matrix1 , matrix2) = \n",      matrix1 + matrix2) # np.add(matrix1 , matrix2)
print("np.subtract(matrix1 , matrix2) = \n", matrix1 - matrix2) # np.subtract(matrix1 , matrix2)
print("np.multiply(matrix1 , matrix2) = \n", matrix1 * matrix2) # np.multiply(matrix1 , matrix2)
print("np.divide(matrix1 , matrix2) = \n",   matrix1 / matrix2) # np.divide(matrix1 , matrix2)
print("np.mod(matrix1 , matrix2) = \n",      matrix1 % matrix2) # np.remainder(matrix1 , matrix2)<jupyter_output>matrix1: 
 [[1 7]
 [9 9]]
matrix2: 
 [[1 9]
 [3 9]]
np.add(matrix1 , matrix2) = 
 [[ 2 16]
 [12 18]]
np.subtract(matrix1 , matrix2) = 
 [[ 0 -2]
 [ 6  0]]
np.multiply(matrix1 , matrix2) = 
 [[ 1 63]
 [27 81]]
np.divide(matrix1 , matrix2) = 
 [[1.         0.77777778]
 [3.         1.        ]]
np.mod(matrix1 , matrix2) = 
 [[0 7]
 [0 0]]<jupyter_text>3. More Mathematical Operations  a. Mathematical Operations on 1-D Array<jupyter_code># Creating 1-D array of 5 elements of random floats between 1 and 10
arr = np.random.rand(5)*10
print("Original ndarray: ", arr)


# After the operation a new `ndarray` is returned, which is a copy of the original array with the operation performed
print("np.abs(arr) = ", np.abs(arr))
print("np.square(arr) = ", np.square(arr))
print("np.sqrt(arr) = ", np.sqrt(arr))
print("np.cbrt(arr) = ", np.cbrt(arr))
print("np.log(arr) = ", np.log(arr))
print("np.log2(arr) = ", np.log2(arr))
print("np.log10(arr) = ", np.log10(arr))
print("np.isnan(arr) = ", np.isnan(arr))
print("np.ceil(arr) = ", np.ceil(arr))
print("np.floor(arr) = ", np.floor(arr))
print("np.cumsum(arr) = ", np.cumsum(arr))<jupyter_output>Original ndarray:  [5.17486385 7.98599034 3.34627109 2.80535219 7.07680051]
np.abs(arr) =  [5.17486385 7.98599034 3.34627109 2.80535219 7.07680051]
np.square(arr) =  [26.7792159  63.77604178 11.19753019  7.87000091 50.0811054 ]
np.sqrt(arr) =  [2.27483271 2.82594946 1.82928158 1.67491856 2.66022565]
np.cbrt(arr) =  [1.72968217 1.99883185 1.49573173 1.41035723 1.91990166]
np.log(arr) =  [1.64381303 2.0776888  1.20784662 1.03152909 1.9568219 ]
np.log2(arr) =  [2.37152091 2.99747133 1.74255433 1.4881819  2.82309725]
np.log10(arr) =  [0.71389893 0.90232878 0.52456112 0.44798739 0.84983695]
np.isnan(arr) =  [False False False False False]
np.ceil(arr) =  [6. 8. 4. 3. 8.]
np.floor(arr) =  [5. 7. 3. 2. 7.]
np.cumsum(arr) =  [ 5.17486385 13.1608542  16.50712529 19.31247747 26.38927798]<jupyter_text>b. Mathematical Operations on 2-D Array<jupyter_code>import numpy as np
# Create a 2x2 matrix with random integers  between 1 to 9
matrix = np.random.randint(1,10, size=(2, 2))
print("matrix: \n", matrix)

# After the operation a new `ndarray` is returned, which is a copy of the original array with the operation performed
print("np.square(matrix) = \n", np.square(matrix))
print("np.sqrt(matrix) = \n", np.sqrt(matrix))
print("np.log2(matrix) = \n", np.log2(matrix))
print("np.isnan(matrix) = \n", np.isnan(matrix))
print("np.ceil(matrix) = \n", np.ceil(matrix))
print("np.floor(matrix) = \n", np.floor(matrix))
print("np.cumsum(matrix) = \n", np.cumsum(matrix))<jupyter_output>matrix: 
 [[8 7]
 [9 3]]
np.square(matrix) = 
 [[64 49]
 [81  9]]
np.sqrt(matrix) = 
 [[2.82842712 2.64575131]
 [3.         1.73205081]]
np.log2(matrix) = 
 [[3.         2.80735492]
 [3.169925   1.5849625 ]]
np.isnan(matrix) = 
 [[False False]
 [False False]]
np.ceil(matrix) = 
 [[8. 7.]
 [9. 3.]]
np.floor(matrix) = 
 [[8. 7.]
 [9. 3.]]
np.cumsum(matrix) = 
 [ 8 15 24 27]<jupyter_text>4. Aggregate Functions  a. Aggregate Functions on 1-D Arrays<jupyter_code># Create a 1-D array having 5 random integers from 1 to 9
arr = np.random.randint(1, 10, size=5)
print("arr: ", arr)

# After the operation a new `ndarray` is returned, which is a copy of the original array with the operation performed
print("np.min(arr) = ",     np.min(arr))
print("np.max(arr) = ",     np.max(arr))
print("np.sum(arr) = ",     np.sum(arr))
print("np.prod(arr) = ", np.prod(arr))
# Create a 1-D array having 5 random integers from 1 to 9
arr = np.random.randint(1, 10, size=5)
print("arr: ", arr)

#Arithmetic mean is the sum of elements along an axis divided by the number of elements. 
print("np.mean(arr) = ",    np.mean(arr))

# The np.average() function computes the weighted average, by multiplying of each array element by its weight
# The np.average() is same as np.mean(), when weight is not specified
print("np.average(arr) = ", np.average(arr))

# Considering an array [1,2,3,4] and corresponding weights [4,3,2,1], the weighted average is calculated by 
# adding the product of the corresponding elements and dividing the sum by the sum of weights.
#             Weighted average = (1*4+2*3+3*2+4*1)/(4+3+2+1)

arr1 = np.array([1,2,3,4])
wts = np.array([4,3,2,1])
print("\narr1: ", arr1)
print("wts:  ", wts)
print("np.average(arr1, weights=wts) = ", np.average(arr1, weights= wts))


# Create a 1-D array having 10 random integers from 1 to 99
arr = np.random.randint(1, 100, size=10)
print("arr: ", arr)

# Median is the value separating the higher half of a data sample from the lower half.
print("np.median(arr) = ",  np.median(arr))

# Percentile is the value below which a given percentage of observations in a group of observations fall.
# 50th percentile is the score at or below which 50% of the scores in the distribution may be found.
print("np.percentile(arr,50) = ",     np.percentile(arr,50))
print("np.percentile(arr,0) = ",     np.percentile(arr,0))
print("np.percentile(arr,100) = ",     np.percentile(arr,100))

# The variance measures the average degree to which each point differs from the mean
# Variance is calculated as the average of squared deviations from mean, i.e., mean((x - x.mean())**2).
print("np.var(arr) = ",    np.var(arr))


# Standard deviation checks how spread out a group of numbers is from the mean
# It is the square root of variance: sqrt(mean((x - x.mean())**2))
print("np.std(arr) = ",    np.std(arr))<jupyter_output>arr:  [24 24 69 56 99 67 62 90 35 79]
np.median(arr) =  64.5
np.percentile(arr,50) =  64.5
np.percentile(arr,0) =  24.0
np.percentile(arr,100) =  99.0
np.var(arr) =  614.65
np.std(arr) =  24.79213584990208<jupyter_text>b. Aggregate Functions on 2-D Arrays<jupyter_code>import numpy as np
# Create a 3x3 (2-D array) initialized with random integers from 1 to 9
matrix = np.random.randint(1,10, size=(3, 3))
print("matrix: \n", matrix)

print("np.sum(matrix, axis=0) = ",     np.sum(matrix, axis=0))
print("np.sum(matrix, axis=1) = ",     np.sum(matrix, axis=1))

# Create a 3x3 (2-D array) initialized with random integers from 1 to 9
matrix = np.random.randint(1,10, size=(3, 3))
print("matrix: \n", matrix)

print("np.min(matrix, axis=0) = ",     np.min(matrix, axis=0))
print("np.min(matrix, axis=1) = ",     np.min(matrix, axis=1))
# Create a 3x3 (2-D array) initialized with random integers from 1 to 9
matrix = np.random.randint(1,10, size=(3, 3))
print("matrix: \n", matrix)


print("np.mean(matrix, axis=0) = ",     np.mean(matrix, axis=0))
print("np.mean(matrix, axis=1) = ",     np.mean(matrix, axis=1))
# Create a 3x3 (2-D array) initialized with random integers from 1 to 9
matrix = np.random.randint(1,10, size=(3, 3))
print("matrix: \n", matrix)

# Median is the value separating the higher half of a data sample from the lower half.
print("np.median(matrix, axis=0) = ",     np.median(matrix, axis=0))
print("np.median(matrix, axis=1) = ",     np.median(matrix, axis=1))<jupyter_output>matrix: 
 [[7 7 3]
 [7 4 7]
 [8 5 1]]
np.median(matrix, axis=0) =  [7. 5. 3.]
np.median(matrix, axis=1) =  [7. 7. 5.]<jupyter_text>Please do practice calculating the percentile, variance and standard deviation along different axis of a 2-D array  5. Comparing NumPy Arrays  a. Comparing NumPy 1-D Arrays- Returns a Boolean numpy array containing True/False after doing element wise comparison<jupyter_code>import numpy as np
# Create two 1-D arrays each having 4 random integers from 1 to 9
arr1 = np.random.randint(1,10, size=4)
arr2 = np.random.randint(1,10, size=4)
print("arr1: ", arr1)
print("arr2: ", arr2)
print("np.equal(arr1, arr2) = ",      arr1 == arr2)      # np.equal(arr1, arr2)
print("np.not_equal(arr1, arr2) = ",  arr1 != arr2)      # np.not_equal(arr1, arr2)

print("np.greater(arr1, arr2) = ",      arr1 > arr2)     # np.greater(arr1, arr2)
print("np.greater_equal(arr1, arr2) = ",  arr1 >= arr2)  # np.greater_equal(arr1, arr2)

print("np.less(arr1, arr2) = ",      arr1 < arr2)        # np.less(arr1, arr2)
print("np.less_equal(arr1, arr2) = ",  arr1 <= arr2)     # np.less_equal(arr1, arr2)<jupyter_output>arr1:  [6 9 4 6]
arr2:  [5 2 8 5]
np.equal(arr1, arr2) =  [False False False False]
np.not_equal(arr1, arr2) =  [ True  True  True  True]
np.greater(arr1, arr2) =  [ True  True False  True]
np.greater_equal(arr1, arr2) =  [ True  True False  True]
np.less(arr1, arr2) =  [False False  True False]
np.less_equal(arr1, arr2) =  [False False  True False]<jupyter_text>b. Comparing NumPy 2-D Arrays<jupyter_code>import numpy as np
# Create a 2x2 matrix with random integers  between 1 to 9
matrix1 = np.random.randint(1,10, size=(2, 2))
matrix2 = np.random.randint(1, 10, size=(2, 2))
print("matrix1: \n", matrix1)
print("matrix2: \n", matrix2)



print("np.equal(matrix1, matrix2) = \n",        matrix1 == matrix2)     
print("np.not_equal(matrix1, matrix2) = \n",    matrix1 != matrix2)     

print("np.greater(matrix1, matrix2) = \n",      matrix1 > matrix2)    
print("np.greater_equal(matrix1, matrix2) = \n",matrix1 >= matrix2) 

print("np.less(matrix1, matrix2) = \n",         matrix1 < matrix2)       
print("np.less_equal(matrix1, matrix2) = \n",   matrix1 <= matrix2)<jupyter_output>matrix1: 
 [[8 6]
 [4 2]]
matrix2: 
 [[9 7]
 [7 4]]
np.equal(matrix1, matrix2) = 
 [[False False]
 [False False]]
np.not_equal(matrix1, matrix2) = 
 [[ True  True]
 [ True  True]]
np.greater(matrix1, matrix2) = 
 [[False False]
 [False False]]
np.greater_equal(matrix1, matrix2) = 
 [[False False]
 [False False]]
np.less(matrix1, matrix2) = 
 [[ True  True]
 [ True  True]]
np.less_equal(matrix1, matrix2) = 
 [[ True  True]
 [ True  True]]<jupyter_text>6. Searching NumPy Arrays using `numpy.where()`  a. Searching NumPy 1-D Arrays - The `numpy.where()` method is used to search NumPy arrays for the index values of any element which matches the condition passed as a parameter to the function.```numpy.where(condition, [x, y])```    - condition : When True, yield x, otherwise yield y. If x and y are not given just return the value of the array    - Returns: ndarray or tuple of ndarrays. If both x and y are specified, the output array contains elements of x where condition is True, and elements from y elsewhere.<jupyter_code>import numpy as np
arr = np.array([1, 2, 3, 4, 5, 4, 4, 9, 2, 3, 8])
t1 = np.where(arr == 4)
print(t1)<jupyter_output>(array([3, 5, 6]),)<jupyter_text>Note: The result is a tuple of 1-D arrays having a single tuple containing the index<jupyter_code>import numpy as np
arr = np.array([1, 0, 3, 9, 5, 2, 0, 8])
t1 = np.where(arr == 0)
print(t1)
import numpy as np
arr = np.array([1, 2, 3, 4, 5, 6, 7, 8])
t1 = np.where(arr%2 == 0)
print(t1)
import numpy as np
arr = np.array([1, 2, 3, 4, 5, 6, 7, 8])
t1 = np.where(arr%2 == 0, True, False)
print(t1)<jupyter_output>[False  True False  True False  True False  True]<jupyter_text>Note: The result is a simple 1-D array, when you use the x and y arguments to `np.where()`  b. Searching NumPy 2-D Arrays - The `numpy.where()` method is used to search NumPy arrays for the index values of any element which matches the condition passed as a parameter to the function.```numpy.where(condition, [x, y])```    - condition : When True, yield x, otherwise yield y. If x and y are not given just return the value of the array    - Returns: ndarray or tuple of ndarrays. If both x and y are specified, the output array contains elements of x where condition is True, and elements from y elsewhere.<jupyter_code>import numpy as np
matrix = np.array([[1, 2, 3], [4, 5, 4], [4, 9, 2], [3, 8, 6]])
print("matrix: \n", matrix)
t1 = np.where(matrix == 4)
print(t1)<jupyter_output>matrix: 
 [[1 2 3]
 [4 5 4]
 [4 9 2]
 [3 8 6]]
(array([1, 1, 2]), array([0, 2, 0]))<jupyter_text>**Note:** The result is a tuple of 1-D arrays having two tuples. The condition is true for three elements at indices `(1,0)`, `(1,2)` and `(2,0)`<jupyter_code>import numpy as np
matrix = np.array([[1, 2, 0], [4, 5, 0], [4, 9, 2], [3, 0, 6]])
print("matrix: \n", matrix)
t1 = np.where(matrix == 0)
print(t1)<jupyter_output>matrix: 
 [[1 2 0]
 [4 5 0]
 [4 9 2]
 [3 0 6]]
(array([0, 1, 3]), array([2, 2, 1]))<jupyter_text>**Note:** The result is a tuple of 1-D arrays having two tuples. The condition is true for three elements at indices:`(0,2)`, `(1,2)` and `(3,1)`<jupyter_code>import numpy as np
matrix = np.array([[1, 2, 3], [5, 7, 3], [4, 9, 1], [3, 5, 3]])
print("matrix: \n", matrix)
t1 = np.where(matrix%2 == 0)
print(t1)<jupyter_output>matrix: 
 [[1 2 3]
 [5 7 3]
 [4 9 1]
 [3 5 3]]
(array([0, 2]), array([1, 0]))<jupyter_text>**Note:** The result is a tuple of 1-D arrays having two tuples. The condition is true for two elements at indices:`(0,2)` and `(1,0)`<jupyter_code>import numpy as np
matrix = np.array([[1, 2, 3], [5, 7, 3], [4, 9, 1], [3, 5, 3]])
print("matrix: \n", matrix)
t1 = np.where(matrix%2 == 0, True, False)
print(t1)<jupyter_output>matrix: 
 [[1 2 3]
 [5 7 3]
 [4 9 1]
 [3 5 3]]
[[False  True False]
 [False False False]
 [ True False False]
 [False False False]]
{'fraction_non_alphanumeric': 0.16121103787286337, 'fraction_numerical': 0.08256060775332365, 'mean_word_length': 3.8386486486486486, 'pattern_counts': {'":': 0, '<': 58, '<?xml version=': 0, '>': 58, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 2, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '17', 'dir': 'jupyter-structured-clean-dedup', 'id': '56171', 'license': 'BSL-1.0', 'n_tokens_mistral': 7717, 'n_tokens_neox': 6847, 'n_words': 2082, 'path': 'data-science-master/Section-3-Python-for-Data-Scientists/Lec-3.03(NumPy-03-BasicOperations).ipynb', 'repo_name': 'Hamid-Ali-99/Python_Just_Python', 'size': '17914'}