KEMBAR78
Lecture 6 | PDF | Matrix (Mathematics) | Object Oriented Programming
0% found this document useful (0 votes)
12 views24 pages

Lecture 6

This document covers list comprehension in Python, highlighting its advantages over traditional for loops for creating lists. It also introduces Object-Oriented Programming (OOP) concepts, including classes, objects, and methods, and provides examples of how to define and use classes. Additionally, it discusses the NumPy library for data manipulation and analysis, showcasing how to create and manipulate arrays.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
12 views24 pages

Lecture 6

This document covers list comprehension in Python, highlighting its advantages over traditional for loops for creating lists. It also introduces Object-Oriented Programming (OOP) concepts, including classes, objects, and methods, and provides examples of how to define and use classes. Additionally, it discusses the NumPy library for data manipulation and analysis, showcasing how to create and manipulate arrays.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 24

Lecture-6

List Comprehension in Python


List comprehension is a more readable and compact way to create new lists using loop.
Instead of using multiple lines with a for loop, list comprehension lets you write the logic in a
single line.

Traditional for loop vs List Comprehension

```python

Traditional approach
squares = [] for x in range(1, 6): squares.append(x**2)

Using list comprehension (shorter, cleaner)


squares = [x**2 for x in range(1, 6)]

General Syntax:

```python [expression for item in iterable if condition]

expression: What to do with each item

iterable: A list, range, or other iterable

condition (optional): Filter items based on a condition

In [20]: # Convert a list of names to lowercase

names = ['Ali', 'ZARA', 'Hamza']


lowercase_names = [name.lower() for name in names]
print(lowercase_names)

['ali', 'zara', 'hamza']


[2, 4, 6]

In [24]: # Filter even numbers

numbers = [1, 2, 3, 4, 5, 6, 7, 8]
evens = [num for num in numbers if num % 2 == 0]
print(evens)
[2, 4, 6, 8]

In [25]: # Example: Extracting domain names from email addresses

emails = [
"ali@example.com",
"sara@university.edu",
"admin@myorg.org",
"info@startup.io"
]

# List comprehension to extract domains


domains = [email.split('@')[1] for email in emails]

print(domains)

['example.com', 'university.edu', 'myorg.org', 'startup.io']

Functions (Some Use Cases)

1. Grade Calculator (Education Analytics)


Uses: if-elif-else, loops, return values.

In [26]: # Write code block of below function; Rule: A grade for score 90 and avove, B for 80 a

def calculate_grade(score):

Cell In[26], line 4

^
SyntaxError: incomplete input

In [ ]: # Example usage

scores = [92, 81, 56, 73, 89]

# Use List Comprehesnion to get list of grades for above scores

grades =

print("Grades:", grades)

Click here for the solution

2. Email Validator Function (Data Cleaning Use Case)


Uses: conditions, string operations, Loops.
In [ ]: # Email Validator Function 'valid_email'; Rule: email having @, dot (.) and at least 6

def valid_email(email):

In [13]: # Test cases


emails = ["john@example.com", "alice@", "invalidemail.com", "sara@mail.org"]

print(email[0],'is', valid_email(email[0]))

# write a loop for checking all the emails, print using F-String

s is Invalid
john@example.com is Invalid
alice@ is Invalid
invalidemail.com is Invalid
sara@mail.org is Invalid

Click here for the solution

3. Text Preprocessing Function (NLP Use Case)


Uses: string handling, chaining operations, real-world pre-ML step.

In [15]: # Text Cleaning Function 'clean_text'; Rule: Remove comas, dots, and remove whitespace

def :
text = # fisrt convert to lower for standardization
text = text.replace().replace() #remove dots and comas, note chained operations
text = #remove spaces on both ends; stripe() function is used
return

['hello world!', 'python is fun', 'clean this']

In [16]: sentences = [" Hello, World!", "Python is Fun.", " Clean This. "]

# create a cleaned list calling clean_text() function, use List Comprehension looping

cleaned =
print(cleaned)

['hello world!', 'python is fun', 'clean this']

Click here for the solution

Design a Solution

🧠 Problem: Smart Water Billing System


A local municipality charges households for water based on usage (in cubic meters) with the
following rules:

First 30 cubic meters → flat rate of 500 PKR


For usage above 30 and up to 60 → 15 PKR per extra cubic meter
For usage above 60 → 20 PKR per extra cubic meter after 60, in addition to the previous
charges.

🔧 Task
Write a Python function calculate_bill(units) that:

Takes the water usage as input,


Applies the above rules, and
Returns the final bill amount.

Also, write a loop that:

Asks the user to enter usage values repeatedly,


Ends when the user enters -1 ,
Prints the bill for each input using the function.

❓ Think and Discuss


What are the conditions involved?
What kind of loop do we need?
Where should we use a function?
What are the edge cases to handle (like exactly 30 or 60 units)?

Answers to Guiding Questions


1. What are the conditions involved?
2. What type of loop will help take multiple inputs?
3. Where should you use a function?
4. What are the edge cases?

In [ ]: # Write code here

✅ Click to Reveal the Solution


Object-Oriented Programming (OOP) in
Python
What is OOP?
Object-Oriented Programming (OOP) is a programming paradigm that organizes software
design around objects rather than functions and logic. An object is an instance of a class, and
it can contain both data (attributes) and functions (methods).

OOP allows you to:

[ ] Group related data and functions together


[ ] Make code reusable and easier to maintain
[ ] Model real-world things clearly in code

Key Concepts in OOP


Class
A class is like a blueprint or template for creating objects.

Object
An object is a specific instance of a class with actual values.

Constructor ( __init__ )
A special method called when an object is created. It sets up the initial state.

Attributes
Variables that belong to an object.

Methods
Functions that are defined inside a class and can be used with objects.

Define Your Own Data Type!


OOP (Object-Oriented Programming) is used when you want to define your own data type
— one that not only holds data but also knows how to work with it.
Python's built-in types (like list , int , str and dict etc.) also have their own data +
methods. These are actually defined using classes behind the scenes.

```python my_list = [1, 2, 3] my_list.append(4) # Using a built-in method

In [26]: # See the class nature of built-in type

print(type(str))
x = 'Hi'
print(type(x))

<class 'type'>
<class 'str'>

What if we want our own type — for example, a Student or a Book ?

That's where OOP helps:

[ ] You can create your own type (class)

[ ] It can hold data (attributes) and

[ ] It can do actions (methods)

Why Use OOP?


✅ Code reusability through classes and objects
✅ Better structure and organization

✅ Makes programs easier to debug and extend


✅ Reflects real-world entities better
✅ Define your own data type for your data

A Simple Example
```python

Define a class
class Book: def init(self, title, author): # Constructor self.title = title # Attribute self.author =
author
def describe(self): # Method
print(f"'{self.title}' is written by {self.author}.")

Create an object
book1 = Book("1984", "George Orwell")

Call a method
book1.describe()

Example-I: Book Class


Class Definition:
[ ] In Python, a special method is a defined function that starts and ends with two
underscores and is invoked automatically when certain conditions are met.

[ ] The init special method, also known as a Constructor, is used to initialize the class with
attributes.

[ ] In Python, built-in classes are named in lower case, but user-defined classes are named
in Camel or Snake case, with the first letter capitalized.

[ ] In Python classes, the term self is a reference to the current object (instance) being used
— it mandatory as the first parameter in instance methods to access or modify object
data.

In [27]: # Define Book class


class Book:
def __init__(self, title, author): # Constructor
self.title = title # Attributes
self.author = author

def describe(self): # Method


print(f"'{self.title}' is written by {self.author}.")

Creating Instances/Objects of a Class:


In [28]: # Create two book objects
book1 = Book("The Alchemist", "Paulo Coelho")
book2 = Book("Python Crash Course", "Eric Matthes")

# Call the describe method


book1.describe()
book2.describe()
'The Alchemist' is written by Paulo Coelho.
'Python Crash Course' is written by Eric Matthes.

In [30]: book1.title
book1.title = 'Alche'

In [31]: book1.title

'Alche'
Out[31]:

Initialize with Default Values or None and set later


In [2]: class Book:
def __init__(self, title=None, author=None):
self.title = title
self.author = author

def describe(self):
if self.title and self.author:
print(f"'{self.title}' is written by {self.author}.")
else:
print("Book information is incomplete.")

# Create without data


book = Book()
book.describe() # Output: Book information is incomplete.

# Set values later


book.title = "Sapiens"
book.author = "Yuval Noah Harari"
book.describe() # Output: 'Sapiens' is written by Yuval Noah Harari.

Book information is incomplete.


'Sapiens' is written by Yuval Noah Harari.

Example-II: Student Class


This example introduces classes, objects, constructors (init), instance variables, and methods, all
in accessible and practical context.

In [33]: # Defining a class named Student


class Student:
def __init__(self, name, roll_number, marks):
# This is the constructor that runs when an object is created
self.name = name # Instance variable for student's name
self.roll_number = roll_number # Instance variable for roll number
self.marks = marks # Instance variable for marks out of 100

def display_info(self):
# Method to display basic info
print(f"Name: {self.name}")
print(f"Roll Number: {self.roll_number}")
print(f"Marks: {self.marks}")

def grade(self):
# Method to calculate and return grade
if self.marks >= 80:
return "Excellent"
elif self.marks >= 60:
return "Good"
else:
return "Needs Improvement"

Creating objects (instances) of the Student class


In [34]: # Creating objects (instances) of the Student class
student1 = Student("Areeba", 101, 92)
student2 = Student("Bilal", 102, 67)
student3 = Student("Faizan", 103, 45)

# Calling methods using the objects


student1.display_info()
print("Grade:", student1.grade())
print("------")

student2.display_info()
print("Grade:", student2.grade())
print("------")

student3.display_info()
print("Grade:", student3.grade())

Name: Areeba
Roll Number: 101
Marks: 92
Grade: Excellent
------
Name: Bilal
Roll Number: 102
Marks: 67
Grade: Good
------
Name: Faizan
Roll Number: 103
Marks: 45
Grade: Needs Improvement

OOP Is Useful in AI and Machine Learning


In Machine Learning, we often:

Build a model
Train it on data
Use it to make predictions

Using Object-Oriented Programming (OOP) helps us organize this process.

Example-III: A Simple Classifier with OOP


In [35]: class SimpleClassifier:
def __init__(self, threshold):
self.threshold = threshold

def predict(self, number):


if number >= self.threshold:
return "High"
else:
return "Low"

# Create a classifier with a threshold of 50


model = SimpleClassifier(threshold=50)

# Predict a value
print(model.predict(70)) # Output: High
print(model.predict(30)) # Output: Low

High
Low

Python Libraries for Data Science and AI/ML

Data Manipulation and Analysis

Numpy Library
NumPy, short for Numerical Python, is a fundamental library for numerical and scientific
computing in Python.

[ ] It provides support for large, multi-dimensional arrays and matrices, along with high-
level functions on these arrays.

[ ] The array object in NumPy is called Array (ndarray). Arrays are frequently used in data
science, where speed and resources are very important.

[ ] NumPy is usually imported under the np alias.

[ ] NumPy Array is usually fixed in size and each element is of the same type.

1D Numpy in Python
In [36]: # pip install numpy #if using first time on your computer

# import numpy library.

import numpy as np
Creating Numpy Array from List using Type Casting:
We can cast a list to Numpy array as follows:

In [37]: # Create a numpy array

a = np.array([5, 1, 7, 3, 4])
a

array([5, 1, 7, 3, 4])
Out[37]:

Each element is of the same type in Numpy Array, in this case integers:

Print the even elements in the given array.

In [39]: arr = np.array([1, 2, 3, 4, 5, 6, 7, 8])

# Enter your code here


arr[1:4]

array([2, 3, 4])
Out[39]:

Indexing Choice using a List


Similarly, we can use a list to select more than one specific index. The list select contains
several values:

In [40]: # Create the index list

select = [0, 2, 3, 5]
select

[0, 2, 3, 5]
Out[40]:

We can use the list as an argument in the brackets. The output is the elements corresponding
to the particular indexes:

In [42]: # Use List to select elements

d = arr[[0, 5, 1]]
d

array([1, 6, 2])
Out[42]:

We can assign the specified elements to a new value. For example, we can assign all these
values to 100 000 as follows:

In [43]: # Assign the specified elements to new value

arr[select] = 100000
arr
array([100000, 2, 100000, 100000, 5, 100000, 7, 8])
Out[43]:

Some Basic Attributes of Numpy Array


Let's review some basic array attributes using the array a :

In [44]: # Create a numpy array by casting [0, 1, 2, 3, 4]

a = np.array([0, 1, 2, 3, 4])
a

array([0, 1, 2, 3, 4])
Out[44]:

The attribute size is the number of elements in the array:

In [45]: # Get the size of numpy array

a.size

5
Out[45]:

The next two attributes will make more sense when we get to higher dimensions but let's
review them.

The attribute ndim represents the number of array dimensions, or the rank of the array. In
this case, 1:

In [46]: # Get the number of dimensions of numpy array

a.ndim

1
Out[46]:

The attribute shape is a tuple of integers indicating the size of the array in each dimension:

In [47]: # Get the shape/size of numpy array

a.shape

(5,)
Out[47]:

Numpy Statistical Functions


In [48]: # Create a numpy array

a = np.array([1, 2, 5, 11])

In [49]: # Get the mean of numpy array

mean = a.mean()
mean
4.75
Out[49]:

In [50]: # Get the standard deviation of numpy array

stdv = a.std()
stdv

3.897114317029974
Out[50]:

In [51]: # Create another numpy array

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

array([-1, 2, 3, 4, 5])
Out[51]:

In [52]: # Get the biggest value in the numpy array

max_b = b.max()
max_b

5
Out[52]:

In [53]: # Get the smallest value in the numpy array

min_b = b.min()
min_b

-1
Out[53]:

Arithmetic Operations: (Vector Operations/Element-


Wise)
You could use arithmetic operators directly between NumPy arrays. Number of elements need
to be same.

Addition
In [54]: u = np.array([1, 0])
v = np.array([0, 1])
np.add(u, v)
# u + v also works

array([1, 1])
Out[54]:

In [55]: # What about doing this with LIST?

l1 = [1, 0]
l2 = [0, 1]
l1+l2

# l1 + 2 #Not applicable
# l1 * 5 # concat 5 timees
# l1 - l2 #Not applicable

[1, 0, 0, 1]
Out[55]:

This operation is equivalent to vector addition:

Adding Constant

Consider the following array: Adding the constant 1 to each element in the array:

In [56]: # Create a constant to numpy array

u = np.array([1, 2, 3, -1])
u + 2

# Not possible with LIST

array([3, 4, 5, 1])
Out[56]:

Subtraction
Consider the numpy array a and numpy array b:

In [57]: a = np.array([10, 20, 30])


b = np.array([5, 30, 15])
np.subtract(a, b) # a- b is also applicable

array([ 5, -10, 15])


Out[57]:

Multiplication (Element-Wise)
Consider the vector numpy array y :

In [58]: # Create numpy arraya

x = np.array([1, 2])
y = np.array([2, 1])

In [59]: z = np.multiply(x, y) #x*y can also be used


z

array([2, 2])
Out[59]:

We can also multiply every element in the array by a number:

In [60]: # Numpy Array Multiplication

z = np.multiply(y, 2)
z

array([4, 2])
Out[60]:
This is equivalent to multiplying a vector by a scaler:

Division
In [61]: a = np.array([10, 20, 30])
b = np.array([2, 10, 3])

We can divide the two arrays and assign it to c:

In [62]: c = np.divide(a, b) #will give error if no. of elements not same


c

array([ 5., 2., 10.])


Out[62]:

Important: Dot Product: Matrix (Vector)


Multiplication
Not Element-wise Product
The dot product "⋅" is also known as scalar product and is defined as the sum of pairwise
multiplication.

(Matrix product is defined between two matrices. Dot product is defined between two vectors.)

The dot product of the two numpy arrays u and v is given by:

In [ ]: u = np.array([1, 2])
v = np.array([3, 2])

In [ ]: # Calculate the dot product

np.dot(u, v) #1*3 + 2*2

Iterating 1-D Arrays


Iterating means going through elements one by one. If we iterate on a 1-D array it will go
through each element one by one.

In [ ]: #If we execute the numpy array, we get in the array format,


#if we use print function, it gives in the form of a list

arr1 = np.array([1, 2, 3])

print(arr1)

But if you want the elements (not in the form of the list), then you can use for loop:
In [ ]: for x in arr1:
print(x)

2D Numpy in Python

Create a 2D Numpy Array


In [ ]: # Import the libraries

import numpy as np

#import matplotlib.pyplot as plt

In [ ]: a0 = [[11, 12, 13]]

A0 = np.array(a0)
A0

# A[A0 > 11] you can even apply conditions like this
# print(np.where(A0 == 13) #, 66, A0)) #where function

In [ ]: import numpy as np

x = np.array([1, 2, 3, 4, 5, 6])
y = np.array([10, 20, 30, 40, 50, 60])

result = np.where(x > 2 , x , y)

print(result)

#The new array will contain elements from the x array where the condition x > 2 is tru
# and elements from the y array where the condition is false.

Consider the list a , which contains three nested lists each of equal size.

In [ ]: # Create a list

a = [[11, 12, 13], [21, 22, 23], [31, 32, 33]]


a

We can cast this list to a 2D Numpy Array as follows:

In [ ]: # Convert list to Numpy Array


# Every element is the same type

A = np.array(a)
A

We can use the attribute ndim to obtain the number of axes or dimensions, referred to as the
rank.
In [ ]: # Show the numpy array dimensions
# A.ndim
A.ndim

Attribute shape returns a tuple corresponding to the size or number of each dimension.

In [ ]: # Show the numpy array shape


# A.shape

A.shape

The total number of elements in the array is given by the attribute size .

In [ ]: # Show the numpy array size


# A.size
A.size

Accessing Elements of a 2-D Numpy Array


We can use rectangular brackets to access the different elements of the array.

The correspondence between the rectangular brackets and the list and the rectangular
representation is shown in the following figure for a 3x3 array:

We can access the 2nd-row, 3rd column as shown in the following figure:
We simply use the square brackets and the indices corresponding to the element we would like:

In [ ]: # Access the element on the second row and third column

A[1, 2]

We can also use the following notation to obtain the single element: but using one bracket is
better

In [ ]: # Access the element on the second row and third column

A[1][2]

Consider the element shown encircled in the following figure

We can access the element as follows:

In [ ]: # Access the element on the first row and first column

A[0, 0]
We can also use Slicing in numpy arrays. Consider the following figure. We would like to obtain
the first two columns in the first row

This can be done with the following syntax:

In [ ]: # Access the element on the first row and first and second columns

A[0:1, 0:2]

Similarly, we can obtain the first two rows of the 3rd column as follows:

In [ ]: # Access the element on the first and second rows and third column

A[0:2, 2:]

Corresponding to the following figure:


Basic Aithmatic Operations: Element-Wise
We can also add arrays. The process is identical to matrix addition. Matrix addition of X and Y
is shown in the following figure:

The numpy array is given by X and Y

In [ ]: # Create a numpy array X

X = np.array([[1, 0], [0, 1]])


X

In [ ]: # Create a numpy array Y

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


Y

We can add the numpy arrays as follows.

In [ ]: # Add X and Y

Z = X + Y
Z

Multiplying a numpy array by a scaler is identical to multiplying a matrix by a scaler.

If we multiply the matrix Y by the scaler 2, we simply multiply every element in the matrix by 2,
as shown in the figure.
We can perform the same operation in numpy as follows

In [ ]: # Create a numpy array Y

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


Y

In [ ]: # Multiply Y with 2

Z = 2 * Y
Z

(Basic) Multiplication of two arrays corresponds to an element-wise product also called


Hadamard product.

Consider matrix X and Y .

The Hadamard product corresponds to multiplying each of the elements in the same position,
i.e. multiplying elements contained in the same color boxes together.

The result is a new matrix that is the same size as matrix Y or X , as shown in the following
figure.
We can perform element-wise product of the array X and Y as follows:

In [ ]: # Create a numpy array Y

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


Y

In [ ]: # Create a numpy array X

X = np.array([[1, 0], [0, 1]])


X

In [ ]: # Multiply X with Y

Z = X * Y
Z

Important: Matrix Multiplication (Dot Product)


A dot product of a matrix is a basic linear algebra computation used in machine learning
models to complete operations with larger amounts of data more efficiently. It's the result of
multiplying two matrices that have matching rows and columns, such as a 3x2 matrix and a
2x3 matrix.

We can perform Matrix Multiplication with the numpy arrays A and B as follows:

[Matrix Multiplication Rule applies: Multiplication possible only if No. of Columns of 1st Array
must be Equal to No. of Rows of 2nd]

First, we define matrix A and B :

In [ ]: # Create a matrix A

A = np.array([[0, 1, 1], [1, 0, 1]])


A

In [ ]: # Create a matrix B

B = np.array([[1, 1], [1, 1], [-1, 1]])


B

We use the numpy function dot to multiply the arrays together (Matrix Multiplication).

In [ ]: # Calculate the dot product

Z = np.dot(A,B)
Z

In [ ]: # You can Calculate the sine of Z

np.sin(Z)
#np.cos(Z)
#np.tan(Z)

Transposed Matrix
We use the numpy attribute T to calculate the Transposed Matrix.

In [ ]: # Create a matrix C

C = np.array([[1,1],[2,2],[3,3]])
C

In [ ]: # Get the transposed of C

C.T

Numpy Mathematical Functions

numpy.zeros array
numpy.zeros(shape, dtype=float, order='C', *, like=None)

Return a new array of given shape and type, filled with zeros.

In [ ]: np.zeros(5)

In [ ]: np.zeros(5, dtype=int)

In [ ]: np.zeros((5, 3), dtype=int) #Returns a 2D array

Numpy Random Number Genrator


random.rand(x) genrates a list of x numbers all between 0 to 1.0. We will need this in our data
splitting.

In [ ]: #from numpy import random,

import numpy as np

# Generate Random Float from 0 to 1

x = np.random.rand()
x

In [ ]: # Generate a 1-D array containing 5 random floats:

x = np.random.rand(5) #< 0.5

#x = np.random.rand(6) < 0.5 # this will result in a True False list


print(x)

In [ ]: # Generate a 2-D array with 3 rows, each row containing 5 random numbers:

x = np.random.rand(3, 5)

print(x)

In [ ]: #Generate a random integer from 0 to 100:


x = np.random.randint(100)

print(x)

In [ ]: # Generate a 1-D array containing 5 random integers from 0 to 100:

x=np.random.randint(100, size=(1000,1000))

print(x)

You might also like