Laboratory 1b: Python Review#

Programming Skills:

  1. Iterations

  2. Functions

  3. Conditional statements

Required Hardware: None

Write you name and email below

Name:

Email:

Importing necessary libraries#

import numpy as np  # Numpy will assist in manipulating datastructures like arrays.
import matplotlib.pyplot as plt # Matplotlib is MATLAB implementation in python for the purpose of plotting and visualising data.
from matplotlib import rcParams  # rcParams

rcParams.update({'font.size': 20})  # for setting text size in plots

Defining variable and understanding their data type

# --- INTEGER DEFINITION ---
# If no decimal place is provided, Python considers it an integer
a = 9

# --- FLOAT DEFINITION ---
# Specifying a decimal point creates a float
b = 6.05

# --- STRING DEFINITION ---
# Strings are collections of characters, defined with '' or ""
c = 'for'
d = "good"

# --- LIST DEFINITION ---
# Lists are flexible containers that can hold multiple data types
e = [a, b, c]

print("Datatype of variable a = ", type(a))
print("Datatype of variable b = ", type(b))
print("Datatype of variable c = ", type(c))
print("Datatype of variable d = ", type(d))
print("Datatype of variable e = ", type(e))
Datatype of variable a =  <class 'int'>
Datatype of variable b =  <class 'float'>
Datatype of variable c =  <class 'str'>
Datatype of variable d =  <class 'str'>
Datatype of variable e =  <class 'list'>
# Type conversion from foat to int and int to float
print(int(9.08))
print(float(98))
9
98.0

Common operations on simple variables:

  1. Addition/Subtraction: +, - — for numeric variables these perform arithmetic (e.g., \(3 + 2 = 5\)). For sequences like lists or strings, + performs concatenation.

  2. Multiplication/Division: *, /* multiplies numbers and can repeat sequences; / performs floating-point division (e.g., \(5 / 2 = 2.5\)).

  3. Exponentiation: ** — raises a number to a power (e.g., \(2^3\) is written as 2 ** 3).

  4. Root: Use np.sqrt() for square roots or fractional powers for \(n^{th}\) roots (e.g., 8 ** (1/3) for \(\sqrt[3]{8}\)).

Data Structures#

In computer languages, datastructures are a systematic way to represent collections of data. This allows us to manipulate, transform, or search through an entire dataset efficiently.

The most common structures in Python are lists and arrays. Both are sequential, meaning elements are arranged linearly and can be accessed via their position (index).

Distinction between lists and arrays#

List (Python list)#

A general-purpose container that can hold a mixture of different types (integers, strings, even other lists).

  • Pros: Flexible, built-in.

  • Cons: Slow for math, uses more memory.

Array (NumPy array)#

Specialized for numerical data. All elements must usually be of the same type (e.g., all floats).

  • Pros: Extremely fast, supports vectorization (applying math to the whole array at once).

  • Cons: Less flexible with mixed data types.

Common list commands:#

  1. Append: list.append(x) adds element x to the end.

  2. Delete: del list[i] removes the element at index i.

  3. Insert: list.insert(i, x) adds element x at index position i.

# Example for working with list
list1 = [3, 14.98, 'cab', np.sqrt(2)]
print(list1)

list1.append(np.sqrt(9))
print(list1)

list1.insert(2, -20)  # Adds -20 at index 2 (the 3rd position)
print(list1)

Working with sequential data structures: Visualization

Both lists and arrays are ordered sequences. Imagine them as a row of boxes, where each box has a specific address called an Index.

Zero-Based Indexing: In Python, we start counting from 0, not 1.

Index

0

1

2

3

4

Content

10

20

30

40

50

  • my_list[0] would give you 10.

  • my_list[4] would give you 50.

We use Iterators (like a for loop) to visit each box in sequence and perform an action.

# example for using for-loop
for i in range(1, 10):
    print("iterating variable value = ", i)
    print("Square of the iterating variable = ", i*i)
# example for summing elements using an index-based loop
array1 = np.array([1, -1, 9, -4, 5])
sum_val = 0
for i in range(0, 5):
    sum_val = sum_val + array1[i]  # the '[i]' keyword after the 'array1' allows one to access the 'ith' element of the array.

print("Total sum of all the elemets =", sum_val)

Problem 1: We want to compute: $\(\sum_{i=1}^{50} i\)$ Start with the code below and fill in the missing pieces.

total = 0

for i in range(____, ____):
    total = total + ____

print(total)

Introduction to if-else statements

An if-else statement is a conditional branch. It allows the program to make decisions based on whether a specific condition is True or False.

  1. if: The computer checks the condition. If it is true, the code block directly under it runs.

  2. elif (Optional): Short for “else if.” Checked only if the first if was false.

  3. else: This is the “catch-all.” It runs only if none of the above conditions were met.

Syntax structure:

if condition == True:
    # runs this block
else:
    # runs this block instead
# Determine whether a number is positive, negative, or zero
num = 10
if num > 0:
  print("number is positive")
elif num < 0:
  print("number is negative")
else:
  print("number is zero")
number is positive
# Example for if-else
number_range_lower_bound = 1
number_range_upper_bound = 11

for i in range(number_range_lower_bound, number_range_upper_bound):
    if i % 2 == 0:     # Here the '%' operator checks for remainder. If a number is divisible it return 0, else it gives out the remainder.
        print("iterator ", i, " is even")
    else:  # whenever the remainder is non-zero the if-block fails and the else-block is executed.
        print("iterator ", i, " is odd")

# Note: The computer will execute any one of the condition blocks. When if-block is satisfied, the else-block won't be executed and vice-versa.

Problem 2: Iterate through the numbers from 1 to 50 and find all the numbers that are divisible by 3.

Store all the numbers divisible by 3 in a new list and print that list out.

# Write code here

Introduction to in-built functions#

Python provides many ready-to-use functions:

  1. np.array(my_list): Converts a Python list into a NumPy array.

  2. len(object): Returns the number of items in a list, array, or string.

# Example of list to array conversion and their dimensions.
list1 = [3, 14.98, 'cab', np.sqrt(2)]
array3 = np.array(list1)
print(array3, array3[2])
print(len(array3[2]), len(list1))

Problem 3: Define a list [-10, -9, …, 0, 1, 2,…, 9, 10] i.e., integers from -10 to 10, and call it x. Then define a new list ‘y’ according to the equation $\(y = 4x^2\)$ Then plot ‘y vs x’. The shape of the graph should resemble a prabola.

# Write your code here

Introduction to user-defined functions#

A User-Defined Function is a reusable block of code that performs a specific task. We define them using the def keyword.

How to define a function:

  1. def keyword: Tells Python you are creating a function.

  2. Function Name: A descriptive name (e.g., calculate_area).

  3. Arguments (Inputs): Variables passed inside the parentheses () that the function uses to work.

  4. The Colon :: Ends the function header.

  5. Indented Body: The actual logic of the function.

  6. return statement: Sends the final result back to the user.

Basic Example:

def greet_user(name):
    message = "Hello " + name
    return message
# Example function for adding
def add_nos(a, b):
    c = a + b
    return c
# Example for calling the function defined above
x, y = 9, 6
z = add_nos(x, y)
print("Result obtained from the function above = ", z)

Problem 3: Define a function to find the roots of a quadratic polynomial: $\(ax^2 + bx + c = 0\)$

You can use the quadratic formula:

\[root_1 = \frac{-b + \sqrt{b^2 - 4ac}}{2a}\]
\[root_2 = \frac{-b - \sqrt{b^2 - 4ac}}{2a}\]
# define function
# call function

Common in-built functions for arrays:#

  1. np.linspace(start, stop, N): Generates \(N\) equally spaced elements.

    • Example: x = np.linspace(0, 10, 5) creates [0, 2.5, 5, 7.5, 10]

  2. np.asarray(list): Converts a Python list to a NumPy array.

    • Example: arr = np.asarray([1, 2, 3]) converts the list to an array.

  3. np.size(array): Returns the total number of elements in the array.

    • Example: np.size(np.array([10, 20])) returns 2.

Problem 4: Approximate the integral $\(I = \int_0^1 x^2\ dx\)$ by the left Riemann sum.

Recall, the Riemann sum is a method of approximating the area under the curve \(f(x)\) by summing up rectangles. (See the figure below: source: https://openstax.org/books/calculus-volume-1/pages/5-1-approximating-areas)

riemann_sum_image.png

Denote the Left Riemann sum by L_N, and partition the interval \([0,1]\) into N equal subintervals. Then: $\(L_N = \sum_{i=0}^{N-1} f(x_i) \Delta x\)$

where the step size \(\Delta x = \frac{1}{N}\) and \(x_i = i \Delta x\).

(a) Write a code that computes \(L_N\) for \(N=10\).

(b) Take the code in part (a) and convert it into a function that computes \(L_N\) for any integer \(N\).

(c) Let \(N_{vals} = [10, 100, 1000]\) and compute for each of these values. Then plot \(N\) vs \(L_N\).

Does \(L_N\) approach the value of \(I = \int_0^1 x^2\ dx\)?

# Write your code here

Problem 5: Lets find where the consecutive values in a list changes sign i.e., goes from negative to positive or the other way. Let x be a list of numbers from -2 to 2 with 100 divisions, and a list which stores the function $\(y = x^3 - x.\)$

(a) Write a function to calculate list ‘y’

(b) Given two numbers \(a\) and \(b\), what does the sign of their product \(ab\) tell you about the signs of \(a\) and \(b\)?

(c) Write code to find when the consecutive values in the list ‘y’ change sign.

(d) Write code to count how many times y switched sign.

 # Write your code here

Boolean Indexing#

Allows you to select data from an array using a set of True/False conditions.

x = np.array([-3, -1, 2, 5, -4])
x>0
array([ True,  True,  True,  True,  True,  True,  True])

Run the code above and answer the following questions:

  • What type of object is the output?

  • How long is it?

  • Which entries are true?

  • What happens when you change the last line to:

x < 0
x = np.array([-3, -1, 2, 5, -4])
mask = x > 0
x[mask]
array([2, 5])

Run the code above and answer the following questions:

  • What does x[x>0] return?

  • What does x[x>2] return?

This idea is called Boolean indexing. Boolean indexing is a way of selecting elements from an array using a boolean (True/False) array of the same length.

# Example for boolean indexing
x = np.array([1, 3, 5, 7, 6, 4, 2])
y = np.array([True, True, True, False, False, True, False])
print("Filtered x: ", x[y])
Filtered x:  [1 3 5 4]
# Another example for boolean indexing for fiteration
# A list of temperature readings in Celsius
temps = np.array([-5.2, 12.5, 0.4, -1.8, 25.0, 3.2, -10.5])

# 1. Create a Boolean mask
# This checks which elements are greater than 0
mask = temps > 0
print("Boolean Mask:", mask)
# Output: [False  True  True False  True  True False]

# 2. Use the mask to index the array
# This returns only the values where the mask is True
above_freezing = temps[mask]

print("Values above freezing:", above_freezing)
# Output: [12.5  0.4 25.  3.2]
Boolean Mask: [False  True  True False  True  True False]
Values above freezing: [12.5  0.4 25.   3.2]

Slicing#

We can also take slices of an array.

arr[a:b]
  • This selects elements starting at index a

  • This stops selecting elements before index b

  • Index b is not included

arr[1:]
  • This selects elements starting at index 1

  • Recall, in python, arrays start at index 0

  • The : with nothing after it means go to the end

  • It returns all elements except the first one

arr[:-1]
  • The : with nothing before it means start at index 0

  • The -1 refers to the last index

  • It returns all elements except the last one

# --- Example of [:-1] and [1:] Indexing ---
arr = np.array([10, 20, 30, 40, 50])
print(arr)

# arr[:-1] gets all elements EXCEPT the last one: [10, 20, 30, 40]
current_elements = arr[:-1]
print(current_elements)

# arr[1:] gets all elements EXCEPT the first one: [20, 30, 40, 50]
next_elements = arr[1:]
print(next_elements)

# By comparing them, we can find the difference or sign change between neighbors
diff = next_elements - current_elements
print("Difference between neighbors:", diff)
[10 20 30 40 50]
[10 20 30 40]
[20 30 40 50]
Difference between neighbors: [10 10 10 10]

Problem 6: Redo problem 5 by using in-built function of NumPy package like numpy.array(), np.sign() and Boolean mask.

Hint: For efficent comparision of adjacent elements in the array you can use array filtering using [:-1] and [1:] as shown previously in this notebook.

# Write your code here