Sunday, May 24, 2026

Python Material - Part - 33 - Numpy_part_1

 __author__ = "Narendra Boyina"


"""Concepts to be Covered on numpy concept.
1. Introduction to NumPy
2. Create Arrays using NumPy
a. Create integer/float/ heterogeneous arrays
b. Create a NumPy array using a tuple
3. Create arrays in multiple dimensions
a. Create a 0D array, 1D array, 2D array, 3D array
4. Accessing Elements
a. Individual elements accessed using the Index from 1-D, 2-D, 3-D arrays
b. Range of elements accessed using the Index from 1-D, 2-D, 3-D arrays
c. Step elements accessed using the Index from 1-D, 2-D, 3-D arrays
c. Accessing elements with Omitting Indices from 1-D, 2-D, 3-D arrays
d. Elements accessing using Negative indexing for(1D,2D,3D arrays)
e. Fancy Indexing
5. Properties of nd Arrays
a. shape
b. Data Type
c. size
d. Itemsize
6. Array Operations - NumPy
a. Arithmetic operations
b. Relational operations


"""
####### Introduction of numpy package ############
"""
What is NumPy?
--> NumPy (Numerical Python) is an external library that works with arrays.
--> NumPy has functions for working in the domain of linear algebra, Fourier transforms, and matrices.
--> NumPy was created in 2005 by Travis Oliphant.

Why is NumPy Faster Than Lists?
--> The drawback of lists are slow to process.
--> NumPy aims to provide an array object that is up to 50 times faster than Python lists.
--> NumPy arrays are stored at one continuous place in memory, unlike lists, so processes can access and manipulate them very efficiently.
--> NumPy arrays are optimised to work with the latest CPU & GPU architectures.

Purpose of NumPy?
--> NumPy transforms Python into a high-performance computing language/tool, particularly well-suited for tasks involving arrays, mathematics, statistics, and data science.
Or for more clarity, read the points below to understand the purpose of NumPy
1. Powerful N-Dimensional Arrays
2. Fast Mathematical Operations
3. Broadcasting and Vectorization
4. Integration with Other LibrariesVectorisation
Backbone of libraries like Pandas, SciPy, TensorFlow, scikit-learn, etc.
Enables interoperability with C, C++ code for performance
5. Random Number Generation & Statistics
Useful for simulations, machine learning, and data generation
Offers tools for statistical analysis (mean, std, correlation, etc.)
6. Data Analysis & Processing
Supports reading from and writing to CSV, text, and binary formats
Helps preprocess large datasets efficiently
"""

import numpy as np

# Checking NumPy Version
""" The version string is stored under __version__ attribute."""
print(np.__version__) # 2.2.4

'''
############## Create Arrays using NumPy ##################
--> The array object in NumPy is called ndarray.
--> We can create a NumPy ndarray object by using the "array()" function.
Note:
List is a comma-separated values [1,2,3]
An array is a space separated values [1 2 3]
'''
""" Creating an integer array """
create_int_arr = np.array([1, 2, 3, 4, 5]) # creating integer array
print(create_int_arr)

"""creating float array """
create_float_arr = np.array([1.5, 2.5, 3.5, 4.5, 5.5]) # creating float array
print(type(create_float_arr)) # This built-in function tells us the type of the object passed to it(<class 'numpy.ndarray'>)
print(create_float_arr)

"""creating heterogeneous array """
create_heterogeneous_arr = np.array(["a", 1, "c", 5.5, "e"]) # creating heterogeneous array
# print(type(create_heterogeneous_arr)) # This built-in function tells us the type of the object passed to it(<class 'numpy.ndarray'>)
# print(create_heterogeneous_arr)
""" Note: To create an ndarray, we can pass a list/tuple/any array-like object into the "array()" method,
then input will be converted into an ndarray """

""" Create a NumPy array using a tuple """
create_arr_with_tuple = np.array((1, 2, 3, 4, 5))
print(create_arr_with_tuple)

""" ############## Create arrays in multiple dimensions ############## """
''' Create a 0-Dimensional Array '''
object_0D_arr = np.array(30)
print(object_0D_arr)

'''
Create a 1-Dimensional Array
1-Dimensional array: A collection of 0-dimensions will form one-dimension
A one-dimensional array in NumPy is a collection of elements arranged in a single row '''

object_1D_arr = np.array([1, 2, 3, 4, 5])
print(object_1D_arr) # Output: [1 2 3 4 5]

"""Create a 2-Dimensional Array
2-Dimensional array: An array that has 2-D arrays as its elements
(or)
An array with rows and columns (like a matrix)"""

object_2D_arr = np.array([[10, 20, 30, 40, 50], [4, 5, 6, 7, 8]]) # 2 rows, 5 columns
print(object_2D_arr)

"""Create a 3-Dimensional Array
3-Dimensional array: An array that has 2-D arrays as its elements is called a 3-D array"""

object_3D_arr = np.array([ [[1, 2, 3], [4, 5, 6]], [[7, 2, 3], [8, 5, 6]] ])
print(object_3D_arr)

""" A 3-dimensional array has: 2 blocks
Each block has 2 rows
Each row has 3 columns """

############## Elements accessing using Index for (1D,2D,3D arrays) ##################
""" Elements accessing using Index for 1-Dimensional Array
Indexing refers to accessing individual elements of an array using their position (index) within the array.
Index always starts with 0 and ends with -1"""

object_1D_arr = np.array([9, 5, 7, 3, 6, 2, 4])
print(object_1D_arr) # Output:

print(object_1D_arr[0]) # Output: 9
print(object_1D_arr[1]) # Output: 5
print(object_1D_arr[-1]) # Output: (negative indexing)

''' Accessing range of elements from an 1D array syntax: object_1D_arr[start:stop]
where start is the starting index (inclusive), stop is the ending index (exclusive) '''

# print(object_1D_arr[1:4]) # Output: [5 7 3] # Accessing elements from index 1 to index 4 (exclusive)
# print(object_1D_arr[2:5]) # Output: ?

""" Step elements accessing using Index for 1-Dimensional Array """
# print(object_1D_arr[0:6:2]) # Output: [9 7 6] # Accessing elements from index 0 to index 6 with step size 2

''' Accessing with Omitted Indices for 1-Dimensional Array

We can omit any of the slicing parameters to use default values.
Omitting start defaults to 0, omitting stop defaults to the end of the array, and omitting step defaults to 1'''

object_1D_arr = np.array([9, 5, 7, 3, 6, 2, 4])

# Accessing by omitting the start Index
# print("Accessing with omitted start:", object_1D_arr[:3]) # Output: [9 5 7]

# Accessing by omitting the stop Index
# print("Accessing with omitted stop:", object_1D_arr[2:]) # Output: [7 3 6 2 4]

# Accessing by omitting start & stop Index
# print("Accessing with omitted step:", object_1D_arr[::2]) # Output: [9 7 6 4]

# Accessing step elements without using omitting concept
# print("Accessing step elements without using omitting concept: ", object_1D_arr[1:5:2])


""" Elements accessing using Index for 2-Dimensional Array"""

object_2D_arr = np.array([[10, 20, 30, 40, 50], [4, 5, 6, 7, 8]]) # 2 rows, 5 columns
# print(object_2D_arr)
'''Here [10, 20, 30, 40, 50] is 0 and then inside starts with 0,1,2
[4, 5, 6, 7, 8]] is 1'''

# print('2nd element on 1st row: ',object_2D_arr[0, 1]) # 20
# print('3rd element on 1st row: ',object_2D_arr[0, 2]) # 30
# print('3rd element on 2nd row: ',object_2D_arr[1, 2]) # ?

''' Elements accessing using Negative indexing for 2-Dimensional Array'''
# print('Last element from 1st row: ', object_2D_arr[0, -1]) # 50
# print('Last element from 2nd row: ', object_2D_arr[1, -1]) # 8
# print('2nd Last element from 2nd row: ', object_2D_arr[1, -2]) # ?

"""Accessing range of elements from an 2D array syntax: object_1D_arr[row, start:stop] """
# print("Accessing range of elements from 0'th row: ",object_2D_arr[0, 1:4]) # [20 30 40]
# print("Accessing range of elements from 1st row: ",object_2D_arr[1, 1:4]) # [5 6 7]


""" Elements accessing using Index for 3-Dimensional Array
Syntac: object_3D_arr[block_index, row_index, column_index] """

object_3D_arr = np.array([ [[44, 78, 33], [51, 45, 60]], [[70, 29, 34], [81, 57, 65]] ])

'''In the above 3d array
[[44, 78, 33], [51, 45, 60]] is the jth block {used for block_index} and then inside row_index starts with 0,1 again inside starts with column_index 0,1,2,3..
[[70, 29, 34], [81, 57, 65]] is 1st block {used for block_index} and then inside row_index starts with 0,1 again inside starts with column_index 0,1,2,3.. '''

# print(object_3D_arr[0, 0, 0]) # 44
# print(object_3D_arr[0, 1, 1]) # 45
# print(object_3D_arr[1, 1, 0]) # 81
# print(object_3D_arr[1, 1, 2]) # 65

''' Elements accessing using Negative indexing for 3-Dimensional Array'''
# print(object_3D_arr[0, 1, -1]) # 60
# print(object_3D_arr[1, 1, -2]) # 57

"""Accessing range of elements from an 3D array
syntax: object_3D_arr[start_block:end_block, start_row:end_row, start_col:end_col]
Break down syntax : object_3D_arr[block, row, column_range]
"""
# print("Accessing range of elements : ", object_3D_arr[0, 1, 1:3]) # [45 60]
# print("Accessing range of elements : ", object_3D_arr[1, 1, 1:3]) # [57 65]

""" # Fancy Indexing:
Fancy indexing allows you to select elements from an array using arrays of indices.
You provide arrays of indices along each axis, and the elements at those indices are returned as a new array """

# Create a 1D array
arr_1d = np.array([21, 42, 53, 64, 85, 41, 54, 67, 89])

# Fancy indexing
indices = [0, 4, 7]
selected_elements = arr_1d[indices]
# print("Selected elements:", selected_elements)


""" ################# Properties of nd Arrays #################
1. shape
2. Data Type
3. size
4. Itemsize"""

"""# 1. shape: Describes the size of each dimension of the array.
It is represented as a tuple of integers indicating the number of rows and columns """

object_2D_arr = np.array([[10, 20, 30, 40, 50], [4, 5, 6, 7, 8]])
# print("Shape of the array:", object_2D_arr.shape) # Output: (2, 5) --> 2 rows, 5 columns

""" # 2. Datatype (dtype): specifies the type of elements stored in the array"""
# print("Data type of the array:", object_2D_arr.dtype) # Output: int64

object_2D_arr = np.array([[1.0, 2.0, 3.0, 4.0, 5.0], [4.1, 5.1, 6.1, 7.1, 8.1]])
# print("Data type of the array:", object_2D_arr.dtype) # Output: float32

""" # 3. Size: The size of an ndarray is the total number of elements in the array """
# print("Size of the array: ", object_2D_arr.size) # Output: 10

""" # 4. Itemsize: itemsize attribute of an array tells you how many bytes each individual element of the array takes in memory.
Why it matters:
It's useful for understanding memory usage.
It helps in optimizing performance for large arrays.
When transferring data between systems (e.g., binary files), you need to know how much each element occupies."""

arr = np.array([1, 2, 3], dtype=np.int32) # (for 32-bit integer --> CPU Type 32-bit Architecture)
# print("Size of each element (in bytes): ", arr.itemsize) # Output: 4
arr = np.array([1, 2, 3], dtype=np.int64) # (for 64-bit integer --> CPU Type 64-bit Architecture)
# print("Size of each element (in bytes): ", arr.itemsize) # Output: 8

""" Note:
For the latest CPU s By default 64-bit Architecture, So if you haven't provided by default element itemsize 8 Bytes
Example given below """
arr = np.array([1, 2, 3])
# print("Size of each element (in bytes): ", arr.itemsize) # Output: 8


""" ############# NumPy - Array Operations #############
1. Basic Arithmetic Operations
2. Inbuilt Arithmetic Operations
2. Relational Operations """

########### 1. Basic Arithmetic Operations ###########

# Addition
arr_sum = np.add([1, 2, 3], [4, 5, 6])
#print("Addition:", arr_sum) # Output: [5 7 9]

# Subtraction
arr_diff = np.subtract([5, 6, 7], [2, 3, 1])
#print("Subtraction:", arr_diff) # Output: [3 3 6]

# Multiplication
arr_mul = np.multiply([2, 3, 4], [3, 4, 5])
#print("Multiplication:", arr_mul) # Output: [ 6 12 20]

# Division --> Always returns the output in the float data type
arr_div = np.divide([10, 12, 14], [2, 3, 2])
# print("Division:", arr_div) # Output: [5. 4. 7.]

# Modulus --> which gives the reminder value
arr_mod = np.mod([10, 11, 12], [3, 4, 5])
#print("Modulus:", arr_mod) # Output: [1 3 2]

# Exponentiation
arr_pow = np.power([2, 3, 4], [2, 3, 2])
#print("Exponentiation:", arr_pow) # Output: [ 4 27 16]

"""########### 2. Inbuilt Arithmetic Operations ###########"""

# Create a sample array
arr = np.array([1, 2, 3, 4, 5])

# Element-wise addition
result_add = np.add(arr, 2) # Add 2 to each element
# print("Addition:", result_add)

# Element-wise multiplication
result_mul = np.multiply(arr, 3) # Multiply each element by 3
# print("Multiplication:", result_mul)


"""########### Relational Operations ########### """

# Create sample arrays
arr_1 = np.array([1, 2, 3, 4])
arr_2 = np.array([2, 2, 4, 3])

# Equal
#print("Equal:", arr_1 == arr_2) # Output: [False True False False]

# Not Equal
#print("Not Equal:", arr_1 != arr_2) # Output: [ True False True True]

# Greater Than
#print("Greater Than:", arr_1 > arr_2) # Output: [False False False True]

# Greater Than or Equal To
#print("Greater Than or Equal To:", arr_1 >= arr_2) # Output: [False True False True]

# Less Than
#print("Less Than:", arr_1 < arr_2) # Output: [ True False False False]

# Less Than or Equal To
#print("Less Than or Equal To:", arr_1 <= arr_2) # Output: [ True True True False]




No comments:

Post a Comment