Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
0% found this document useful (0 votes)
35 views

Python Detailed Notes

The document provides detailed notes on Python programming, covering fundamental concepts such as printing output, variable assignment, and basic data types. It explains how to perform arithmetic operations, create and manipulate lists, and handle different data types, including strings, integers, and booleans. Additionally, it highlights Python's dynamic typing and the ability to store heterogeneous data in lists.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
35 views

Python Detailed Notes

The document provides detailed notes on Python programming, covering fundamental concepts such as printing output, variable assignment, and basic data types. It explains how to perform arithmetic operations, create and manipulate lists, and handle different data types, including strings, integers, and booleans. Additionally, it highlights Python's dynamic typing and the ability to store heterogeneous data in lists.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 104

Python Detailed Notes

1. Hello Python!
Explanation

"Hello Python!" is a traditional first program in many programming languages. Its purpose is to
demonstrate the simplest possible way to get the language to produce output, often a simple
message like "Hello, World!". In Python, this is achieved using the print() function.

Syntax
Python
print("Your message here")

Code Example
Python
print("Hello, Python!")

2. Your first Python code


Explanation

Building on "Hello Python!", your first Python code typically involves executing this simple
command. Python code is executed line by line. When you run a Python script, the interpreter
reads each line and performs the action specified. The print() function is a built-in function that
displays the given argument(s) to the console.

Syntax
Python
# A simple statement to execute
print("Text or variable to display")

Code Example
Python
# This is your first Python code!
print("Learning Python is fun!")
print("Welcome to the world of programming.")

3. Python as a calculator
Explanation

One of the most fundamental uses of Python is performing mathematical calculations. Python's
interpreter can act like a powerful calculator, handling basic arithmetic operations directly. It
follows the standard order of operations (PEMDAS/BODMAS).

Syntax

Python uses standard arithmetic operators:

● +: Addition
● -: Subtraction
● *: Multiplication
● /: Division (results in float)
● //: Floor Division (results in integer, discards remainder)
● %: Modulo (returns the remainder of a division)
● **: Exponentiation (power)

Code Example
Python
# Addition
result_add = 5 + 3
print(f"5 + 3 = {result_add}") # Output: 5 + 3 = 8

# Subtraction
result_sub = 10 - 4
print(f"10 - 4 = {result_sub}") # Output: 10 - 4 = 6

# Multiplication
result_mult = 6 * 7
print(f"6 * 7 = {result_mult}") # Output: 6 * 7 = 42

# Division
result_div = 15 / 2
print(f"15 / 2 = {result_div}") # Output: 15 / 2 = 7.5

# Floor Division
result_floor_div = 15 // 2
print(f"15 // 2 = {result_floor_div}") # Output: 15 // 2 = 7

# Modulo
result_modulo = 15 % 2
print(f"15 % 2 = {result_modulo}") # Output: 15 % 2 = 1

# Exponentiation
result_exp = 2 ** 3
print(f"2 ** 3 = {result_exp}") # Output: 2 ** 3 = 8

# Order of operations
complex_calc = (2 + 3) * 4 / 2
print(f"(2 + 3) * 4 / 2 = {complex_calc}") # Output: (2 + 3) * 4 / 2 = 10.0

4. Variables and Types


Explanation

In Python, variables are named locations used to store data in computer memory. They act as
labels for values. Every piece of data in Python has a specific "type" that determines what kind
of value it is (e.g., a whole number, a decimal number, text, etc.) and what operations can be
performed on it. Python is dynamically typed, meaning you don't need to declare a variable's
type before assigning a value to it; the type is inferred at runtime.

Common basic types include:

● int (integer): Whole numbers (e.g., 5, -10, 0).


● float (floating-point number): Numbers with decimal points (e.g., 3.14, -0.5, 2.0).
● str (string): Sequences of characters, enclosed in single or double quotes (e.g., "hello",
'Python').
● bool (boolean): Represents truth values, either True or False.

Syntax

Variables are created by assigning a value to a name using the assignment operator =.

Python
variable_name = value
You can check the type of a variable using the type() function:

Python
type(variable_name)

Code Example
Python
# Integer variable
age = 30
print(f"Age: {age}, Type: {type(age)}") # Output: Age: 30, Type: <class 'int'>

# Float variable
price = 19.99
print(f"Price: {price}, Type: {type(price)}") # Output: Price: 19.99, Type: <class 'float'>

# String variable
name = "Alice"
print(f"Name: {name}, Type: {type(name)}") # Output: Name: Alice, Type: <class 'str'>

# Boolean variable
is_active = True
print(f"Is Active: {is_active}, Type: {type(is_active)}") # Output: Is Active: True, Type: <class
'bool'>

# Variable can be reassigned to a different type (dynamic typing)


my_variable = 10
print(f"my_variable: {my_variable}, Type: {type(my_variable)}")
my_variable = "Hello"
print(f"my_variable: {my_variable}, Type: {type(my_variable)}")

5. Variable Assignment
Explanation

Variable assignment is the process of binding a name (the variable) to a specific value in
memory. In Python, the single equals sign (=) is the assignment operator. When a variable is
assigned a new value, the old value it referred to (if any) is no longer accessible via that variable
name. Python supports chained assignment and assigning multiple variables simultaneously.
Syntax
Python
variable_name = value
variable1 = variable2 = value # Chained assignment
var1, var2, var3 = val1, val2, val3 # Multiple assignment

Code Example
Python
# Basic assignment
my_number = 10
my_text = "Python"
print(f"my_number: {my_number}")
print(f"my_text: {my_text}")

# Reassignment
my_number = 20
print(f"my_number after reassignment: {my_number}")

# Chained assignment
x = y = z = 100
print(f"x: {x}, y: {y}, z: {z}")

# Multiple assignment
a, b = 5, 10
print(f"a: {a}, b: {b}")

# Swapping values using multiple assignment


val1 = 100
val2 = 200
print(f"Before swap: val1={val1}, val2={val2}")
val1, val2 = val2, val1
print(f"After swap: val1={val1}, val2={val2}")

6. Calculations with variables


Explanation

Once values are stored in variables, you can use these variables in arithmetic expressions just
as you would use raw numbers. Python substitutes the current value of the variable into the
calculation. This makes your code more readable, flexible, and maintainable, as you can
change a value in one place (the variable assignment) and have it affect all calculations where
that variable is used.

Syntax
Python
result_variable = var1 operator var2

Code Example
Python
# Define variables
num1 = 25
num2 = 7
pi_value = 3.14159

# Perform calculations using variables


sum_nums = num1 + num2
difference_nums = num1 - num2
product_nums = num1 * num2
quotient_nums = num1 / num2
remainder_nums = num1 % num2
power_num = num1 ** 2

print(f"Sum: {sum_nums}") # Output: Sum: 32


print(f"Difference: {difference_nums}") # Output: Difference: 18
print(f"Product: {product_nums}") # Output: Product: 175
print(f"Quotient: {quotient_nums}") # Output: Quotient: 3.571428571428571
print(f"Remainder: {remainder_nums}") # Output: Remainder: 4
print(f"Power: {power_num}") # Output: Power: 625

# Combining variables and literals


radius = 5
area_circle = pi_value * (radius ** 2)
print(f"Area of circle with radius {radius}: {area_circle}") # Output: Area of circle with radius 5:
78.53975

7. Other variable types


Explanation
Beyond the basic int, float, str, and bool, Python has many other built-in data types. These
include more complex structures that can hold collections of items or represent specific kinds of
data. Understanding these types is crucial for working with more sophisticated data.

Common other types include:

● list: An ordered, mutable (changeable) collection of items. Items can be of different


types.
● tuple: An ordered, immutable (unchangeable) collection of items.
● dict (dictionary): An unordered, mutable collection of key-value pairs. Each key must
be unique.
● set: An unordered collection of unique items.
● NoneType: Represents the absence of a value, represented by the keyword None.

Syntax

● List: [item1, item2, ...]


● Tuple: (item1, item2, ...) or item1, item2, ... (without parentheses for multiple items)
● Dictionary: {key1: value1, key2: value2, ...}
● Set: {item1, item2, ...} (or set([item1, item2]) for empty set)
● None: None

Code Example
Python
# List
my_list = [1, "apple", 3.14, True]
print(f"my_list: {my_list}, Type: {type(my_list)}") # Output: my_list: [1, 'apple', 3.14, True], Type:
<class 'list'>

# Tuple
my_tuple = (10, "banana", False)
print(f"my_tuple: {my_tuple}, Type: {type(my_tuple)}") # Output: my_tuple: (10, 'banana', False),
Type: <class 'tuple'>

# Dictionary
my_dict = {"name": "Charlie", "age": 25, "city": "New York"}
print(f"my_dict: {my_dict}, Type: {type(my_dict)}") # Output: my_dict: {'name': 'Charlie', 'age': 25,
'city': 'New York'}, Type: <class 'dict'>

# Set
my_set = {1, 2, 3, 2, 1} # Duplicates are automatically removed
print(f"my_set: {my_set}, Type: {type(my_set)}") # Output: my_set: {1, 2, 3}, Type: <class 'set'>

# NoneType
no_value = None
print(f"no_value: {no_value}, Type: {type(no_value)}") # Output: no_value: None, Type: <class
'NoneType'>

8. Operations with other types


Explanation

Different data types support different operations. While you can perform arithmetic on numbers,
strings support concatenation and repetition, and booleans are used in logical operations.
Understanding how to interact between different types, especially through type conversion
(casting), is fundamental. Python will raise TypeError if you try to perform an operation that is
not supported between specific types.

Syntax

● Concatenation (Strings): str1 + str2


● Repetition (Strings): str * integer
● Type Conversion (Casting): int(value), float(value), str(value), bool(value)

Code Example
Python
# String Concatenation
greeting = "Hello"
name = "World"
full_message = greeting + " " + name + "!"
print(f"Concatenated string: {full_message}") # Output: Concatenated string: Hello World!

# String Repetition
star_line = "*" * 10
print(f"Repeated string: {star_line}") # Output: Repeated string: **********

# Combining numbers and strings (requires type conversion)


age_num = 30
message = "I am " + str(age_num) + " years old."
print(f"Combined string and number: {message}") # Output: Combined string and number: I am
30 years old.

# Integer to Float
int_num = 10
float_from_int = float(int_num)
print(f"Int to Float: {float_from_int}, Type: {type(float_from_int)}") # Output: Int to Float: 10.0,
Type: <class 'float'>

# Float to Integer (truncates decimal part)


float_num = 3.99
int_from_float = int(float_num)
print(f"Float to Int: {int_from_float}, Type: {type(int_from_float)}") # Output: Float to Int: 3, Type:
<class 'int'>

# String to Integer/Float
str_num_int = "123"
int_from_str = int(str_num_int)
print(f"String to Int: {int_from_str}, Type: {type(int_from_str)}") # Output: String to Int: 123, Type:
<class 'int'>

str_num_float = "45.67"
float_from_str = float(str_num_float)
print(f"String to Float: {float_from_str}, Type: {type(float_from_str)}") # Output: String to Float:
45.67, Type: <class 'float'>

# Boolean conversion
bool_from_int_zero = bool(0)
bool_from_int_nonzero = bool(5)
bool_from_empty_str = bool("")
bool_from_nonempty_str = bool("abc")
print(f"Bool from 0: {bool_from_int_zero}") # Output: Bool from 0: False
print(f"Bool from 5: {bool_from_int_nonzero}") # Output: Bool from 5: True
print(f"Bool from '': {bool_from_empty_str}") # Output: Bool from '': False
print(f"Bool from 'abc': {bool_from_nonempty_str}") # Output: Bool from 'abc': True

# Example of TypeError (uncomment to see error)


# print(5 + "hello") # This will raise a TypeError

9. Python Lists
Explanation

Lists are one of the most versatile and widely used data structures in Python. A list is an
ordered collection of items, and it is mutable, meaning you can change its content (add,
remove, or modify elements) after it's created. List items are enclosed in square brackets [] and
separated by commas. Lists can contain items of different data types.
Syntax
Python
my_list = [item1, item2, item3, ...]

Code Example
Python
# Creating an empty list
empty_list = []
print(f"Empty list: {empty_list}, Type: {type(empty_list)}")

# Creating a list of integers


numbers = [1, 2, 3, 4, 5]
print(f"List of numbers: {numbers}")

# Creating a list of strings


fruits = ["apple", "banana", "cherry"]
print(f"List of fruits: {fruits}")

# Accessing elements by index (lists are zero-indexed)


# First element
print(f"First fruit: {fruits[0]}") # Output: First fruit: apple
# Last element (using negative indexing)
print(f"Last fruit: {fruits[-1]}") # Output: Last fruit: cherry

10. Create a list


Explanation

Creating a list is straightforward in Python. You simply enclose the items you want in the list
within square brackets [], separating each item with a comma. The items can be of any data
type.

Syntax
Python
list_name = [element1, element2, element3, ...]

Code Example
Python
# A list of numbers
temperatures = [22, 25, 19, 28, 21]
print(f"Temperatures: {temperatures}")

# A list of strings
names = ["Alice", "Bob", "Charlie", "David"]
print(f"Names: {names}")

# An empty list
shopping_list = []
print(f"Shopping list (empty): {shopping_list}")

11. Create lists with different types


Explanation

One of the powerful features of Python lists is their ability to store items of different data types
within the same list. This heterogeneity allows you to represent complex data structures where
related information of varying kinds needs to be grouped together.

Syntax
Python
heterogeneous_list = [integer_value, float_value, "string_value", True, None, other_list, ...]

Code Example
Python
# A list containing an integer, a float, a string, and a boolean
mixed_data = [10, 3.14, "Hello", True]
print(f"Mixed data list: {mixed_data}")

# A list representing a person's record


person_record = ["John Doe", 30, 1.85, True, "New York"] # Name, Age, Height, IsStudent, City
print(f"Person record: {person_record}")

# A list containing different data types, including None


various_items = ["task_A", 100, None, 7.5, False]
print(f"Various items list: {various_items}")
12. List of lists
Explanation

Lists can contain other lists as their elements. This creates a nested structure, often used to
represent tabular data, matrices, or multi-dimensional arrays. Each inner list can be thought of
as a row or a sub-collection.

Syntax
Python
list_of_lists = [
[inner_list_item1, inner_list_item2],
[inner_list_item3, inner_list_item4],
# ...
]

Code Example
Python
# A simple 2x2 matrix
matrix = [
[1, 2],
[3, 4]
]
print(f"Matrix: {matrix}")

# Student records: [Name, Age, Grade]


student_data = [
["Alice", 20, "A"],
["Bob", 22, "B"],
["Charlie", 21, "A+"]
]
print(f"Student data: {student_data}")

# Accessing elements in a list of lists


# To access 'Bob' (row 1, element 0)
print(f"Second student's name: {student_data[1][0]}") # Output: Second student's name: Bob

# To access 'A+' (row 2, element 2)


print(f"Third student's grade: {student_data[2][2]}") # Output: Third student's grade: A+
13. Subsetting Lists
Explanation

Subsetting a list means selecting one or more elements from it. In Python, list elements are
accessed using zero-based indexing, where the first element is at index 0, the second at index
1, and so on. Negative indexing can be used to access elements from the end of the list, with -1
referring to the last element, -2 to the second to last, etc.

Syntax

● Access single element: my_list[index]


● Access from the end: my_list[-index_from_end]

Code Example
Python
my_list = ["apple", "banana", "cherry", "date", "elderberry"]

# Accessing the first element


first_element = my_list[0]
print(f"First element: {first_element}") # Output: First element: apple

# Accessing the third element


third_element = my_list[2]
print(f"Third element: {third_element}") # Output: Third element: cherry

# Accessing the last element using negative indexing


last_element = my_list[-1]
print(f"Last element: {last_element}") # Output: Last element: elderberry

# Accessing the second to last element


second_last_element = my_list[-2]
print(f"Second to last element: {second_last_element}") # Output: Second to last element: date

# Trying to access an index out of bounds will raise an IndexError


# print(my_list[10]) # This would cause an IndexError

14. Subset and conquer


Explanation
"Subset and conquer" is a conceptual phrase here referring to the strategy of breaking down a
problem into smaller, manageable parts by extracting specific subsets of data. When dealing
with lists, it means precisely selecting the elements you need for a particular task, rather than
working with the entire list. This concept applies directly to list subsetting and slicing.

Syntax

(This is a conceptual topic, syntax is covered in "Subsetting Lists" and "Slicing and dicing")

Code Example
Python
# Example: We have a list of daily temperatures
daily_temperatures = [18, 20, 22, 23, 21, 19, 20, 24, 26, 25] # 10 days of data

# "Conquer" a specific problem: Find the temperature on the 5th day (index 4)
temp_day_5 = daily_temperatures[4]
print(f"Temperature on day 5: {temp_day_5}") # Output: Temperature on day 5: 21

# "Conquer" another problem: Check the first temperature in the week


first_temp_week = daily_temperatures[0]
print(f"First temperature of the week: {first_temp_week}") # Output: First temperature of the
week: 18

# "Conquer" a problem related to the last few days (slicing, covered next)
# Last three days of data
last_three_days = daily_temperatures[-3:]
print(f"Temperatures of the last three days: {last_three_days}") # Output: Temperatures of the
last three days: [24, 26, 25]

# This illustrates using subsetting to focus on relevant data for specific tasks.

15. Slicing and dicing


Explanation

Slicing allows you to extract a sub-list (a "slice") from an existing list. It creates a new list
containing a contiguous sequence of elements from the original list. The syntax uses a colon (:).
The slice includes the starting index up to (but not including) the ending index. If the start or end
index is omitted, it defaults to the beginning or end of the list, respectively.

Syntax
Python
my_list[start:end] # Elements from start up to (but not including) end
my_list[start:] # Elements from start to the end of the list
my_list[:end] # Elements from the beginning up to (but not including) end
my_list[:] # A copy of the entire list
my_list[start:end:step] # Elements from start to end, with a specified step size

Code Example
Python
letters = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']

# Get elements from index 2 up to (but not including) index 5


slice1 = letters[2:5]
print(f"letters[2:5]: {slice1}") # Output: letters[2:5]: ['c', 'd', 'e']

# Get elements from index 4 to the end


slice2 = letters[4:]
print(f"letters[4:]: {slice2}") # Output: letters[4:]: ['e', 'f', 'g', 'h', 'i']

# Get elements from the beginning up to (but not including) index 3


slice3 = letters[:3]
print(f"letters[:3]: {slice3}") # Output: letters[:3]: ['a', 'b', 'c']

# Get a copy of the entire list


copy_list = letters[:]
print(f"letters[:]: {copy_list}") # Output: letters[:]: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']

# Slicing with a step


# Get every second element from the beginning
slice_step1 = letters[::2]
print(f"letters[::2]: {slice_step1}") # Output: letters[::2]: ['a', 'c', 'e', 'g', 'i']

# Get every second element from index 1 to 7


slice_step2 = letters[1:8:2]
print(f"letters[1:8:2]: {slice_step2}") # Output: letters[1:8:2]: ['b', 'd', 'f', 'h']

# Reverse a list using slicing


reversed_list = letters[::-1]
print(f"letters[::-1]: {reversed_list}") # Output: letters[::-1]: ['i', 'h', 'g', 'f', 'e', 'd', 'c', 'b', 'a']
16. Subsetting lists of lists
Explanation

When you have a list of lists (a nested list), you can subset it by applying indexing multiple
times. The first index selects the inner list (or "row"), and the second index then selects an
element within that chosen inner list. This allows you to pinpoint specific data points in tabular or
matrix-like structures.

Syntax
Python
list_of_lists[row_index][column_index]

You can also slice rows:

Python
list_of_lists[row_slice]

And then apply column indexing/slicing to the result, but remember that a slice of a list of lists
will return a list of lists, not a single list to which you can apply a single column index directly.

Code Example
Python
# Student data: [Name, Age, Grade]
student_data = [
["Alice", 20, "A"],
["Bob", 22, "B"],
["Charlie", 21, "A+"],
["Diana", 23, "C"]
]

# Access Bob's age (second row, second element)


bobs_age = student_data[1][1]
print(f"Bob's age: {bobs_age}") # Output: Bob's age: 22

# Access Charlie's grade (third row, third element)


charlies_grade = student_data[2][2]
print(f"Charlie's grade: {charlies_grade}") # Output: Charlie's grade: A+

# Get the entire record for Diana (last row)


diana_record = student_data[-1]
print(f"Diana's record: {diana_record}") # Output: Diana's record: ['Diana', 23, 'C']

# Access a specific element from Diana's record


diana_name = student_data[-1][0]
print(f"Diana's name: {diana_name}") # Output: Diana's name: Diana

# Slicing rows
# Get the records for Bob and Charlie
bob_charlie_records = student_data[1:3]
print(f"Records for Bob and Charlie: {bob_charlie_records}")
# Output: Records for Bob and Charlie: [['Bob', 22, 'B'], ['Charlie', 21, 'A+']]

# Get only the names of all students (requires a loop, or list comprehension for more advanced)
# For now, illustrating access:
# For Alice's name from a slice:
print(f"Alice's name from first record: {student_data[0][0]}")

17. Manipulating Lists


Explanation

Lists are mutable, meaning their contents can be changed after creation. This allows for
dynamic manipulation, including adding, removing, or changing elements. This flexibility is a
core reason why lists are so widely used in Python.

Syntax

● Adding elements:
○ list.append(item): Adds an item to the end of the list.
○ list.insert(index, item): Inserts an item at a specific index.
○ list.extend(another_list): Appends all items from another iterable (like another
list) to the end of the current list.
● Removing elements:
○ list.remove(value): Removes the first occurrence of a specified value.
○ list.pop(index): Removes and returns the item at a given index (or the last item
if no index is specified).
○ del list[index] or del list[start:end]: Deletes item(s) at a specified index or
slice.
● Modifying elements:
○ list[index] = new_value: Changes the element at a specific index.
○ list[start:end] = new_sublist: Replaces a slice with a new sublist.

Code Example
Python
my_list = ['a', 'b', 'c']
print(f"Original list: {my_list}") # Output: Original list: ['a', 'b', 'c']

# 1. Add elements
my_list.append('d')
print(f"After append('d'): {my_list}") # Output: After append('d'): ['a', 'b', 'c', 'd']

my_list.insert(1, 'x')
print(f"After insert(1, 'x'): {my_list}") # Output: After insert(1, 'x'): ['a', 'x', 'b', 'c', 'd']

# 2. Modify elements
my_list[2] = 'B'
print(f"After my_list[2] = 'B': {my_list}") # Output: After my_list[2] = 'B': ['a', 'x', 'B', 'c', 'd']

my_list[0:2] = ['Z', 'Y'] # Replace a slice


print(f"After my_list[0:2] = ['Z', 'Y']: {my_list}") # Output: After my_list[0:2] = ['Z', 'Y', 'B', 'c', 'd']

# 3. Remove elements
removed_item_pop = my_list.pop(3) # Removes 'c'
print(f"After pop(3), removed: '{removed_item_pop}': {my_list}") # Output: After pop(3), removed:
'c': ['Z', 'Y', 'B', 'd']

my_list.remove('Y')
print(f"After remove('Y'): {my_list}") # Output: After remove('Y'): ['Z', 'B', 'd']

del my_list[0]
print(f"After del my_list[0]: {my_list}") # Output: After del my_list[0]: ['B', 'd']

# Using extend
list1 = [1, 2]
list2 = [3, 4]
list1.extend(list2)
print(f"After list1.extend(list2): {list1}") # Output: After list1.extend(list2): [1, 2, 3, 4]

# Note: `+` operator for lists creates a new list, doesn't modify in place
list_concat = [1, 2] + [3, 4]
print(f"List concatenation: {list_concat}") # Output: List concatenation: [1, 2, 3, 4]

18. Replace list elements


Explanation

Replacing elements in a list involves assigning a new value to an existing index or a slice of the
list. This directly modifies the list in place, making it a very efficient operation for updating data.

Syntax

● Single element: my_list[index] = new_value


● Slice of elements: my_list[start:end] = new_sublist
○ The new_sublist can have a different length than the slice it replaces, causing
the list to grow or shrink.

Code Example
Python
fruits = ["apple", "banana", "cherry", "date"]
print(f"Original fruits: {fruits}") # Output: Original fruits: ['apple', 'banana', 'cherry', 'date']

# Replace a single element


fruits[1] = "grape"
print(f"After replacing 'banana' with 'grape': {fruits}") # Output: After replacing 'banana' with
'grape': ['apple', 'grape', 'cherry', 'date']

# Replace a slice with a new list of the same length


fruits[2:4] = ["kiwi", "lemon"]
print(f"After replacing ['cherry', 'date'] with ['kiwi', 'lemon']: {fruits}") # Output: After replacing
['cherry', 'date'] with ['kiwi', 'lemon']: ['apple', 'grape', 'kiwi', 'lemon']

# Replace a slice with a new list of different length (list grows)


fruits[1:2] = ["orange", "peach", "mango"] # Replaces 'grape' with three new fruits
print(f"After replacing ['grape'] with longer list: {fruits}") # Output: After replacing ['grape'] with
longer list: ['apple', 'orange', 'peach', 'mango', 'kiwi', 'lemon']

# Replace a slice with an empty list (deletes elements)


fruits[3:5] = [] # Deletes 'mango' and 'kiwi'
print(f"After replacing ['mango', 'kiwi'] with empty list: {fruits}") # Output: After replacing ['mango',
'kiwi'] with empty list: ['apple', 'orange', 'peach', 'lemon']

19. Extend a list


Explanation
The extend() method is used to add all the elements of an iterable (like another list, tuple,
string, or set) to the end of the current list. It modifies the list in place. This is different from
append() which adds its entire argument as a single element to the list (even if the argument is
another list). It's also different from list concatenation (+), which creates a new list.

Syntax
Python
my_list.extend(iterable_to_add)

Code Example
Python
primary_colors = ["red", "yellow", "blue"]
secondary_colors = ["orange", "green", "purple"]
print(f"Primary colors: {primary_colors}")
print(f"Secondary colors: {secondary_colors}")

# Extend primary_colors with secondary_colors


primary_colors.extend(secondary_colors)
print(f"After extending primary_colors: {primary_colors}")
# Output: After extending primary_colors: ['red', 'yellow', 'blue', 'orange', 'green', 'purple']

# Example with append vs. extend


list_a = [1, 2]
list_b = [3, 4]

list_a.append(list_b)
print(f"Using append: {list_a}") # Output: Using append: [1, 2, [3, 4]] (list_b becomes a single
element)

list_c = [1, 2]
list_c.extend(list_b)
print(f"Using extend: {list_c}") # Output: Using extend: [1, 2, 3, 4] (elements of list_b are added
individually)

# Extending with a string (iterates over characters)


items = ['a', 'b']
items.extend("xyz")
print(f"Extending with a string: {items}") # Output: Extending with a string: ['a', 'b', 'x', 'y', 'z']

20. Delete list elements


Explanation

Deleting elements from a list can be done in several ways, depending on whether you know the
element's value, its index, or a range of indices. These operations modify the list in place.

● remove(value): Deletes the first occurrence of a specified value. Raises a


ValueError if the value is not found.
● pop(index): Deletes the element at a specific index and returns it. If no index is
provided, it removes and returns the last element. Raises an IndexError if the index is
out of range.
● del keyword: Deletes element(s) at a specified index or slice. It does not return the
deleted element(s). Raises an IndexError for invalid index or TypeError for invalid
type.

Syntax
Python
my_list.remove(value_to_remove)
removed_item = my_list.pop(index_to_remove) # index is optional
del my_list[index_to_delete]
del my_list[start:end] # Delete a slice

Code Example
Python
programming_languages = ["Python", "Java", "C++", "JavaScript", "Java"]
print(f"Original list: {programming_languages}")

# Using remove()
programming_languages.remove("Java") # Removes the first 'Java'
print(f"After remove('Java'): {programming_languages}") # Output: After remove('Java'):
['Python', 'C++', 'JavaScript', 'Java']

# Using pop()
removed_lang = programming_languages.pop(1) # Removes 'C++'
print(f"After pop(1), removed: '{removed_lang}': {programming_languages}") # Output: After
pop(1), removed: 'C++': ['Python', 'JavaScript', 'Java']

removed_last = programming_languages.pop() # Removes the last element ('Java')


print(f"After pop() (last element), removed: '{removed_last}': {programming_languages}") #
Output: After pop() (last element), removed: 'Java': ['Python', 'JavaScript']

# Using del
del programming_languages[0] # Deletes 'Python'
print(f"After del programming_languages[0]: {programming_languages}") # Output: After del
programming_languages[0]: ['JavaScript']

another_list = [10, 20, 30, 40, 50, 60]


print(f"\nAnother list: {another_list}")
del another_list[1:4] # Deletes 20, 30, 40
print(f"After del another_list[1:4]: {another_list}") # Output: After del another_list[1:4]: [10, 50, 60]

# Error handling (uncomment to see error)


# try:
# programming_languages.remove("Ruby") # Value not in list
# except ValueError as e:
# print(f"Error: {e}")

21. Inner workings of lists


Explanation

Understanding the "inner workings" of lists involves grasping how Python stores and manages
them, especially regarding mutability and object references.

● Ordered Sequence: Lists maintain the order of elements as they are inserted.
● Dynamic Size: They can grow or shrink as elements are added or removed.
● Heterogeneous: Can store items of different data types.
● Mutable: Contents can be changed in place.
● Memory References: When you assign one list to another variable (e.g., list_b =
list_a), both variables refer to the same list object in memory. Modifying list_a will also
affect list_b. To create a copy that is independent, you need to use slicing (list_b =
list_a[:]) or the copy() method (list_b = list_a.copy()). This concept is crucial for
avoiding unexpected side effects.

Syntax

● Assignment (reference): new_list = original_list


● Shallow Copy (slice): new_list = original_list[:]
● Shallow Copy (method): new_list = original_list.copy()
● Deep Copy (for nested lists): import copy; new_list =
copy.deepcopy(original_list) (needed when inner lists also need to be independent
copies)

Code Example
Python
# 1. Assignment creates a reference
original_list = [1, 2, 3]
reference_list = original_list # reference_list points to the same object as original_list
print(f"Original: {original_list}, Reference: {reference_list}")

reference_list.append(4) # Modifying through reference_list


print(f"Original after modification: {original_list}") # Output: Original after modification: [1, 2, 3, 4]
print(f"Reference after modification: {reference_list}") # Output: Reference after modification: [1,
2, 3, 4]
print(f"Are they the same object? {original_list is reference_list}") # Output: Are they the same
object? True

# 2. Creating a shallow copy (using slicing)


list_a = [10, 20, 30]
list_b = list_a[:] # Creates a new list object with same elements
print(f"\nList A: {list_a}, List B (slice copy): {list_b}")

list_b.append(40)
print(f"List A after B's modification: {list_a}") # Output: List A after B's modification: [10, 20, 30]
print(f"List B after B's modification: {list_b}") # Output: List B after B's modification: [10, 20, 30,
40]
print(f"Are they the same object? {list_a is list_b}") # Output: Are they the same object? False

# 3. Shallow copy issue with nested lists


nested_original = [[1, 2], [3, 4]]
nested_copy_shallow = nested_original[:] # This is a shallow copy
print(f"\nNested original: {nested_original}")
print(f"Nested shallow copy: {nested_copy_shallow}")

nested_copy_shallow[0].append(5) # Modifying an INNER list in the shallow copy


print(f"Nested original after shallow copy inner modification: {nested_original}") # Output: Nested
original after shallow copy inner modification: [[1, 2, 5], [3, 4]]
print(f"Nested shallow copy after inner modification: {nested_copy_shallow}") # Output: Nested
shallow copy after inner modification: [[1, 2, 5], [3, 4]]
# Both changed because the inner lists are still references to the same objects!

# 4. Deep copy (for truly independent nested lists)


import copy
nested_original_deep = [[1, 2], [3, 4]]
nested_copy_deep = copy.deepcopy(nested_original_deep) # This is a deep copy
print(f"\nNested original for deep copy: {nested_original_deep}")
print(f"Nested deep copy: {nested_copy_deep}")
nested_copy_deep[0].append(5)
print(f"Nested original after deep copy inner modification: {nested_original_deep}") # Output:
Nested original after deep copy inner modification: [[1, 2], [3, 4]]
print(f"Nested deep copy after inner modification: {nested_copy_deep}") # Output: Nested deep
copy after inner modification: [[1, 2, 5], [3, 4]]
# Only the deep copy changed!

22. Functions
Explanation

A function is a block of organized, reusable code that performs a single, related action.
Functions provide better modularity for your application and a high degree of code reusing.
Python has many built-in functions, and you can also define your own. When you "call" a
function, you execute the code within it.

Syntax
Python
# Calling a built-in function
function_name(argument1, argument2, ...)

# Defining your own function


def function_name(parameter1, parameter2, ...):
"""
Docstring: A brief description of what the function does.
"""
# Code block for the function
# ...
return result # Optional: returns a value

Code Example
Python
# Using built-in functions
my_list = [10, 20, 5, 30, 15]
length = len(my_list) # len() gets the number of items
print(f"Length of my_list: {length}") # Output: Length of my_list: 5

max_value = max(my_list) # max() gets the largest item


print(f"Max value in my_list: {max_value}") # Output: Max value in my_list: 30
# Defining a simple custom function
def greet(name):
"""
This function prints a greeting message.
"""
message = f"Hello, {name}!"
print(message)

# Calling the custom function


greet("Alice") # Output: Hello, Alice!
greet("Bob") # Output: Hello, Bob!

# Function that returns a value


def add_numbers(x, y):
"""
This function adds two numbers and returns their sum.
"""
sum_result = x + y
return sum_result

total = add_numbers(5, 7)
print(f"Sum of 5 and 7: {total}") # Output: Sum of 5 and 7: 12

another_total = add_numbers(10.5, 2.3)


print(f"Sum of 10.5 and 2.3: {another_total}") # Output: Sum of 10.5 and 2.3: 12.8

23. Familiar functions


Explanation

Python provides a rich set of built-in functions that are readily available for common tasks
without needing to import any modules. You've already encountered some of them. Becoming
familiar with these functions can significantly speed up your coding and make your code more
concise.

Some common familiar functions include:

● print(): Displays output to the console.


● len(): Returns the number of items in an object (e.g., list, string, tuple).
● type(): Returns the type of an object.
● int(), float(), str(), bool(): Type conversion functions.
● min(), max(): Returns the smallest/largest item in an iterable.
● sum(): Returns the sum of all items in an iterable.
● range(): Generates a sequence of numbers.

Syntax
Python
function_name(argument(s))

Code Example
Python
data_points = [10, 5, 8, 12, 3, 7]
text = "Python Programming"

# len()
list_length = len(data_points)
string_length = len(text)
print(f"Length of data_points list: {list_length}") # Output: Length of data_points list: 6
print(f"Length of text string: {string_length}") # Output: Length of text string: 18

# type()
print(f"Type of data_points: {type(data_points)}") # Output: Type of data_points: <class 'list'>
print(f"Type of text: {type(text)}") # Output: Type of text: <class 'str'>

# min(), max(), sum()


min_val = min(data_points)
max_val = max(data_points)
total_sum = sum(data_points)
print(f"Minimum value: {min_val}") # Output: Minimum value: 3
print(f"Maximum value: {max_val}") # Output: Maximum value: 12
print(f"Sum of values: {total_sum}") # Output: Sum of values: 45

# Type conversion
num_str = "123"
converted_int = int(num_str)
print(f"'{num_str}' as int: {converted_int}, Type: {type(converted_int)}") # Output: '123' as int:
123, Type: <class 'int'>

# range() (often used in loops)


print("Numbers from 0 to 4 using range:")
for i in range(5): # Generates numbers 0, 1, 2, 3, 4
print(i, end=" ") # Output: 0 1 2 3 4
print()
24. Help!
Explanation

When working with Python, you'll frequently need to understand how functions, methods, or
objects work. Python's built-in help() function is invaluable for this. It provides access to the
documentation (docstrings) of Python objects, detailing their purpose, arguments, and return
values. This is a primary tool for self-help and exploration in Python.

Syntax
Python
help(object_name)

You can pass the function name, method name, or even a data type to help().

Code Example
Python
# Get help on the print() function
print("Getting help for print() function:")
help(print)
# Output:
# Help on built-in function print in module builtins:
#
# print(...)
# print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)
#
# Prints the values to a stream, or to sys.stdout by default.
# Optional keyword arguments:
# file: a file-like object (stream); defaults to the current sys.stdout.
# sep: string inserted between values, default a space.
# end: string appended after the last value, default a newline.
# flush: whether to forcibly flush the stream.
# (The output will continue with more details)

print("\nGetting help for list.append method:")


help(list.append)
# Output:
# Help on method_descriptor:
#
# append(self, object, /)
# Append object to the end of the list.
# (The output will continue with more details)

# You can also get help on an object itself


my_list = [1, 2, 3]
print("\nGetting help for a list object:")
help(my_list)
# This will show help for the list type, including all its methods.
# (Output will be extensive, listing all list methods like append, insert, remove, etc.)

25. Multiple arguments


Explanation

Many Python functions are designed to accept multiple arguments (also known as parameters)
to perform their task. These arguments provide the necessary input for the function to operate.
Arguments can be passed positionally (order matters) or as keyword arguments (name
matters).

Syntax
Python
function_name(positional_arg1, positional_arg2, keyword_arg=value, ...)

Code Example
Python
# The print() function is a good example of multiple arguments
print("Hello", "Python", "World", sep="-", end="!")
# Output: Hello-Python-World! (sep separates arguments, end is appended at the end)

# A custom function with multiple positional arguments


def calculate_area(length, width):
"""Calculates the area of a rectangle."""
area = length * width
return area

room_area = calculate_area(5, 8)
print(f"\nArea of the room: {room_area} sq units") # Output: Area of the room: 40 sq units

# A function with a mix of positional and keyword arguments


def greet_person(name, greeting="Hello", punctuation="!"):
"""Greets a person with a customizable greeting and punctuation."""
message = f"{greeting}, {name}{punctuation}"
print(message)

greet_person("Alice") # Positional for name, defaults for others


# Output: Hello, Alice!

greet_person("Bob", greeting="Hi") # Positional for name, keyword for greeting


# Output: Hi, Bob!

greet_person("Charlie", punctuation="???", greeting="Greetings") # All keyword arguments


# Output: Greetings, Charlie???

# Example of function with varying number of arguments (*args and **kwargs) - advanced
def print_args(*args, **kwargs):
print("Positional arguments:", args)
print("Keyword arguments:", kwargs)

print_args(1, 2, "hello", name="Alice", age=30)


# Output:
# Positional arguments: (1, 2, 'hello')
# Keyword arguments: {'name': 'Alice', 'age': 30}

26. Methods
Explanation

In Python, a method is a function that "belongs" to an object. It's a function that is associated
with a specific data type (like a string, list, dictionary, etc.) and can be called on instances of that
type. Methods perform operations specifically relevant to the object they are called on. They are
accessed using dot notation (object.method_name()).

Syntax
Python
object_name.method_name(arguments_if_any)

Code Example
Python
# Example with a string object
my_string = "python programming"
print(f"Original string: '{my_string}'")

# .upper() method: Converts string to uppercase


upper_string = my_string.upper()
print(f"Uppercase: '{upper_string}'") # Output: Uppercase: 'PYTHON PROGRAMMING'

# .capitalize() method: Capitalizes the first letter


capitalized_string = my_string.capitalize()
print(f"Capitalized: '{capitalized_string}'") # Output: Capitalized: 'Python programming'

# .count() method: Counts occurrences of a substring


count_p = my_string.count('p')
print(f"Count of 'p': {count_p}") # Output: Count of 'p': 2

# Example with a list object


my_list = [3, 1, 4, 1, 5, 9, 2]
print(f"\nOriginal list: {my_list}")

# .sort() method: Sorts the list in place


my_list.sort()
print(f"Sorted list: {my_list}") # Output: Sorted list: [1, 1, 2, 3, 4, 5, 9]

# .append() method: Adds an element to the end (already covered)


my_list.append(8)
print(f"List after append(8): {my_list}") # Output: List after append(8): [1, 1, 2, 3, 4, 5, 9, 8]

# .index() method: Returns the index of the first occurrence of a value


index_of_9 = my_list.index(9)
print(f"Index of 9: {index_of_9}") # Output: Index of 9: 6

27. String Methods


Explanation

Strings are immutable sequences of characters. Python provides a rich set of built-in methods
that allow you to manipulate, analyze, and format strings. Since strings are immutable, these
methods typically return a new string with the desired changes, rather than modifying the
original string in place.

Syntax
Python
my_string.method_name(arguments_if_any)

Code Example
Python
my_string = " Hello Python Learners! "
print(f"Original string: '{my_string}'")

# .upper() and .lower(): Case conversion


print(f"Uppercase: '{my_string.upper()}'") # Output: Uppercase: ' HELLO PYTHON
LEARNERS! '
print(f"Lowercase: '{my_string.lower()}'") # Output: Lowercase: ' hello python learners! '

# .strip(): Removes leading/trailing whitespace


stripped_string = my_string.strip()
print(f"Stripped: '{stripped_string}'") # Output: Stripped: 'Hello Python Learners!'

# .replace(old, new): Replaces occurrences of a substring


replaced_string = stripped_string.replace("Python", "Java")
print(f"Replaced 'Python' with 'Java': '{replaced_string}'") # Output: Replaced 'Python' with
'Java': 'Hello Java Learners!'

# .startswith(prefix) and .endswith(suffix): Check start/end of string


print(f"Starts with 'Hello': {stripped_string.startswith('Hello')}") # Output: Starts with 'Hello': True
print(f"Ends with 'Learners!': {stripped_string.endswith('Learners!')}") # Output: Ends with
'Learners!': True

# .find(substring): Returns the lowest index of the substring if found, -1 otherwise


index_python = stripped_string.find("Python")
print(f"Index of 'Python': {index_python}") # Output: Index of 'Python': 6

# .split(delimiter): Splits the string into a list of substrings


words = stripped_string.split(" ")
print(f"Split by space: {words}") # Output: Split by space: ['Hello', 'Python', 'Learners!']

csv_data = "apple,banana,cherry"
fruits_list = csv_data.split(',')
print(f"Split CSV data: {fruits_list}") # Output: Split CSV data: ['apple', 'banana', 'cherry']

# .join(iterable): Joins elements of an iterable (e.g., list of strings) with the string itself as a
separator
joined_string = "-".join(["A", "B", "C"])
print(f"Joined string: {joined_string}") # Output: Joined string: A-B-C
28. List Methods
Explanation

Lists, being mutable objects, have a set of methods that allow you to modify their content
directly (in-place) or retrieve information about them. These methods are typically called on a list
object using dot notation.

Syntax
Python
my_list.method_name(arguments_if_any)

Code Example
Python
numbers = [4, 1, 3, 2, 1, 5]
print(f"Original list: {numbers}")

# .append(item): Adds an item to the end


numbers.append(6)
print(f"After append(6): {numbers}") # Output: After append(6): [4, 1, 3, 2, 1, 5, 6]

# .insert(index, item): Inserts an item at a specific position


numbers.insert(2, 99)
print(f"After insert(2, 99): {numbers}") # Output: After insert(2, 99): [4, 1, 99, 3, 2, 1, 5, 6]

# .remove(value): Removes the first occurrence of a value


numbers.remove(1)
print(f"After remove(1): {numbers}") # Output: After remove(1): [4, 99, 3, 2, 1, 5, 6]

# .pop(index): Removes and returns item at index (or last if no index)


popped_item = numbers.pop() # Removes 6
print(f"After pop() (last), removed: {popped_item}: {numbers}") # Output: After pop() (last),
removed: 6: [4, 99, 3, 2, 1, 5]

# .sort(): Sorts the list in ascending order (in-place)


numbers.sort()
print(f"After sort(): {numbers}") # Output: After sort(): [1, 2, 3, 4, 5, 99]

# .reverse(): Reverses the order of elements (in-place)


numbers.reverse()
print(f"After reverse(): {numbers}") # Output: After reverse(): [99, 5, 4, 3, 2, 1]

# .count(value): Returns the number of times a value appears


count_of_1 = numbers.count(1)
print(f"Count of 1: {count_of_1}") # Output: Count of 1: 1

# .index(value): Returns the index of the first occurrence of a value


index_of_5 = numbers.index(5)
print(f"Index of 5: {index_of_5}") # Output: Index of 5: 1

# .clear(): Removes all elements from the list


numbers.clear()
print(f"After clear(): {numbers}") # Output: After clear(): []

29. List Methods (2)


Explanation

This section continues to explore more list methods, particularly those that are often used for
common list manipulations and queries. While some of these were touched upon in the previous
section or "Manipulating Lists", emphasizing them separately helps reinforce their specific use
cases.

Syntax
Python
my_list.method_name(arguments_if_any)

Code Example
Python
# Re-initializing for fresh examples
elements = ['x', 'b', 'c', 'a', 'b', 'd']
print(f"Initial list: {elements}")

# .index(value, start, end): Find index within a slice


first_b_index = elements.index('b')
print(f"First index of 'b': {first_b_index}") # Output: First index of 'b': 1

# Find 'b' starting from index 2


second_b_index = elements.index('b', 2)
print(f"Second index of 'b' (starting from index 2): {second_b_index}") # Output: Second index of
'b' (starting from index 2): 4

# .copy(): Creates a shallow copy of the list


original_list = [1, [2, 3], 4]
copied_list = original_list.copy() # Shallow copy
print(f"Original list: {original_list}")
print(f"Copied list (using .copy()): {copied_list}")
print(f"Are they same object? {original_list is copied_list}") # False

# Modifying the copy doesn't affect original for top-level elements


copied_list.append(5)
print(f"Original after copied_list append: {original_list}") # [1, [2, 3], 4]
print(f"Copied after append: {copied_list}") # [1, [2, 3], 4, 5]

# But for nested lists, changes still affect both (shallow copy behavior)
copied_list[1].append(6)
print(f"Original after copied_list nested append: {original_list}") # [1, [2, 3, 6], 4]
print(f"Copied after nested append: {copied_list}") # [1, [2, 3, 6], 4, 5]

# .count(value): Returns the number of occurrences of a value


my_numbers = [1, 2, 2, 3, 1, 4, 2]
count_of_2 = my_numbers.count(2)
print(f"\nList for count: {my_numbers}")
print(f"Count of '2': {count_of_2}") # Output: Count of '2': 3

count_of_5 = my_numbers.count(5)
print(f"Count of '5': {count_of_5}") # Output: Count of '5': 0

30. Packages
Explanation

In Python, a package is a way of organizing related modules (Python files) into a directory
hierarchy. It's essentially a folder containing Python scripts and possibly sub-folders (sub-
packages). Packages are used to modularize code, making it easier to manage, reuse, and
distribute larger applications. The Python Standard Library itself is organized into packages, and
many external functionalities are provided as third-party packages.

Syntax

To use code from a package, you typically need to import it.


Code Example

(No direct code to "create" a package here, but an example of importing a standard one)

Python
# To use mathematical functions beyond basic arithmetic,
# you can import the 'math' package from the Python Standard Library.

# Using the math package


import math

# Accessing functions from the math package using dot notation


radius = 5
area = math.pi * (radius ** 2) # math.pi is a constant in the math package
print(f"Area of circle with radius {radius}: {area}") # Output: Area of circle with radius 5:
78.53981633974483

square_root_of_16 = math.sqrt(16)
print(f"Square root of 16: {square_root_of_16}") # Output: Square root of 16: 4.0

# The `math` package is a fundamental example.


# Later, you will see `NumPy` and `pandas` which are external packages.

31. Import package


Explanation

To use functions, classes, or variables defined in a module or package, you must first import
them into your current Python script. The import statement makes the contents of the
module/package available.

Syntax
Python
import package_name

After importing, you access elements of the package using package_name.element_name.

Code Example
Python
# Import the random package to generate random numbers
import random

# Use a function from the random package


random_number = random.randint(1, 10) # Generates a random integer between 1 and 10
(inclusive)
print(f"A random number between 1 and 10: {random_number}")

# Simulate a coin flip


coin_flip = random.choice(["Heads", "Tails"])
print(f"Coin flip result: {coin_flip}")

# Import the datetime package to work with dates and times


import datetime

# Get the current date and time


current_time = datetime.datetime.now()
print(f"Current date and time: {current_time}")

# Create a specific date


my_birthday = datetime.date(1990, 5, 15)
print(f"My birthday: {my_birthday}")

32. Selective import


Explanation

Instead of importing an entire package, you can selectively import specific functions, classes, or
variables from a module within a package. This is done using the from ... import ... syntax.
This approach avoids cluttering your namespace with the entire package's contents and allows
you to use the imported items directly without the package_name. prefix.

Syntax
Python
from package_name import specific_item1, specific_item2

You can then use specific_item1 and specific_item2 directly.

Code Example
Python
# Instead of `import math`, import only `pi` and `sqrt`
from math import pi, sqrt

radius = 7
area = pi * (radius ** 2) # Use pi directly
print(f"Area of circle with radius {radius} (using direct pi): {area}")

root_25 = sqrt(25) # Use sqrt directly


print(f"Square root of 25 (using direct sqrt): {root_25}")

# Import specific components from the datetime package


from datetime import date, time

today = date.today()
print(f"Today's date: {today}")

# Create a time object


noon = time(12, 0, 0) # 12:00:00
print(f"Noon time: {noon}")

# If you tried to use datetime.datetime.now() without importing datetime or datetime.datetime, it


would fail
# print(datetime.datetime.now()) # This would cause an error unless datetime was imported

33. Different ways of importing


Explanation

Python offers a few variations for importing modules and packages, each with its own use case
and implications for your code's readability and potential for naming conflicts.

1. Standard Import: import package_name


○ Requires package_name.item to access components.
○ Good for clarity, avoids naming conflicts.
2. Selective Import: from package_name import item1, item2
○ Allows direct use of item1, item2.
○ Can lead to naming conflicts if item1 or item2 names already exist in your
script.
3. Import with Alias: import package_name as alias_name
○ Assigns a shorter, conventional, or more convenient alias to the package.
○ Still requires alias_name.item to access components.
○ Common for widely used packages like numpy (aliased as np) and pandas
(aliased as pd).
4. Wildcard Import (Generally Discouraged): from package_name import *
○ Imports all public names from the module directly into the current namespace.
○ Can lead to severe naming conflicts and make it hard to tell where functions are
coming from.
○ Avoid in production code.

Syntax
Python
import package_name
from package_name import item
import package_name as alias
from package_name import * # Avoid this!

Code Example
Python
# 1. Standard Import
import sys
print(f"Python version (standard import): {sys.version.split(' ')[0]}")

# 2. Selective Import
from os import getcwd, listdir # Import specific functions from the 'os' module
print(f"Current working directory (selective import): {getcwd()}")
# print(f"Contents of current directory: {listdir()}") # Potentially long output

# 3. Import with Alias (very common for data science libraries)


import random as rnd
die_roll = rnd.randint(1, 6)
print(f"Die roll (using alias): {die_roll}")

# Example of a common alias for NumPy (will cover NumPy next)


# import numpy as np

# 4. Wildcard Import (Demonstration only, avoid in real code)


print("\n--- Wildcard Import Example (Avoid in practice) ---")
from math import * # Imports all math functions directly
print(f"Value of pi (wildcard): {pi}")
print(f"Ceiling of 3.14 (wildcard): {ceil(3.14)}")
# The problem: If you also had a variable named 'pi' in your code, it would be overwritten,
# and it's not immediately obvious that 'ceil' comes from the 'math' module.
print("--- End Wildcard Import Example ---")
34. NumPy
Explanation

NumPy (Numerical Python) is the fundamental package for scientific computing with Python. It
provides a high-performance multidimensional array object (ndarray), and tools for working
with these arrays. NumPy arrays are significantly more efficient than Python lists for numerical
operations, especially with large datasets, because they are implemented in C and Fortran. It's
the backbone for many other scientific libraries in Python (like pandas, SciPy, scikit-learn).

Key features of NumPy arrays:

● Homogeneous: All elements in a NumPy array must be of the same data type.
● Vectorized operations: Operations are applied element-wise, making calculations
much faster than manual loops over Python lists.
● Fixed size: Once created, the size of a NumPy array cannot change (though you can
create new ones from existing data).

Syntax

Typically imported with the alias np:

Python
import numpy as np

Creating a NumPy array from a list:

Python
np.array(python_list)

Code Example
Python
import numpy as np

# Create a NumPy array from a Python list


my_list = [1, 2, 3, 4, 5]
numpy_array = np.array(my_list)
print(f"Python list: {my_list}, Type: {type(my_list)}")
print(f"NumPy array: {numpy_array}, Type: {type(numpy_array)}")
# Output:
# Python list: [1, 2, 3, 4, 5], Type: <class 'list'>
# NumPy array: [1 2 3 4 5], Type: <class 'numpy.ndarray'>
# Demonstrating vectorized operations
list_a = [1, 2, 3]
list_b = [4, 5, 6]

# With Python lists, '+' concatenates


list_sum = list_a + list_b
print(f"List concatenation: {list_sum}") # Output: List concatenation: [1, 2, 3, 4, 5, 6]

# With NumPy arrays, '+' performs element-wise addition


np_array_a = np.array(list_a)
np_array_b = np.array(list_b)
numpy_sum = np_array_a + np_array_b
print(f"NumPy element-wise sum: {numpy_sum}") # Output: NumPy element-wise sum: [5 7 9]

# Other vectorized operations


print(f"NumPy multiplication: {np_array_a * 2}") # Output: NumPy multiplication: [2 4 6]
print(f"NumPy division: {np_array_b / 2}") # Output: NumPy division: [2. 2.5 3. ]

# Check data type of elements in the array


print(f"Data type of NumPy array elements: {numpy_array.dtype}") # Output: Data type of
NumPy array elements: int64 (or int32 depending on system)

35. Your First NumPy Array


Explanation

Creating your first NumPy array is a crucial step to leveraging NumPy's power. The most
common way to create an ndarray is by passing a Python list (or nested lists for multi-
dimensional arrays) to the np.array() function. Remember that all elements in a NumPy array
will be forced to a single, common data type.

Syntax
Python
import numpy as np
my_array = np.array(list_or_nested_list)

Code Example
Python
import numpy as np
# Create a 1D NumPy array from a simple list
heights_cm = [170, 180, 175, 190, 165]
np_heights = np.array(heights_cm)
print(f"1D NumPy array: {np_heights}")
print(f"Shape of 1D array: {np_heights.shape}") # (5,) means 5 elements, 1 dimension
print(f"Type of elements: {np_heights.dtype}") # Output: Type of elements: int64 (or int32)

# Create a 2D NumPy array from a list of lists (representing rows)


# Example: [height_cm, weight_kg]
data = [[170, 70], [180, 80], [175, 75]]
np_data = np.array(data)
print(f"\n2D NumPy array:\n{np_data}")
print(f"Shape of 2D array: {np_data.shape}") # (3, 2) means 3 rows, 2 columns
print(f"Type of elements: {np_data.dtype}") # Output: Type of elements: int64 (or int32)

# NumPy array converting types if heterogeneous


mixed_list = [1, 2.5, 3] # Contains int and float
np_mixed = np.array(mixed_list)
print(f"\nNumPy array from mixed list: {np_mixed}")
print(f"Type of elements (automatically converted to float): {np_mixed.dtype}") # Output: Type of
elements (automatically converted to float): float64

36. Baseball players' height


Explanation

This is a specific application scenario to demonstrate working with NumPy arrays. It implies
having a dataset of baseball players' heights, likely as a list, and then converting it to a NumPy
array to perform numerical operations efficiently. This example serves to provide a concrete
context for applying np.array() and subsequent numerical operations.

Syntax

(Re-using np.array() and basic arithmetic operations)

Code Example
Python
import numpy as np

# Heights of baseball players in inches (example data)


heights_in = [74, 74, 72, 72, 73, 69, 69, 71, 76, 71, 73, 73, 74, 74, 69, 70, 73, 75, 78, 79,
76, 74, 74, 73, 75, 75, 74, 72, 74, 71, 70, 75, 77, 72, 71, 74, 74, 73, 75, 76,
74, 72, 76, 75, 74, 69, 72, 70, 73, 75, 76, 70, 72, 73, 74, 71, 76, 71, 75, 76,
71, 75, 74, 74, 69, 70, 73, 75, 78, 79, 76, 74, 74, 73, 75, 75, 74, 72, 74, 71,
70, 75, 77, 72, 71, 74, 74, 73, 75, 76, 74, 72, 76, 75, 74, 69, 72, 70, 73, 75]

# Convert the list to a NumPy array


np_heights_in = np.array(heights_in)
print(f"NumPy array of heights (inches):\n{np_heights_in[:10]}...") # Show first 10 for brevity
print(f"Shape: {np_heights_in.shape}")
print(f"Data type: {np_heights_in.dtype}")

# Perform a vectorized calculation: convert inches to meters


# 1 inch = 0.0254 meters
np_heights_m = np_heights_in * 0.0254
print(f"\nNumPy array of heights (meters):\n{np_heights_m[:10]}...") # Show first 10 for brevity

# Calculate the average height in meters


average_height_m = np.mean(np_heights_m)
print(f"Average height of players: {average_height_m:.2f} meters") # Output: Average height of
players: 1.83 meters

37. NumPy Side Effects


Explanation

The "side effects" typically refer to the implicit type conversion that NumPy performs when
creating an array from mixed-type Python lists. Because NumPy arrays are homogeneous (all
elements must be of the same type), if you pass a list containing different types, NumPy will
automatically "upcast" all elements to the most general (or "largest") compatible type to avoid
data loss. For example, if an array contains integers and floats, all integers will be converted to
floats. If it contains numbers and strings, all numbers might be converted to strings.

Another "side effect" (or important characteristic) is that many NumPy operations return new
arrays, rather than modifying the original in-place, especially when dealing with arithmetic
operations.

Syntax

(No specific syntax, but understanding the behavior of np.array() and operations)

Code Example
Python
import numpy as np

# Example 1: Type Upcasting


mixed_list_1 = [1, 2, 3.5, 4]
np_mixed_1 = np.array(mixed_list_1)
print(f"List: {mixed_list_1}")
print(f"NumPy Array: {np_mixed_1}")
print(f"Elements Data Type (int to float): {np_mixed_1.dtype}")
# Output:
# List: [1, 2, 3.5, 4]
# NumPy Array: [1. 2. 3.5 4. ]
# Elements Data Type (int to float): float64

mixed_list_2 = [1, 2, "hello", 4.5] # Contains number and string


np_mixed_2 = np.array(mixed_list_2)
print(f"\nList: {mixed_list_2}")
print(f"NumPy Array: {np_mixed_2}")
print(f"Elements Data Type (all to string): {np_mixed_2.dtype}")
# Output:
# List: [1, 2, 'hello', 4.5]
# NumPy Array: ['1' '2' 'hello' '4.5']
# Elements Data Type (all to string): <U5 (or similar string type)
# Note: '<U5' means Unicode string of max length 5. All numbers were converted to strings.

# Example 2: Operations returning new arrays (not modifying in-place)


original_array = np.array([1, 2, 3])
print(f"\nOriginal array: {original_array}")

# Arithmetic operations create new arrays


new_array = original_array * 2
print(f"New array after multiplication: {new_array}")
print(f"Original array (unchanged): {original_array}") # Original is not modified

# Contrast with list.sort() or list.append() which modify in-place


my_python_list = [3, 1, 2]
my_python_list.sort() # In-place modification
print(f"Python list after sort (in-place): {my_python_list}")

38. Subsetting NumPy Arrays


Explanation

Subsetting NumPy arrays allows you to select specific elements or slices of the array, similar to
Python lists but with more advanced capabilities. NumPy's subsetting is very powerful for data
manipulation and analysis.

Syntax

● Single element: my_array[index]


● Slicing (1D): my_array[start:end:step]
● Boolean indexing: my_array[boolean_array] (selects elements where the
corresponding boolean is True)

Code Example
Python
import numpy as np

my_array = np.array([10, 20, 30, 40, 50, 60, 70, 80, 90])
print(f"Original NumPy array: {my_array}")

# 1. Accessing a single element


first_element = my_array[0]
print(f"First element: {first_element}") # Output: First element: 10

fifth_element = my_array[4]
print(f"Fifth element: {fifth_element}") # Output: Fifth element: 50

last_element = my_array[-1]
print(f"Last element: {last_element}") # Output: Last element: 90

# 2. Slicing 1D arrays
slice1 = my_array[2:6] # Elements from index 2 up to (but not including) 6
print(f"Slice from index 2 to 5: {slice1}") # Output: Slice from index 2 to 5: [30 40 50 60]

slice_to_end = my_array[5:] # From index 5 to the end


print(f"Slice from index 5 to end: {slice_to_end}") # Output: Slice from index 5 to end: [60 70 80
90]

slice_from_beginning = my_array[:3] # From beginning up to (but not including) index 3


print(f"Slice from beginning to index 2: {slice_from_beginning}") # Output: Slice from beginning
to index 2: [10 20 30]

slice_with_step = my_array[::2] # Every second element


print(f"Slice with step of 2: {slice_with_step}") # Output: Slice with step of 2: [10 30 50 70 90]
# 3. Boolean indexing (very powerful for filtering)
# Create a boolean array based on a condition
condition = my_array > 50
print(f"Boolean array (my_array > 50): {condition}")
# Output: Boolean array (my_array > 50): [False False False False False True True True
True]

# Use the boolean array to subset the original array


elements_greater_than_50 = my_array[condition]
print(f"Elements greater than 50: {elements_greater_than_50}") # Output: Elements greater than
50: [60 70 80 90]

# Chained boolean indexing


elements_between_30_and_70 = my_array[(my_array > 30) & (my_array < 70)] # Using '&' for
AND
print(f"Elements between 30 and 70: {elements_between_30_and_70}") # Output: Elements
between 30 and 70: [40 50 60]

39. 2D NumPy Arrays


Explanation

A 2D NumPy array is an array of arrays, effectively representing a matrix or a table with rows
and columns. It's crucial for working with tabular data in scientific computing and data analysis.
Like 1D arrays, all elements in a 2D array must have the same data type.

Syntax
Python
import numpy as np
two_d_array = np.array([
[row1_col1, row1_col2, ...],
[row2_col1, row2_col2, ...],
# ...
])

The shape of a 2D array is (number_of_rows, number_of_columns).

Code Example
Python
import numpy as np

# Create a 2D NumPy array (3 rows, 2 columns)


# Each inner list represents a row
data = [
[10, 20], # Row 0
[30, 40], # Row 1
[50, 60] # Row 2
]
two_d_array = np.array(data)

print(f"My 2D NumPy Array:\n{two_d_array}")


# Output:
# My 2D NumPy Array:
# [[10 20]
# [30 40]
# [50 60]]

print(f"Shape of the 2D array: {two_d_array.shape}") # Output: Shape of the 2D array: (3, 2)


print(f"Number of dimensions: {two_d_array.ndim}") # Output: Number of dimensions: 2
print(f"Total number of elements: {two_d_array.size}") # Output: Total number of elements: 6
print(f"Data type of elements: {two_d_array.dtype}") # Output: Data type of elements: int64

40. Your First 2D NumPy Array


Explanation

This topic specifically targets the creation of your initial 2D NumPy array, building on the general
concept of 2D arrays. It emphasizes the direct conversion of a "list of lists" (where each inner list
is a row) into a NumPy ndarray. This is a very common way to represent tabular data in
NumPy.

Syntax
Python
import numpy as np
my_2d_array = np.array([
[value_r0_c0, value_r0_c1, ...],
[value_r1_c0, value_r1_c1, ...],
# ...
])
Code Example
Python
import numpy as np

# Let's consider data for students: [Math Score, Science Score]


student_scores = [
[85, 90], # Student 1
[78, 82], # Student 2
[92, 88], # Student 3
[70, 75] # Student 4
]

# Create the 2D NumPy array


np_student_scores = np.array(student_scores)

print(f"Student Scores 2D Array:\n{np_student_scores}")


# Output:
# Student Scores 2D Array:
# [[85 90]
# [78 82]
# [92 88]
# [70 75]]

print(f"Shape: {np_student_scores.shape}") # Output: Shape: (4, 2)


print(f"Data type: {np_student_scores.dtype}") # Output: Data type: int64

# Example of a 2D array with mixed types (will upcast to float)


mixed_2d_data = [
[1, 2.5],
[3, 4]
]
np_mixed_2d = np.array(mixed_2d_data)
print(f"\nMixed 2D data:\n{np_mixed_2d}")
print(f"Data type of mixed 2D array: {np_mixed_2d.dtype}") # Output: Data type of mixed 2D
array: float64

41. Baseball data in 2D form


Explanation
This topic applies the concept of 2D NumPy arrays to a specific, real-world-like dataset:
baseball player statistics. Instead of just heights, we now consider multiple attributes (like height
and weight) for each player, naturally forming rows and columns in a 2D array. This reinforces
how ndarray can represent structured data.

Syntax

(Creation and basic access remain the same as "Your First 2D NumPy Array")

Code Example
Python
import numpy as np

# Example data for 3 baseball players: [height_inches, weight_lbs]


baseball_data = [
[74, 180], # Player 1
[72, 215], # Player 2
[75, 210], # Player 3
[70, 175], # Player 4
[78, 200] # Player 5
]

# Convert the list of lists into a 2D NumPy array


np_baseball = np.array(baseball_data)

print(f"Baseball Data (Height in inches, Weight in lbs):\n{np_baseball}")


# Output:
# Baseball Data (Height in inches, Weight in lbs):
# [[ 74 180]
# [ 72 215]
# [ 75 210]
# [ 70 175]
# [ 78 200]]

print(f"Shape of baseball data: {np_baseball.shape}") # Output: Shape of baseball data: (5, 2)


print(f"Data type: {np_baseball.dtype}") # Output: Data type: int64

# Now you can easily perform operations on columns (e.g., convert all weights to kg)
# 1 lb = 0.453592 kg
np_baseball_kg = np_baseball[:, 1] * 0.453592 # Select all rows, only the second column
(weight)
print(f"\nWeights in kg: {np_baseball_kg}")
# Output: Weights in kg: [ 81.64656 97.52228 95.25432 79.3786 90.7184 ]
# Or convert all heights to meters
# 1 inch = 0.0254 meters
np_baseball_m = np_baseball[:, 0] * 0.0254
print(f"Heights in meters: {np_baseball_m}")

42. Subsetting 2D NumPy Arrays


Explanation

Subsetting 2D NumPy arrays requires specifying indices for both rows and columns. You can
select single elements, entire rows/columns, or rectangular slices. This is done by providing
comma-separated indices/slices within the square brackets.

Syntax
Python
my_2d_array[row_index, column_index] # Single element
my_2d_array[row_index, :] # Entire row
my_2d_array[:, column_index] # Entire column
my_2d_array[row_start:row_end, col_start:col_end] # Rectangular slice

You can also use boolean indexing for rows or columns, or a combination.

Code Example
Python
import numpy as np

# Example 2D array representing student scores:


# Rows: Students (S1, S2, S3, S4, S5)
# Columns: Scores (Math, Science, English)
student_scores = np.array([
[85, 90, 78], # S1
[78, 82, 85], # S2
[92, 88, 95], # S3
[70, 75, 72], # S4
[88, 91, 87] # S5
])
print(f"Original Student Scores (2D Array):\n{student_scores}")

# 1. Accessing a single element (e.g., S3's English score)


s3_english_score = student_scores[2, 2]
print(f"\nS3's English score: {s3_english_score}") # Output: S3's English score: 95

# 2. Accessing an entire row (e.g., S2's scores)


s2_scores = student_scores[1, :]
print(f"S2's scores: {s2_scores}") # Output: S2's scores: [78 82 85]

# Accessing the last row


last_student_scores = student_scores[-1, :]
print(f"Last student's scores: {last_student_scores}") # Output: Last student's scores: [88 91 87]

# 3. Accessing an entire column (e.g., all Math scores)


all_math_scores = student_scores[:, 0]
print(f"All Math scores: {all_math_scores}") # Output: All Math scores: [85 78 92 70 88]

# 4. Slicing rows and columns (rectangular subset)


# Get Math and Science scores for S1 and S2
s1_s2_math_science = student_scores[0:2, 0:2]
print(f"S1 and S2 Math/Science scores:\n{s1_s2_math_science}")
# Output:
# S1 and S2 Math/Science scores:
# [[85 90]
# [78 82]]

# Get all scores for S3, S4, S5 (last 3 rows)


last_three_students = student_scores[2:, :]
print(f"Last three students' scores:\n{last_three_students}")

# 5. Using integer array indexing (to pick non-contiguous rows/columns)


# Get S1 and S3's scores
selected_students = student_scores[[0, 2], :]
print(f"S1 and S3's scores:\n{selected_students}")
# Output:
# S1 and S3's scores:
# [[85 90 78]
# [92 88 95]]

# Get Math and English scores for all students


math_english_scores = student_scores[:, [0, 2]]
print(f"Math and English scores for all students:\n{math_english_scores}")
# Output:
# Math and English scores for all students:
# [[85 78]
# [78 85]
# [92 95]
# [70 72]
# [88 87]]

43. 2D Arithmetic
Explanation

NumPy's power truly shines with 2D arithmetic. Just like 1D arrays, operations on 2D arrays are
element-wise by default. This means that if you add two 2D arrays, the corresponding elements
are added together. If you multiply a 2D array by a scalar, every element in the array is
multiplied by that scalar. This vectorized approach is significantly faster than using nested loops
with Python lists.

Syntax
Python
# Assuming two_d_array_A and two_d_array_B are 2D NumPy arrays of compatible shapes
result_array = two_d_array_A + two_d_array_B
result_array = two_d_array_A * scalar_value
result_array = two_d_array_A / two_d_array_B # Element-wise division

For matrix multiplication, you use @ or np.dot().

Code Example
Python
import numpy as np

# Exam scores for two subjects for two students


exam_scores_term1 = np.array([
[70, 80],
[85, 90]
])

exam_scores_term2 = np.array([
[75, 82],
[80, 95]
])

print(f"Term 1 Scores:\n{exam_scores_term1}")
print(f"\nTerm 2 Scores:\n{exam_scores_term2}")
# 1. Element-wise addition: Total score for each subject per student
total_scores = exam_scores_term1 + exam_scores_term2
print(f"\nTotal Scores (Term1 + Term2):\n{total_scores}")
# Output:
# Total Scores (Term1 + Term2):
# [[145 162]
# [165 185]]

# 2. Element-wise multiplication by a scalar (e.g., apply a weight factor)


weighted_term1 = exam_scores_term1 * 1.1 # 10% bonus for Term 1
print(f"\nWeighted Term 1 Scores (10% bonus):\n{weighted_term1}")
# Output:
# Weighted Term 1 Scores (10% bonus):
# [[77. 88. ]
# [93.5 99. ]]

# 3. Element-wise comparison (results in a boolean array)


are_better_in_term2 = exam_scores_term2 > exam_scores_term1
print(f"\nWere scores better in Term 2?\n{are_better_in_term2}")
# Output:
# Were scores better in Term 2?
# [[ True True]
# [False True]]

# 4. Matrix Multiplication (different from element-wise multiplication)


# Use '@' operator for matrix multiplication (Python 3.5+)
matrix_A = np.array([[1, 2], [3, 4]])
matrix_B = np.array([[5, 6], [7, 8]])
matrix_product = matrix_A @ matrix_B
print(f"\nMatrix A:\n{matrix_A}")
print(f"Matrix B:\n{matrix_B}")
print(f"Matrix Product (A @ B):\n{matrix_product}")
# Output:
# Matrix Product (A @ B):
# [[19 22]
# [43 50]]

44. NumPy: Basic Statistics


Explanation
NumPy provides a wide range of functions for performing basic statistical computations on
arrays, such as calculating the mean, median, standard deviation, sum, minimum, and
maximum. These functions are highly optimized for performance and can operate on entire
arrays or along specific axes (rows or columns) of 2D arrays.

Syntax
Python
np.mean(array)
np.median(array)
np.std(array)
np.sum(array)
np.min(array)
np.max(array)

# For 2D arrays, specify axis:


np.mean(array, axis=0) # Mean of each column
np.mean(array, axis=1) # Mean of each row

● axis=0: Operations are performed down the columns.


● axis=1: Operations are performed across the rows.

Code Example
Python
import numpy as np

# Sample data: scores of 5 students in 3 subjects (Math, Science, English)


scores = np.array([
[85, 90, 78],
[78, 82, 85],
[92, 88, 95],
[70, 75, 72],
[88, 91, 87]
])
print(f"Student Scores:\n{scores}")

# 1. Statistics on the entire array


overall_mean = np.mean(scores)
overall_median = np.median(scores)
overall_std = np.std(scores)
print(f"\nOverall Mean Score: {overall_mean:.2f}") # Output: Overall Mean Score: 84.00
print(f"Overall Median Score: {overall_median}") # Output: Overall Median Score: 85.0
print(f"Overall Standard Deviation: {overall_std:.2f}") # Output: Overall Standard Deviation: 6.94
# 2. Statistics along an axis (e.g., mean score for each subject)
# axis=0 means compute across the rows for each column
mean_scores_per_subject = np.mean(scores, axis=0)
print(f"\nMean scores per subject (Math, Science, English): {mean_scores_per_subject:.2f}")
# Output: Mean scores per subject (Math, Science, English): [82.60 85.20 83.40]

# 3. Statistics along an axis (e.g., average score for each student)


# axis=1 means compute along the columns for each row
mean_scores_per_student = np.mean(scores, axis=1)
print(f"Mean scores per student: {mean_scores_per_student:.2f}")
# Output: Mean scores per student: [84.33 81.67 91.67 72.33 88.67]

# Other basic statistics


total_sum = np.sum(scores)
print(f"\nSum of all scores: {total_sum}") # Output: Sum of all scores: 1260

min_score = np.min(scores)
max_score = np.max(scores)
print(f"Minimum score: {min_score}") # Output: Minimum score: 70
print(f"Maximum score: {max_score}") # Output: Maximum score: 95

45. Average versus median


Explanation

When analyzing data, both the average (mean) and the median are measures of central
tendency, but they provide different insights, especially when dealing with skewed data or
outliers.

● Average (Mean): Calculated by summing all values and dividing by the count of values.
It is sensitive to extreme values (outliers).
● Median: The middle value in a dataset when the values are ordered from least to
greatest. If there's an even number of values, it's the average of the two middle values.
The median is robust to outliers, meaning it's less affected by extremely high or low
values.

Choosing between mean and median depends on the nature of your data and what you want to
represent. For example, income data is often skewed by a few very high earners, making the
median a more representative measure of typical income.

Syntax
Python
np.mean(array)
np.median(array)

Code Example
Python
import numpy as np

# Dataset 1: Heights (relatively symmetric distribution)


heights = np.array([170, 172, 175, 178, 180, 182, 185])
print(f"Heights data: {heights}")
print(f"Mean height: {np.mean(heights):.2f}") # Output: Mean height: 177.43
print(f"Median height: {np.median(heights)}") # Output: Median height: 178.0

# Dataset 2: Incomes (likely skewed with potential outliers)


# One very high income skews the average
incomes = np.array([30000, 35000, 40000, 42000, 50000, 60000, 500000])
print(f"\nIncomes data: {incomes}")
print(f"Mean income: {np.mean(incomes):.2f}") # Output: Mean income: 109571.43
(influenced by 500k)
print(f"Median income: {np.median(incomes)}") # Output: Median income: 42000.0 (more
representative of typical income)

# Demonstrating the effect of an outlier


data_with_outlier = np.array([1, 2, 3, 4, 100])
print(f"\nData with outlier: {data_with_outlier}")
print(f"Mean with outlier: {np.mean(data_with_outlier):.2f}") # Output: Mean with outlier: 22.00
print(f"Median with outlier: {np.median(data_with_outlier)}") # Output: Median with outlier: 3.0

46. Explore the baseball data


Explanation

This is a continuation of the "Baseball data in 2D form" topic, focusing on applying the statistical
functions you just learned (mean, median, etc.) to a real-world dataset. The goal is to extract
meaningful insights from the data, such as average height, median weight, or the standard
deviation of certain measurements.

Syntax
(Application of np.mean(), np.median(), np.std() on specific columns of a 2D NumPy array)

Code Example
Python
import numpy as np

# Re-using the baseball data (heights in inches, weights in lbs)


np_baseball = np.array([
[74, 180], [74, 215], [72, 210], [72, 210], [73, 188],
[69, 176], [69, 195], [71, 200], [76, 200], [71, 186],
[73, 220], [73, 178], [74, 260], [74, 200], [69, 192],
[70, 190], [73, 190], [75, 220], [78, 220], [79, 185]
])

print(f"Baseball data (first 5 players):\n{np_baseball[:5]}")


print(f"Shape: {np_baseball.shape}") # (20, 2) in this example

# Extract height and weight columns


heights_in = np_baseball[:, 0] # All rows, first column (height)
weights_lbs = np_baseball[:, 1] # All rows, second column (weight)

print(f"\nHeights (inches): {heights_in}")


print(f"Weights (lbs): {weights_lbs}")

# Calculate statistics for heights


mean_height_in = np.mean(heights_in)
median_height_in = np.median(heights_in)
std_height_in = np.std(heights_in)

print(f"\n--- Height Statistics (inches) ---")


print(f"Mean height: {mean_height_in:.2f}") # Output: Mean height: 73.15
print(f"Median height: {median_height_in}") # Output: Median height: 73.0
print(f"Standard deviation of height: {std_height_in:.2f}") # Output: Standard deviation of height:
2.82

# Calculate statistics for weights


mean_weight_lbs = np.mean(weights_lbs)
median_weight_lbs = np.median(weights_lbs)
std_weight_lbs = np.std(weights_lbs)

print(f"\n--- Weight Statistics (lbs) ---")


print(f"Mean weight: {mean_weight_lbs:.2f}") # Output: Mean weight: 199.15
print(f"Median weight: {median_weight_lbs}") # Output: Median weight: 197.5
print(f"Standard deviation of weight: {std_weight_lbs:.2f}") # Output: Standard deviation of
weight: 18.06

# Example: Filtering players based on height


tall_players_weights = np_baseball[heights_in > 75, 1] # Weights of players taller than 75
inches
print(f"\nWeights of players taller than 75 inches: {tall_players_weights}") # Output: Weights of
players taller than 75 inches: [200 220 220 185]
print(f"Average weight of tall players: {np.mean(tall_players_weights):.2f} lbs")

47. Comparison Operators


Explanation

Comparison operators (also known as relational operators) are used to compare two values.
They evaluate to a Boolean value (True or False) based on whether the comparison is true or
false. These operators are fundamental for creating conditional statements and for filtering data.

Syntax

● ==: Equal to
● !=: Not equal to
● >: Greater than
● <: Less than
● >=: Greater than or equal to
● <=: Less than or equal to

Code Example
Python
# Variables for comparison
a = 10
b = 20
c = 10
text1 = "Python"
text2 = "python"

# Equality (==)
print(f"Is a equal to b? {a == b}") # Output: False
print(f"Is a equal to c? {a == c}") # Output: True
print(f"Is text1 equal to text2? {text1 == text2}") # Output: False (case-sensitive)

# Not Equal (!=)


print(f"Is a not equal to b? {a != b}") # Output: True
# Greater Than (>)
print(f"Is b greater than a? {b > a}") # Output: True

# Less Than (<)


print(f"Is a less than b? {a < b}") # Output: True

# Greater Than or Equal To (>=)


print(f"Is a greater than or equal to c? {a >= c}") # Output: True
print(f"Is b greater than or equal to a? {b >= a}") # Output: True

# Less Than or Equal To (<=)


print(f"Is a less than or equal to b? {a <= b}") # Output: True

# Comparison of strings (lexicographical order)


print(f"Is 'apple' < 'banana'? {'apple' < 'banana'}") # Output: True
print(f"Is 'cat' > 'Car'? {'cat' > 'Car'}") # Output: True (lowercase 'c' > uppercase 'C')

48. Equality
Explanation

The equality operator == checks if two values are the same. It returns True if they are equal
and False otherwise. It's important to distinguish == (comparison) from = (assignment).
Python is case-sensitive for string comparisons.

Syntax
Python
value1 == value2

Code Example
Python
num1 = 100
num2 = 100
num3 = 200

string1 = "hello"
string2 = "hello"
string3 = "Hello"
list1 = [1, 2, 3]
list2 = [1, 2, 3]
list3 = [3, 2, 1]

# Comparing numbers
print(f"Is num1 == num2? {num1 == num2}") # Output: True
print(f"Is num1 == num3? {num1 == num3}") # Output: False

# Comparing strings (case-sensitive)


print(f"Is string1 == string2? {string1 == string2}") # Output: True
print(f"Is string1 == string3? {string1 == string3}") # Output: False

# Comparing lists (elements and order must be identical)


print(f"Is list1 == list2? {list1 == list2}") # Output: True
print(f"Is list1 == list3? {list1 == list3}") # Output: False

# Comparing different types (usually False, but Python tries to convert sometimes)
print(f"Is 10 == 10.0? {10 == 10.0}") # Output: True (Python converts int to float for comparison)
print(f"Is '10' == 10? {'10' == 10}") # Output: False (no automatic conversion for string vs int)

49. Greater and less than


Explanation

The > (greater than) and < (less than) operators are used to determine the relative order of two
values. They are fundamental for sorting, filtering, and controlling program flow. They return
True if the condition is met, and False otherwise.

Syntax
Python
value1 > value2
value1 < value2

Similar syntax applies to >= (greater than or equal to) and <= (less than or equal to).

Code Example
Python
temp_celsius = 25
temp_fahrenheit = 77
# Greater than (>)
print(f"Is {temp_celsius}C > 20C? {temp_celsius > 20}") # Output: True
print(f"Is {temp_fahrenheit}F > 80F? {temp_fahrenheit > 80}") # Output: False

# Less than (<)


print(f"Is {temp_celsius}C < 30C? {temp_celsius < 30}") # Output: True
print(f"Is {temp_fahrenheit}F < 70F? {temp_fahrenheit < 70}") # Output: False

# Greater than or equal to (>=)


min_age_drive = 16
my_age = 18
print(f"Am I old enough to drive? {my_age >= min_age_drive}") # Output: True

# Less than or equal to (<=)


max_capacity = 100
current_occupancy = 99
print(f"Is occupancy within limit? {current_occupancy <= max_capacity}") # Output: True

# Comparing strings lexicographically (alphabetical order)


word1 = "apple"
word2 = "banana"
word3 = "Apple"
print(f"Is '{word1}' < '{word2}'? {word1 < word2}") # Output: True
print(f"Is '{word3}' < '{word1}'? {word3 < word1}") # Output: True ('A' comes before 'a' in ASCII)

50. Compare arrays


Explanation

NumPy allows you to apply comparison operators directly to arrays. When you compare two
NumPy arrays (or an array and a scalar), the comparison is performed element-wise. The
result is a new NumPy array of boolean values, where each element is True or False
depending on the result of the comparison for the corresponding elements. This is incredibly
powerful for filtering and selecting data.

Syntax
Python
np_array1 == np_array2
np_array > scalar_value
np_array_A <= np_array_B
Shapes must be compatible for element-wise comparison.

Code Example
Python
import numpy as np

# 1D arrays
heights = np.array([170, 180, 175, 190, 165])
threshold_height = 175

# Element-wise comparison with a scalar


is_tall = heights > threshold_height
print(f"Heights: {heights}")
print(f"Is taller than {threshold_height}cm? {is_tall}")
# Output: Is taller than 175cm? [False True False True False]

# Use boolean array for subsetting (filtering)


tall_players = heights[is_tall]
print(f"Players taller than {threshold_height}cm: {tall_players}") # Output: Players taller than
175cm: [180 190]

# 2D arrays
scores1 = np.array([
[85, 90],
[70, 75]
])
scores2 = np.array([
[80, 92],
[70, 70]
])

print(f"\nScores 1:\n{scores1}")
print(f"Scores 2:\n{scores2}")

# Element-wise equality of two 2D arrays


are_scores_equal = scores1 == scores2
print(f"\nAre scores equal (element-wise)?\n{are_scores_equal}")
# Output:
# Are scores equal (element-wise)?
# [[False False]
# [ True False]]

# Element-wise greater than with a scalar


passed_threshold = scores1 > 80
print(f"\nScores > 80 (element-wise)?\n{passed_threshold}")
# Output:
# Scores > 80 (element-wise)?
# [[ True True]
# [False False]]

# Element-wise greater than or equal to


scores_at_least_85 = scores1 >= 85
print(f"\nScores >= 85 (element-wise)?\n{scores_at_least_85}")
# Output:
# Scores >= 85 (element-wise)?
# [[ True True]
# [False False]]

51. Boolean Operators


Explanation

Boolean operators (and, or, not) are used to combine or modify boolean expressions
(True/False values). They are crucial for building complex conditions in if statements, loops,
and data filtering, allowing you to check for multiple criteria simultaneously.

Syntax

● condition1 and condition2: True if BOTH conditions are True.


● condition1 or condition2: True if AT LEAST ONE condition is True.
● not condition: Inverts the boolean value (True becomes False, False becomes True).

Code Example
Python
is_sunny = True
is_warm = False
has_hat = True

# 1. 'and' operator
# Both conditions must be True
print(f"Is it sunny AND warm? {is_sunny and is_warm}") # Output: False
print(f"Is it sunny AND I have a hat? {is_sunny and has_hat}") # Output: True

# 2. 'or' operator
# At least one condition must be True
print(f"Is it sunny OR warm? {is_sunny or is_warm}") # Output: True
print(f"Is it warm OR I have a hat? {is_warm or has_hat}") # Output: True
print(f"Is it warm OR I don't have a hat? {is_warm or not has_hat}") # Output: False (False or
False is False)

# 3. 'not' operator
# Inverts the boolean value
print(f"Is it NOT warm? {not is_warm}") # Output: True
print(f"Is it NOT sunny? {not is_sunny}") # Output: False

# Combining operators
# Example: If it's sunny AND (warm OR I have a hat)
complex_condition = is_sunny and (is_warm or has_hat)
print(f"Complex condition (sunny AND (warm OR hat)): {complex_condition}") # Output: True
(True and (False or True) -> True and True -> True)

52. and, or, not (1)


Explanation

This topic specifically re-emphasizes the core usage of the logical operators and, or, and not
with simple boolean values or expressions. Understanding their truth tables is key:

● A and B: True only if A is True AND B is True.


● A or B: True if A is True OR B is True (or both).
● not A: True if A is False, False if A is True.

Syntax
Python
expression1 and expression2
expression1 or expression2
not expression

Code Example
Python
# Simple boolean variables
bool_a = True
bool_b = False
bool_c = True
# Using 'and'
print(f"True and True: {bool_a and bool_c}") # Output: True
print(f"True and False: {bool_a and bool_b}") # Output: False
print(f"False and False: {bool_b and bool_b}") # Output: False

# Using 'or'
print(f"True or True: {bool_a or bool_c}") # Output: True
print(f"True or False: {bool_a or bool_b}") # Output: True
print(f"False or False: {bool_b or bool_b}") # Output: False

# Using 'not'
print(f"not True: {not bool_a}") # Output: False
print(f"not False: {not bool_b}") # Output: True

# Chaining comparisons with boolean operators


age = 25
is_student = True

is_eligible = (age >= 18) and (age <= 30)


print(f"Is age between 18 and 30? {is_eligible}") # Output: True

can_enroll = (age > 18) or is_student


print(f"Can enroll (age > 18 OR student)? {can_enroll}") # Output: True

must_study = not is_student


print(f"Must study (if not student)? {must_study}") # Output: False

53. and, or, not (2)


Explanation

This continuation delves slightly deeper into the behavior of and and or beyond just returning
True/False. Python's and and or operators use short-circuit evaluation:

● and: If the first operand is False, it immediately returns the first operand without
evaluating the second. Otherwise, it returns the second operand.
● or: If the first operand is True, it immediately returns the first operand without evaluating
the second. Otherwise, it returns the second operand.

This behavior is important for efficiency and sometimes for control flow, especially when the
second operand might involve a costly computation or a potential error. Also, Python considers
certain "falsy" values (like 0, None, "", [], {}) as False in a boolean context, and "truthy"
values as True.

Syntax

(Same as before, but pay attention to the return value)

Code Example
Python
# Short-circuiting with 'and'
print(f"False and (1 / 0): {False and (1 / 0)}")
# Output: False (1/0 is not evaluated because False is already determined)

# print(f"True and (1 / 0): {True and (1 / 0)}") # This would cause a ZeroDivisionError
# Output: ZeroDivisionError: division by zero (because True is evaluated, then it tries to
evaluate 1/0)

# Short-circuiting with 'or'


print(f"True or (1 / 0): {True or (1 / 0)}")
# Output: True (1/0 is not evaluated because True is already determined)

# print(f"False or (1 / 0): {False or (1 / 0)}") # This would cause a ZeroDivisionError


# Output: ZeroDivisionError: division by zero (because False is evaluated, then it tries to
evaluate 1/0)

# Truthiness and Falsiness


# Falsy values: 0, None, '', [], {}, () (empty tuple), set() (empty set)
# All other values are Truthy
print(f"\nbool(0): {bool(0)}") # Output: bool(0): False
print(f"bool(1): {bool(1)}") # Output: bool(1): True
print(f"bool(''): {bool('')}") # Output: bool(''): False
print(f"bool('abc'): {bool('abc')}") # Output: bool('abc'): True
print(f"bool([]): {bool([])}") # Output: bool([]): False
print(f"bool([1, 2]): {bool([1, 2])}") # Output: bool([1, 2]): True
print(f"bool(None): {bool(None)}") # Output: bool(None): False

# Applying short-circuiting with truthy/falsy values


# If first is True (truthy), 'or' returns first value
result_or = "Hello" or "World"
print(f"\n'Hello' or 'World': '{result_or}'") # Output: 'Hello' or 'World': 'Hello'

# If first is False (falsy), 'or' returns second value


result_or_2 = "" or "Default"
print(f"'' or 'Default': '{result_or_2}'") # Output: '' or 'Default': 'Default'
# If first is False (falsy), 'and' returns first value
result_and = "" and "World"
print(f"'' and 'World': '{result_and}'") # Output: '' and 'World': ''

# If first is True (truthy), 'and' returns second value


result_and_2 = "Hello" and "World"
print(f"'Hello' and 'World': '{result_and_2}'") # Output: 'Hello' and 'World': 'World'

54. Boolean operators with NumPy


Explanation

When performing boolean operations (and, or, not) directly on NumPy arrays, you cannot use
the Python keywords and, or, not. These keywords operate on single boolean values. For
element-wise boolean operations on NumPy arrays, you must use the bitwise logical
operators:

● & (bitwise AND): For element-wise logical AND.


● | (bitwise OR): For element-wise logical OR.
● ~ (bitwise NOT): For element-wise logical NOT.

These operators return a new boolean NumPy array where the operation has been applied to
each corresponding element. They are crucial for complex filtering conditions on numerical data.

Syntax
Python
(condition_array_1) & (condition_array_2) # Element-wise AND
(condition_array_1) | (condition_array_2) # Element-wise OR
~(condition_array) # Element-wise NOT

Important: Always use parentheses around individual comparison expressions when combining
them with & or | because of operator precedence.

Code Example
Python
import numpy as np

# Sample data: Ages of people


ages = np.array([15, 22, 18, 35, 17, 28, 40])
print(f"Ages: {ages}")

# Condition 1: Age is greater than 18


condition1 = ages > 18
print(f"Ages > 18: {condition1}")
# Output: Ages > 18: [False True False True False True True]

# Condition 2: Age is less than 30


condition2 = ages < 30
print(f"Ages < 30: {condition2}")
# Output: Ages < 30: [ True True True False True True False]

# 1. Element-wise AND (`&`)


# People who are older than 18 AND younger than 30
eligible_for_program = (ages > 18) & (ages < 30)
print(f"Eligible for program (18 < age < 30): {eligible_for_program}")
# Output: Eligible for program (18 < age < 30): [False True False False False True False]
print(f"Ages eligible: {ages[eligible_for_program]}") # Output: Ages eligible: [22 28]

# 2. Element-wise OR (`|`)
# People who are younger than 18 OR older than 35
extreme_ages = (ages < 18) | (ages > 35)
print(f"Extreme ages (age < 18 OR age > 35): {extreme_ages}")
# Output: Extreme ages (age < 18 OR age > 35): [ True False False False True False True]
print(f"Ages with extreme values: {ages[extreme_ages]}") # Output: Ages with extreme values:
[15 17 40]

# 3. Element-wise NOT (`~`)


# People who are NOT younger than 18 (i.e., 18 or older)
not_too_young = ~(ages < 18)
print(f"Not too young (age >= 18): {not_too_young}")
# Output: Not too young (age >= 18): [False True True True False True False]
print(f"Ages not too young: {ages[not_too_young]}") # Output: Ages not too young: [22 18 35 28
40]

# --- IMPORTANT NOTE ---


# Using 'and', 'or', 'not' keywords with NumPy arrays will result in an error:
# print(ages > 18 and ages < 30) # This would raise a ValueError

55. if, elif, else


Explanation

if, elif (else if), and else statements are control flow constructs that allow your program to
execute different blocks of code based on certain conditions. This enables your programs to
make decisions and respond dynamically to varying inputs or states.

● if: The basic conditional statement. If its condition is True, the code block inside the if
statement is executed.
● elif: Short for "else if." It's used to check additional conditions if the preceding if or elif
conditions were False. You can have multiple elif blocks.
● else: An optional block that executes if none of the preceding if or elif conditions were
True. It serves as a default path.

Syntax
Python
if condition1:
# Code to execute if condition1 is True
elif condition2:
# Code to execute if condition1 is False AND condition2 is True
else:
# Code to execute if all preceding conditions are False

Indentation is crucial in Python to define the code blocks.

Code Example
Python
temperature = 28
time_of_day = "morning"

# 1. Basic `if` statement


if temperature > 25:
print("It's a hot day!") # Output: It's a hot day!

# 2. `if-else` statement
if time_of_day == "morning":
print("Good morning!") # Output: Good morning!
else:
print("Hello!")

# 3. `if-elif-else` chain
score = 85

if score >= 90:


print("Grade: A")
elif score >= 80:
print("Grade: B") # Output: Grade: B
elif score >= 70:
print("Grade: C")
else:
print("Grade: F")

# Another example: Checking multiple conditions


humidity = 70
wind_speed = 15 # mph

if temperature > 30 and humidity > 80:


print("It's scorching and muggy!")
elif temperature > 25 and wind_speed < 10:
print("It's warm but calm.")
elif temperature > 20 or humidity > 60:
print("It's either warm or humid.") # Output: It's either warm or humid.
else:
print("Pleasant weather.")

# A single line if-else (ternary operator)


status = "Adult" if age >= 18 else "Minor"
age = 15
status = "Adult" if age >= 18 else "Minor"
print(f"Age {age} is a {status}") # Output: Age 15 is a Minor

56. Warmup (for if, elif, else)


Explanation

This is a gentle introduction or practice scenario before diving deep into if, elif, else. The idea
is to present a simple problem that can be solved using basic conditional logic. It helps solidify
the understanding of comparison operators and basic if statements.

Syntax

(Basic if statement syntax)

Code Example
Python
# Warmup Scenario: Check if a number is positive.

number = 10

if number > 0:
print("The number is positive.") # Output: The number is positive.

number = -5
if number > 0:
print("The number is positive.") # This line won't be printed

# Warmup Scenario 2: Check if a password meets a minimum length.


password = "mysecretpassword"
min_length = 8

if len(password) >= min_length:


print("Password meets minimum length requirement.") # Output: Password meets minimum
length requirement.

short_password = "short"
if len(short_password) >= min_length:
print("Password meets minimum length requirement.") # This line won't be printed

57. if
Explanation

The if statement is the most basic form of conditional execution. It allows you to execute a block
of code only if a specified condition evaluates to True. If the condition is False, the code block
associated with the if statement is simply skipped, and the program continues with the code
after the if block.

Syntax
Python
if condition:
# This code block executes if 'condition' is True
statement1
statement2
# ...
The condition can be any expression that evaluates to a boolean (True or False) value.

Code Example
Python
# Scenario: Check if it's raining to decide if you need an umbrella.
is_raining = True

if is_raining:
print("Don't forget your umbrella!") # Output: Don't forget your umbrella!
print("It's wet outside.")

# Change the condition


is_raining = False

if is_raining:
print("Don't forget your umbrella!") # This won't print
print("It's wet outside.")

print("Time to go!") # This line always executes

# Example with a numerical condition


stock_price = 150
if stock_price > 100:
print("Stock price is high.") # Output: Stock price is high.

# Example with a string condition


user_status = "admin"
if user_status == "admin":
print("Welcome, Administrator!") # Output: Welcome, Administrator!

58. Add else


Explanation

The else statement provides an alternative path of execution when the condition in the
preceding if statement (or if-elif chain) evaluates to False. It ensures that one of the code
blocks will always be executed: either the if block (if the condition is True) or the else block (if
the condition is False).

Syntax
Python
if condition:
# Code to execute if condition is True
else:
# Code to execute if condition is False

Code Example
Python
# Scenario: Check if a number is even or odd.
number = 7

if number % 2 == 0: # The '%' (modulo) operator gives the remainder of a division


print(f"{number} is an even number.")
else:
print(f"{number} is an odd number.") # Output: 7 is an odd number.

number = 12
if number % 2 == 0:
print(f"{number} is an even number.") # Output: 12 is an even number.
else:
print(f"{number} is an odd number.")

# Scenario: Check if an item is in stock.


stock_count = 0

if stock_count > 0:
print("Item is in stock. You can purchase.")
else:
print("Item is out of stock.") # Output: Item is out of stock.

# Scenario: Check if a user is logged in.


is_logged_in = False

if is_logged_in:
print("Access granted to user profile.")
else:
print("Please log in to continue.") # Output: Please log in to continue.

59. Customize further: elif


Explanation
The elif (short for "else if") statement allows you to test multiple conditions sequentially. It's
used when you have more than two possible outcomes and each outcome depends on a
distinct condition. The elif condition is only checked if the preceding if (or previous elif)
condition(s) were False. The first True condition encountered will have its corresponding block
executed, and the rest of the elif and else chain will be skipped.

Syntax
Python
if first_condition:
# Code for first_condition True
elif second_condition:
# Code for first_condition False AND second_condition True
elif third_condition:
# Code for first_condition False AND second_condition False AND third_condition True
else:
# Code if none of the above conditions are True

Code Example
Python
# Scenario: Assign a grade based on a score.
score = 75

if score >= 90:


print("Grade: A (Excellent!)")
elif score >= 80:
print("Grade: B (Very Good)")
elif score >= 70:
print("Grade: C (Good)") # Output: Grade: C (Good)
elif score >= 60:
print("Grade: D (Pass)")
else:
print("Grade: F (Fail)")

# Scenario: Determine the time of day.


hour = 14 # 2 PM

if hour < 12:


print("Good Morning!")
elif hour < 18: # This 'elif' is checked only if hour >= 12
print("Good Afternoon!") # Output: Good Afternoon!
else: # This 'else' is checked only if hour >= 18
print("Good Evening!")
# Scenario: Traffic light logic
light_color = "yellow"

if light_color == "green":
print("Go!")
elif light_color == "yellow":
print("Prepare to stop.") # Output: Prepare to stop.
elif light_color == "red":
print("Stop!")
else:
print("Invalid light color.")

60. Filtering pandas DataFrames


Explanation

This topic introduces how to filter data in pandas DataFrames, a core skill in data analysis.
pandas is a powerful library built on NumPy, providing data structures like DataFrames, which
are tabular (like spreadsheets or SQL tables). Filtering a DataFrame involves selecting rows (or
sometimes columns) that meet specific criteria. This is typically done using boolean indexing.

Syntax
Python
import pandas as pd

# Create a DataFrame (if not already loaded)


df = pd.DataFrame(data)

# Filtering syntax:
filtered_df = df[df['column_name'] == value]
filtered_df = df[(df['column1'] > threshold) & (df['column2'] == 'category')]

The df[...] syntax with a boolean Series inside acts as a row selector.

Code Example
Python
import pandas as pd
import numpy as np # Often used with pandas
# Create a sample DataFrame for demonstration
data = {
'Name': ['Alice', 'Bob', 'Charlie', 'David', 'Eve'],
'Age': [25, 30, 22, 35, 28],
'City': ['New York', 'London', 'New York', 'Paris', 'London'],
'Score': [85, 92, 78, 95, 88]
}
df = pd.DataFrame(data)
print(f"Original DataFrame:\n{df}\n")

# 1. Filter by a single condition (e.g., Age > 25)


# This creates a boolean Series:
age_condition = df['Age'] > 25
print(f"Boolean Series for Age > 25:\n{age_condition}\n")
# Output:
# 0 False
# 1 True
# 2 False
# 3 True
# 4 True
# Name: Age, dtype: bool

# Use the boolean Series to filter the DataFrame


filtered_by_age = df[age_condition]
print(f"Filtered (Age > 25):\n{filtered_by_age}\n")
# Output:
# Name Age City Score
#1 Bob 30 London 92
# 3 David 35 Paris 95
#4 Eve 28 London 88

# Shorter syntax for single condition


filtered_by_score = df[df['Score'] >= 90]
print(f"Filtered (Score >= 90):\n{filtered_by_score}\n")

# 2. Filter by multiple conditions (using & for AND, | for OR)


# People in New York AND Score > 80
ny_high_score = df[(df['City'] == 'New York') & (df['Score'] > 80)]
print(f"Filtered (City='New York' AND Score > 80):\n{ny_high_score}\n")
# Output:
# Name Age City Score
# 0 Alice 25 New York 85

# People from London OR Paris


london_or_paris = df[(df['City'] == 'London') | (df['City'] == 'Paris')]
print(f"Filtered (City='London' OR City='Paris'):\n{london_or_paris}\n")

# 3. Using .isin() for multiple discrete values


# People whose city is either 'London' or 'Paris'
cities_to_filter = ['London', 'Paris']
filtered_by_isin = df[df['City'].isin(cities_to_filter)]
print(f"Filtered (City in ['London', 'Paris'] using .isin()):\n{filtered_by_isin}\n")

61. Driving right (1)


Explanation

This is likely a conceptual example to illustrate boolean filtering on a specific dataset related to
driving behavior or car statistics. It primes the user for applying the pandas filtering techniques
to a practical scenario. Imagine a DataFrame with columns like 'speed' or 'driving_side'.

Syntax

(Application of comparison operators and simple df[] indexing)

Code Example
Python
import pandas as pd

# Sample data: A DataFrame with 'country' and 'drives_right' columns


data = {
'country': ['United States', 'Canada', 'United Kingdom', 'Australia', 'Japan', 'India'],
'drives_right': [True, False, False, False, True, False]
}
cars = pd.DataFrame(data)
print(f"Original 'cars' DataFrame:\n{cars}\n")

# Scenario: Filter for countries where people drive on the right side of the road.
# This directly translates to checking if 'drives_right' column is True.

# 1. Create the boolean Series


drives_right_condition = cars['drives_right'] == True
# Or simply: drives_right_condition = cars['drives_right']

print(f"Boolean Series for 'drives_right':\n{drives_right_condition}\n")


# Output:
# 0 True
# 1 False
# 2 False
# 3 False
# 4 True
# 5 False
# Name: drives_right, dtype: bool

# 2. Apply the boolean Series to filter the DataFrame


right_hand_driving_countries = cars[drives_right_condition]
print(f"Countries that drive on the right:\n{right_hand_driving_countries}\n")
# Output:
# country drives_right
# 0 United States True
#4 Japan True

# Direct filtering
left_hand_driving_countries = cars[cars['drives_right'] == False]
print(f"Countries that drive on the left:\n{left_hand_driving_countries}\n")

62. Driving right (2)


Explanation

This topic continues the "Driving right" scenario, potentially adding more complexity to the
filtering, such as combining conditions or selecting specific columns after filtering. It's about
refining your data selection based on multiple criteria from the same dataset.

Syntax

(Combining boolean conditions using & or | on DataFrame Series)

Code Example
Python
import pandas as pd

# Re-using the 'cars' DataFrame


data = {
'country': ['United States', 'Canada', 'United Kingdom', 'Australia', 'Japan', 'India'],
'drives_right': [True, False, False, False, True, False],
'num_cars_per_capita': [800, 700, 500, 650, 600, 200] # Hypothetical data
}
cars = pd.DataFrame(data)
print(f"Original 'cars' DataFrame:\n{cars}\n")

# Scenario: Filter for countries where people drive on the right AND have a high number of cars
per capita (e.g., > 600).

# Condition 1: Drives on the right


condition_right_drive = cars['drives_right'] == True

# Condition 2: High number of cars per capita


condition_high_cars = cars['num_cars_per_capita'] > 600

# Combine conditions using '&' (element-wise AND)


filtered_countries = cars[condition_right_drive & condition_high_cars]
print(f"Countries driving right with > 600 cars per capita:\n{filtered_countries}\n")
# Output:
# country drives_right num_cars_per_capita
# 0 United States True 800
#4 Japan True 600 (wait, Japan should not be here, it is 600, not >600.
Let's make it 599 for demo purposes. Or change condition to >= 600)

# Let's adjust the data slightly or the condition to make a better point if desired.
# For example, if Japan had 601 cars per capita:
cars_adjusted = pd.DataFrame({
'country': ['United States', 'Canada', 'United Kingdom', 'Australia', 'Japan', 'India'],
'drives_right': [True, False, False, False, True, False],
'num_cars_per_capita': [800, 700, 500, 650, 601, 200]
})
# Now, condition_high_cars for adjusted data: cars_adjusted['num_cars_per_capita'] > 600
# Japan would be included.

# Alternative: Filter for countries that drive on the left OR have less than 500 cars per capita
condition_left_drive = cars['drives_right'] == False
condition_low_cars = cars['num_cars_per_capita'] < 500

filtered_countries_or = cars[condition_left_drive | condition_low_cars]


print(f"Countries driving left OR with < 500 cars per capita:\n{filtered_countries_or}\n")
# Output:
# country drives_right num_cars_per_capita
# 2 United Kingdom False 500
#5 India False 200
63. Cars per capita (1)
Explanation

This is another specific scenario, often used to demonstrate filtering on numerical columns in a
pandas DataFrame. "Cars per capita" is a continuous numerical variable, and you'd typically
filter it using comparison operators (>, <, >=, <=).

Syntax

(Similar to df[df['column'] > value])

Code Example
Python
import pandas as pd

# Re-using a cars DataFrame with 'cars_per_capita'


data = {
'country': ['United States', 'Canada', 'Germany', 'China', 'India', 'Brazil'],
'cars_per_capita': [800, 750, 650, 150, 50, 250],
'continent': ['North America', 'North America', 'Europe', 'Asia', 'Asia', 'South America']
}
cars_data = pd.DataFrame(data)
print(f"Original Cars Data:\n{cars_data}\n")

# Scenario: Identify countries with more than 500 cars per capita.

# Create the boolean Series


high_car_countries_condition = cars_data['cars_per_capita'] > 500
print(f"Boolean Series for > 500 cars per capita:\n{high_car_countries_condition}\n")

# Apply the filter


high_car_countries = cars_data[high_car_countries_condition]
print(f"Countries with > 500 cars per capita:\n{high_car_countries}\n")
# Output:
# country cars_per_capita continent
# 0 United States 800 North America
#1 Canada 750 North America
#2 Germany 650 Europe

# Find countries with very low cars per capita (e.g., less than 100)
low_car_countries = cars_data[cars_data['cars_per_capita'] < 100]
print(f"Countries with < 100 cars per capita:\n{low_car_countries}\n")

64. Cars per capita (2)


Explanation

This topic extends the "Cars per capita" scenario, likely by adding more complex filtering logic
involving multiple conditions or by selecting specific columns from the filtered DataFrame. It
reinforces the idea of combining boolean conditions to refine data selection.

Syntax

(Combining boolean conditions on DataFrame Series and selecting columns after filtering)

Code Example
Python
import pandas as pd

# Re-using the 'cars_data' DataFrame


data = {
'country': ['United States', 'Canada', 'Germany', 'China', 'India', 'Brazil'],
'cars_per_capita': [800, 750, 650, 150, 50, 250],
'continent': ['North America', 'North America', 'Europe', 'Asia', 'Asia', 'South America']
}
cars_data = pd.DataFrame(data)
print(f"Original Cars Data:\n{cars_data}\n")

# Scenario: Find countries in Asia with less than 200 cars per capita.

# Condition 1: Continent is Asia


condition_asia = cars_data['continent'] == 'Asia'

# Condition 2: Cars per capita is less than 200


condition_low_cars = cars_data['cars_per_capita'] < 200

# Combine using '&'


asia_low_cars_countries = cars_data[condition_asia & condition_low_cars]
print(f"Countries in Asia with < 200 cars per capita:\n{asia_low_cars_countries}\n")
# Output:
# country cars_per_capita continent
#3 China 150 Asia
#4 India 50 Asia

# Scenario: Find countries in North America OR with more than 700 cars per capita.
condition_north_america = cars_data['continent'] == 'North America'
condition_very_high_cars = cars_data['cars_per_capita'] > 700

north_america_or_very_high_cars = cars_data[condition_north_america |
condition_very_high_cars]
print(f"Countries in North America OR with > 700 cars per capita:\
n{north_america_or_very_high_cars}\n")
# Output:
# country cars_per_capita continent
# 0 United States 800 North America
#1 Canada 750 North America
#2 Germany 650 Europe (Germany was caught by >700, even though it's not
North America)

# Selecting specific columns from the filtered result


just_country_names = asia_low_cars_countries['country']
print(f"Just the names of countries in Asia with < 200 cars:\n{just_country_names}\n")
# Output:
# 3 China
# 4 India
# Name: country, dtype: object

65. while loop


Explanation

A while loop repeatedly executes a block of code as long as a specified condition remains
True. The loop continues until the condition becomes False or a break statement is
encountered. It's often used when the number of iterations is not known beforehand.

Syntax
Python
while condition:
# Code to execute as long as 'condition' is True
# Make sure something inside the loop changes the condition
# to eventually become False to avoid an infinite loop!
Code Example
Python
# Scenario: Count down from 5
count = 5
print("Countdown:")
while count > 0:
print(count)
count -= 1 # Decrement count by 1 (shorthand for count = count - 1)
print("Blast off!")
# Output:
# Countdown:
#5
#4
#3
#2
#1
# Blast off!

# Scenario: Accumulate sum until it exceeds a certain value


current_sum = 0
num = 1
target_sum = 10

print("\nAccumulating sum:")
while current_sum < target_sum:
print(f"Adding {num} to {current_sum}")
current_sum += num
num += 1
print(f"Final sum: {current_sum}")
# Output:
# Accumulating sum:
# Adding 1 to 0
# Adding 2 to 1
# Adding 3 to 3
# Adding 4 to 6
# Final sum: 10 (Loop stopped when sum became 10, which is not less than 10)

# Using break to exit loop early


i=0
print("\nLoop with break:")
while True: # Infinite loop condition
print(i)
if i >= 3:
break # Exit the loop when i is 3 or more
i += 1
# Output:
# Loop with break:
#0
#1
#2
#3

66. while: warming up


Explanation

This is a simple introductory example to get comfortable with the while loop structure. It
typically involves a very basic condition and a clear way to change the condition to eventually
terminate the loop, preventing an infinite loop.

Syntax

(Basic while loop syntax)

Code Example
Python
# Warmup Scenario: Print numbers from 1 to 3.

num = 1
print("Counting to 3:")
while num <= 3:
print(num)
num += 1 # Increment num by 1
print("Done counting.")
# Output:
# Counting to 3:
#1
#2
#3
# Done counting.

# Warmup Scenario 2: Repeat a phrase a few times.


repetitions = 0
max_repetitions = 2
print("\nRepeating a phrase:")
while repetitions < max_repetitions:
print("Hello from the loop!")
repetitions += 1
print("Finished repetitions.")
# Output:
# Repeating a phrase:
# Hello from the loop!
# Hello from the loop!
# Finished repetitions.

67. Basic while loop


Explanation

The basic while loop consists of the while keyword, a condition, and a colon, followed by an
indented block of code. The loop repeatedly checks the condition before each iteration. If the
condition is True, the block is executed. If False, the loop terminates. It's crucial to ensure that
the loop's body modifies one or more variables involved in the condition, otherwise, it will run
forever (an "infinite loop").

Syntax
Python
while condition_that_becomes_false:
# Code block
# Modify variables affecting 'condition_that_becomes_false'

Code Example
Python
# Scenario: Keep asking for user input until a valid number is entered.
user_input = ""
is_valid_number = False

while not is_valid_number:


try:
user_input = input("Please enter a number: ")
number = int(user_input) # Try converting input to an integer
is_valid_number = True # If conversion succeeds, set flag to True to exit loop
print(f"You entered: {number}")
except ValueError:
print("Invalid input. That's not a valid number. Please try again.")
# Example run:
# Please enter a number: abc
# Invalid input. That's not a valid number. Please try again.
# Please enter a number: 123
# You entered: 123

# Scenario: Simulate rolling a die until a 6 is rolled.


import random
roll = 0
attempts = 0

print("\nRolling a die until a 6 is rolled...")


while roll != 6:
roll = random.randint(1, 6) # Roll the die
attempts += 1
print(f"Roll {attempts}: {roll}")
print(f"It took {attempts} rolls to get a 6!")
# Example output (will vary):
# Rolling a die until a 6 is rolled...
# Roll 1: 3
# Roll 2: 5
# Roll 3: 1
# Roll 4: 6
# It took 4 rolls to get a 6!

68. Add conditionals (to while loop)


Explanation

You can embed if, elif, else statements inside a while loop to add more sophisticated
decision-making logic within each iteration. This allows the loop to perform different actions or
react differently based on conditions that change during the loop's execution, leading to more
dynamic and powerful programs.

Syntax
Python
while loop_condition:
# Code inside the loop
if inner_condition1:
# Do something specific
elif inner_condition2:
# Do something else
else:
# Default action

# Crucially, update loop_condition related variables here

Code Example
Python
# Scenario: Process a list of tasks, with special handling for certain tasks.
tasks = ["upload data", "clean data", "analyze data", "export report"]
processed_tasks = []
task_index = 0

print("Processing tasks:")
while task_index < len(tasks):
current_task = tasks[task_index]

if "clean" in current_task:
print(f"Special handling for: '{current_task}' - Applying data cleaning algorithm...")
elif "upload" in current_task:
print(f"Starting upload for: '{current_task}' - Checking network connection...")
else:
print(f"Processing regular task: '{current_task}'")

processed_tasks.append(current_task)
task_index += 1 # Move to the next task

print(f"\nAll tasks processed. Processed tasks: {processed_tasks}")


# Output:
# Processing tasks:
# Starting upload for: 'upload data' - Checking network connection...
# Special handling for: 'clean data' - Applying data cleaning algorithm...
# Processing regular task: 'analyze data'
# Processing regular task: 'export report'
#
# All tasks processed. Processed tasks: ['upload data', 'clean data', 'analyze data', 'export
report']

# Scenario: A game loop where player health decreases and game ends when health is zero.
player_health = 100
enemy_attacks = [10, 5, 20, 15, 30]
attack_num = 0
print("\nGame started!")
while player_health > 0 and attack_num < len(enemy_attacks):
damage = enemy_attacks[attack_num]
player_health -= damage
print(f"Enemy attacks for {damage} damage. Player health: {player_health}")

if player_health <= 0:
print("Player defeated! Game Over.")
elif player_health < 30:
print("Warning: Low health! Find health pack!")

attack_num += 1

if player_health > 0:
print("All enemies defeated. Player wins!")

69. for loop


Explanation

A for loop is used for iterating over a sequence (like a list, tuple, string, or range) or other
iterable objects. It executes a block of code once for each item in the sequence. for loops are
ideal when you know the number of iterations or when you need to process each item in a
collection.

Syntax
Python
for item in iterable:
# Code to execute for each 'item'

The item variable takes on the value of each element in the iterable during successive
iterations.

Code Example
Python
# 1. Loop over a list of numbers
numbers = [1, 2, 3, 4, 5]
print("Numbers in the list:")
for num in numbers:
print(num)
# Output:
# Numbers in the list:
#1
#2
#3
#4
#5

# 2. Loop over a string (iterates through characters)


my_word = "Python"
print("\nCharacters in 'Python':")
for char in my_word:
print(char)
# Output:
# Characters in 'Python':
#P
#y
#t
#h
#o
#n

# 3. Loop using range() (common for fixed number of iterations)


print("\nCounting with range(5):")
for i in range(5): # Generates numbers 0, 1, 2, 3, 4
print(i)
# Output:
# Counting with range(5):
#0
#1
#2
#3
#4

# Loop with conditions inside


temperatures = [25, 18, 30, 10, 22]
print("\nTemperature check:")
for temp in temperatures:
if temp > 20:
print(f"{temp}°C is warm.")
else:
print(f"{temp}°C is cool.")
# Output:
# Temperature check:
# 25°C is warm.
# 18°C is cool.
# 30°C is warm.
# 10°C is cool.
# 22°C is warm.

70. Loop over a list


Explanation

This is the most common use case for a for loop. You iterate through each element of a list,
performing some operation with each element. This is ideal for processing collections of data
where the order of elements matters.

Syntax
Python
for element_variable in my_list:
# Perform operations using element_variable

Code Example
Python
# A list of fruits
fruits = ["apple", "banana", "cherry", "date"]

print("My favorite fruits are:")


for fruit in fruits:
print(f"- {fruit}")
# Output:
# My favorite fruits are:
# - apple
# - banana
# - cherry
# - date

# Calculate the sum of numbers in a list


prices = [10.50, 5.00, 12.75, 8.25]
total_cost = 0

for price in prices:


total_cost += price # Add current price to total_cost

print(f"\nTotal cost of items: ${total_cost:.2f}") # Output: Total cost of items: $36.50

# Apply a discount
discounted_prices = []
for price in prices:
discounted_price = price * 0.9 # 10% discount
discounted_prices.append(discounted_price)

print(f"Original prices: {prices}")


print(f"Discounted prices: {discounted_prices}")

71. Indexes and values (1)


Explanation

Often, when looping over a list, you need both the value of the element and its index (its
position). Python's enumerate() function is perfect for this. It returns pairs of (index, value)
for each item in the iterable, allowing you to access both simultaneously in your loop.

Syntax
Python
for index, value in enumerate(my_list):
# Use index and value in the loop body

Code Example
Python
# List of tasks
tasks = ["Review report", "Schedule meeting", "Prepare presentation", "Send follow-up"]

print("Tasks with their index:")


for index, task in enumerate(tasks):
print(f"Task {index}: {task}")
# Output:
# Tasks with their index:
# Task 0: Review report
# Task 1: Schedule meeting
# Task 2: Prepare presentation
# Task 3: Send follow-up
# Scenario: Find the index of specific items
items = ["bread", "milk", "eggs", "cheese"]
item_to_find = "eggs"

print("\nFinding 'eggs':")
for i, item in enumerate(items):
if item == item_to_find:
print(f"'{item_to_find}' found at index {i}.") # Output: 'eggs' found at index 2.
break # Exit loop once found (optional)

72. Indexes and values (2)


Explanation

This continues the concept of accessing both indexes and values, perhaps with more complex
scenarios or demonstrating alternative (though less Pythonic) ways to achieve the same result
as enumerate(). It might also involve conditional logic based on both index and value.

Syntax

(Reinforcing enumerate() or demonstrating manual index tracking)

Code Example
Python
# Scenario: Update elements based on their index.
numbers = [10, 20, 30, 40, 50]

print("Original numbers:", numbers)

# Increase even-indexed numbers by 5


for idx, val in enumerate(numbers):
if idx % 2 == 0: # Check if index is even
numbers[idx] = val + 5 # Update the element in the original list
print(f"Numbers after updating even-indexed: {numbers}")
# Output: Numbers after updating even-indexed: [15, 20, 35, 40, 55]

# Alternative (less Pythonic) way to get index and value (discouraged in most cases)
print("\nManual index tracking:")
for i in range(len(numbers)):
print(f"Index {i}: Value {numbers[i]}")
# Output:
# Manual index tracking:
# Index 0: Value 15
# Index 1: Value 20
# Index 2: Value 35
# Index 3: Value 40
# Index 4: Value 55

# Scenario: Print elements and their indices, but only for elements meeting a condition
cities = ["London", "Paris", "Rome", "Berlin", "Madrid"]
print("\nCities that start with 'R':")
for index, city in enumerate(cities):
if city.startswith('R'):
print(f"Index {index}: {city}") # Output: Index 2: Rome

73. Loop over list of lists


Explanation

When you have a nested list (a list where elements are themselves lists), you can use nested
for loops to iterate through both the outer lists (rows) and the inner lists (elements within each
row). This is common for processing tabular data represented as lists of lists.

Syntax
Python
for outer_item in list_of_lists:
for inner_item in outer_item:
# Process inner_item

Or, if you know the structure of inner lists:

Python
for row in list_of_lists:
for col_val in row:
# Process col_val

Code Example
Python
# Student scores: [Name, Math, Science]
student_data = [
["Alice", 85, 90],
["Bob", 78, 82],
["Charlie", 92, 88]
]

print("Iterating through student data:")


for student_record in student_data:
print(f"Student: {student_record[0]}, Math: {student_record[1]}, Science: {student_record[2]}")
# Output:
# Iterating through student data:
# Student: Alice, Math: 85, Science: 90
# Student: Bob, Math: 78, Science: 82
# Student: Charlie, Math: 92, Science: 88

# Using nested loops to access each individual score


print("\nIndividual scores:")
for row_index, student_record in enumerate(student_data):
student_name = student_record[0]
for col_index, score in enumerate(student_record[1:]): # Iterate over scores only
subject = ""
if col_index == 0: subject = "Math"
elif col_index == 1: subject = "Science"
print(f" {student_name}'s {subject} score: {score}")
# Output:
# Individual scores:
# Alice's Math score: 85
# Alice's Science score: 90
# Bob's Math score: 78
# Bob's Science score: 82
# Charlie's Math score: 92
# Charlie's Science score: 88

# Calculate average score for each student


print("\nAverage scores per student:")
for student_record in student_data:
name = student_record[0]
math_score = student_record[1]
science_score = student_record[2]
average = (math_score + science_score) / 2
print(f"{name}: Average = {average:.2f}")
# Output:
# Average scores per student:
# Alice: Average = 87.50
# Bob: Average = 80.00
# Charlie: Average = 90.00

74. Loop Data Structures Part 1


Explanation

This topic generalizes looping to other fundamental Python data structures beyond lists. It
focuses on dictionaries and NumPy arrays, demonstrating how for loops adapt to their specific
structures.

Syntax

● Loop over dictionary:


○ for key in my_dict: (iterates over keys)
○ for value in my_dict.values(): (iterates over values)
○ for key, value in my_dict.items(): (iterates over key-value pairs)
● Loop over NumPy array:
○ for element in np_array: (iterates over elements for 1D, or rows for 2D)

Code Example

(Detailed in subsequent specific topics)

75. Loop over dictionary


Explanation

Dictionaries are unordered collections of key-value pairs. When you loop directly over a
dictionary using a for loop, it iterates over its keys by default. However, you can use dictionary
methods (.keys(), .values(), .items()) to iterate explicitly over keys, values, or both.

Syntax
Python
# Iterating over keys (default)
for key in my_dictionary:
# Use key: my_dictionary[key]
# Iterating over values
for value in my_dictionary.values():
# Use value

# Iterating over key-value pairs (most common and recommended)


for key, value in my_dictionary.items():
# Use key and value

Code Example
Python
# A dictionary of product prices
product_prices = {
"Laptop": 1200,
"Mouse": 25,
"Keyboard": 75,
"Monitor": 300
}

print("Iterating over dictionary keys (default):")


for product in product_prices: # Iterates over keys
print(product)
# Output:
# Laptop
# Mouse
# Keyboard
# Monitor

print("\nIterating over dictionary values:")


for price in product_prices.values():
print(price)
# Output:
# 1200
# 25
# 75
# 300

print("\nIterating over dictionary items (key-value pairs):")


for product, price in product_prices.items():
print(f"{product}: ${price}")
# Output:
# Laptop: $1200
# Mouse: $25
# Keyboard: $75
# Monitor: $300

# Scenario: Calculate total value of items


total_value = 0
for price in product_prices.values():
total_value += price
print(f"\nTotal value of all products: ${total_value}") # Output: Total value of all products: $1600

76. Loop over NumPy array


Explanation

When looping over a NumPy array with a for loop:

● For a 1D array, it iterates directly over each element.


● For a 2D array, it iterates over each row (which itself is a 1D array). To iterate over
individual elements in a 2D array, you'd typically use nested loops or vectorized
operations (which are much faster and preferred).

While loops are possible, for iterating through elements, NumPy's vectorized operations are
almost always more efficient than Python for loops.

Syntax
Python
for element in 1d_numpy_array:
# Process element

for row in 2d_numpy_array:


# Process row (which is a 1D array)
# for element in row: # Nested loop for individual elements
# Process element

Code Example
Python
import numpy as np

# 1. Loop over a 1D NumPy array


data_1d = np.array([10, 20, 30, 40, 50])
print("Looping over 1D NumPy array:")
for value in data_1d:
print(value)
# Output:
# 10
# 20
# 30
# 40
# 50

# 2. Loop over a 2D NumPy array (iterates row by row)


data_2d = np.array([
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
])
print("\nLooping over 2D NumPy array (row by row):")
for row in data_2d:
print(row)
# Output:
# [1 2 3]
# [4 5 6]
# [7 8 9]

print("\nLooping over 2D NumPy array (element by element using nested loop):")


for row in data_2d:
for element in row:
print(element, end=" ")
print() # Newline after each row
# Output:
#123
#456
#789

# Important: Prefer vectorized operations over loops for efficiency with NumPy!
# Example: Adding 10 to all elements in data_1d
data_1d_plus_10 = data_1d + 10 # This is much faster than a loop
print(f"\nVectorized addition: {data_1d_plus_10}")

77. Loop Data Structures Part 2


Explanation
This topic continues the exploration of looping through more advanced data structures,
specifically pandas DataFrames. While for loops can be used, pandas provides more
optimized (and often more "Pythonic") ways to iterate or apply operations across DataFrames,
such as iterrows() for rows and apply() for more complex element/column-wise operations.

Syntax

● Loop over DataFrame rows: for index, row in df.iterrows():


● Loop over DataFrame columns: for column_name in df.columns: (then use
df[column_name])

Code Example

(Detailed in subsequent specific topics)

78. Loop over DataFrame (1)


Explanation

Iterating over a pandas DataFrame row by row is a common task. The DataFrame.iterrows()
method is the standard and most explicit way to do this. For each iteration, iterrows() yields a
tuple containing the index of the row and the row itself as a Series object.

While direct iteration with for loops is possible, it's generally discouraged for large DataFrames
due to performance reasons. Vectorized operations (NumPy or pandas built-in functions) are
almost always faster. However, iterrows() is useful when you need to access both the index
and the data of each row, or when row-by-row logic is unavoidable.

Syntax
Python
import pandas as pd
for index, row in df.iterrows():
# 'index' is the row index (e.g., 0, 1, 2...)
# 'row' is a pandas Series representing the current row
# You can access elements of the row using row['column_name']

Code Example
Python
import pandas as pd
# Create a sample DataFrame
data = {
'Name': ['Alice', 'Bob', 'Charlie'],
'Age': [25, 30, 22],
'City': ['New York', 'London', 'Paris']
}
df = pd.DataFrame(data)
print(f"Original DataFrame:\n{df}\n")

# Loop over DataFrame rows using iterrows()


print("Iterating over DataFrame rows:")
for index, row in df.iterrows():
print(f"Index: {index}")
print(f" Name: {row['Name']}, Age: {row['Age']}, City: {row['City']}")
print("-" * 20)
# Output:
# Iterating over DataFrame rows:
# Index: 0
# Name: Alice, Age: 25, City: New York
# --------------------
# Index: 1
# Name: Bob, Age: 30, City: London
# --------------------
# Index: 2
# Name: Charlie, Age: 22, City: Paris
# --------------------

# Scenario: Print a greeting for each person


print("\nGreetings for each person:")
for idx, person_data in df.iterrows():
print(f"Hello, {person_data['Name']} from {person_data['City']}!")
# Output:
# Greetings for each person:
# Hello, Alice from New York!
# Hello, Bob from London!
# Hello, Charlie from Paris!

79. Loop over DataFrame (2)


Explanation
This continues the DataFrame looping, potentially demonstrating more complex operations
within the loop, such as conditional logic applied to each row, or performing calculations that
involve multiple columns of the current row. It reinforces the power of iterrows() for row-wise
processing when vectorized solutions are not immediately obvious or sufficient.

Syntax

(Nested logic within the for index, row in df.iterrows(): structure)

Code Example
Python
import pandas as pd

# Create a sample DataFrame with sales data


sales_data = {
'Product': ['A', 'B', 'C', 'A', 'B'],
'Price': [100, 50, 200, 105, 55],
'Quantity': [2, 3, 1, 1, 2],
'Region': ['East', 'West', 'East', 'Central', 'West']
}
df_sales = pd.DataFrame(sales_data)
print(f"Original Sales DataFrame:\n{df_sales}\n")

# Scenario: Calculate total value for each sale and apply a regional discount.
# Add a new 'Total Value' column
df_sales['Total Value'] = 0.0 # Initialize column

print("Processing sales records with regional discounts:")


for index, row in df_sales.iterrows():
product = row['Product']
price = row['Price']
quantity = row['Quantity']
region = row['Region']

# Calculate base value


base_value = price * quantity
final_value = base_value

# Apply discount based on region


if region == 'East':
final_value *= 0.95 # 5% discount for East region
print(f"Sale {index}: {product} in {region} - Base: ${base_value}, Discounted (East): $
{final_value:.2f}")
elif region == 'West':
final_value *= 0.90 # 10% discount for West region
print(f"Sale {index}: {product} in {region} - Base: ${base_value}, Discounted (West): $
{final_value:.2f}")
else:
print(f"Sale {index}: {product} in {region} - Base: ${base_value}, No special discount: $
{final_value:.2f}")

# Update the 'Total Value' column for the current row


df_sales.loc[index, 'Total Value'] = final_value

print(f"\nDataFrame after calculating discounted Total Value:\n{df_sales}\n")


# Output will show the calculations and then the updated DataFrame.

# IMPORTANT: For adding/modifying columns, vectorized operations are usually faster:


# Example: Calculate total value using vectorized operation
# df_sales['Calculated Total Value'] = df_sales['Price'] * df_sales['Quantity']
# print(f"Vectorized Total Value:\n{df_sales}")

80. Add column (1)


Explanation

Adding a new column to a pandas DataFrame is a very common operation in data


manipulation. It's typically done by assigning a Series (which can be derived from existing
columns or created from a list/NumPy array) to a new column name within the DataFrame. This
operation is efficient and often done using vectorized arithmetic.

Syntax
Python
df['new_column_name'] = value_or_series

If value_or_series is a single value, it will be broadcasted to all rows. If it's a Series or array, its
length must match the DataFrame's number of rows.

Code Example
Python
import pandas as pd

# Create a sample DataFrame


data = {
'Name': ['Alice', 'Bob', 'Charlie'],
'Math_Score': [85, 78, 92],
'Science_Score': [90, 82, 88]
}
students_df = pd.DataFrame(data)
print(f"Original DataFrame:\n{students_df}\n")

# 1. Add a new column with a single constant value


students_df['Class'] = 'Grade 10'
print(f"After adding 'Class' column (constant value):\n{students_df}\n")
# Output:
# Name Math_Score Science_Score Class
# 0 Alice 85 90 Grade 10
#1 Bob 78 82 Grade 10
# 2 Charlie 92 88 Grade 10

# 2. Add a new column based on a calculation from existing columns (vectorized)


# Calculate 'Average_Score'
students_df['Average_Score'] = (students_df['Math_Score'] + students_df['Science_Score']) / 2
print(f"After adding 'Average_Score' column:\n{students_df}\n")
# Output:
# Name Math_Score Science_Score Class Average_Score
# 0 Alice 85 90 Grade 10 87.5
#1 Bob 78 82 Grade 10 80.0
# 2 Charlie 92 88 Grade 10 90.0

# 3. Add a column from a list (must match DataFrame length)


status_list = ['Pass', 'Fail', 'Pass']
students_df['Pass_Status'] = status_list
print(f"After adding 'Pass_Status' column from a list:\n{students_df}\n")

81. Add column (2)


Explanation

This topic further explores adding columns, possibly demonstrating more complex logic for
column creation, such as using conditional statements to derive values for the new column (e.g.,
categorizing data), or using the .apply() method for more intricate row-wise or element-wise
transformations when vectorized solutions are not straightforward.

Syntax
● Using .apply(): df['new_col'] = df.apply(function_name, axis=1) (for row-wise)
● Using np.where() for conditional column creation: df['new_col'] =
np.where(condition, value_if_true, value_if_false)

Code Example
Python
import pandas as pd
import numpy as np # Often used with conditional logic for DataFrames

# Re-using the students_df from previous example


data = {
'Name': ['Alice', 'Bob', 'Charlie', 'David', 'Eve'],
'Math_Score': [85, 78, 92, 65, 88],
'Science_Score': [90, 82, 88, 70, 91]
}
students_df = pd.DataFrame(data)
students_df['Average_Score'] = (students_df['Math_Score'] + students_df['Science_Score']) / 2
print(f"DataFrame before adding new conditional columns:\n{students_df}\n")

# 1. Add a new column based on a condition using np.where() (highly recommended for
element-wise conditionals)
# If Average_Score >= 80, 'Excellent', else 'Good'
students_df['Performance_Level'] = np.where(students_df['Average_Score'] >= 85, 'Excellent',
'Good')
print(f"After adding 'Performance_Level' with np.where():\n{students_df}\n")
# Output:
# Name Math_Score Science_Score Average_Score Performance_Level
# 0 Alice 85 90 87.5 Excellent
#1 Bob 78 82 80.0 Good
# 2 Charlie 92 88 90.0 Excellent
# 3 David 65 70 67.5 Good
#4 Eve 88 91 89.5 Excellent

# 2. Add a new column using a custom function with .apply()


# This is useful when the logic is too complex for simple vectorized operations or np.where()
def get_grade(avg_score):
if avg_score >= 90:
return 'A'
elif avg_score >= 80:
return 'B'
elif avg_score >= 70:
return 'C'
else:
return 'F'
students_df['Grade'] = students_df['Average_Score'].apply(get_grade)
print(f"After adding 'Grade' column using .apply():\n{students_df}\n")
# Output:
# Name Math_Score Science_Score Average_Score Performance_Level Grade
# 0 Alice 85 90 87.5 Excellent B
#1 Bob 78 82 80.0 Good B
# 2 Charlie 92 88 90.0 Excellent A
# 3 David 65 70 67.5 Good F
#4 Eve 88 91 89.5 Excellent B

# Using apply with axis=1 for row-wise logic (e.g., combine name and city)
def create_summary(row):
return f"{row['Name']} ({row['City']})"

# Assuming 'City' column exists from an earlier example, let's add it for this demo:
students_df['City'] = ['New York', 'London', 'Paris', 'New York', 'London']
students_df['Summary'] = students_df.apply(create_summary, axis=1)
print(f"After adding 'Summary' column using .apply(axis=1):\n{students_df}\n")
# Output:
# Name Math_Score Science_Score Average_Score Performance_Level Grade City
Summary
# 0 Alice 85 90 87.5 Excellent B New York Alice (New York)
#1 Bob 78 82 80.0 Good B London Bob (London)
# 2 Charlie 92 88 90.0 Excellent A Paris Charlie (Paris)
# 3 David 65 70 67.5 Good F New York David (New York)
#4 Eve 88 91 89.5 Excellent B Lon

You might also like