W2
Beginner 3 sessions • 6 hours Python

Week 2: Data Structures, NumPy and Vectorised Computing

.ipynb
Follow along in JupyterDownload the complete Week 2 notebook — every code example ready to run.
Download Notebook

Data Science Fundamentals Course

Week 2 Overview

Week 2 builds on your Python fundamentals by introducing data structures that are essential for organizing and manipulating data. You will learn how to work with collections of data using lists, tuples, dictionaries, and sets. Additionally, you will learn how to read and write data to files, which is crucial for data science work. Finally, we will cover exception handling to make your programs more robust. By the end of Week 2, you will be able to:

  • Create and manipulate lists, tuples, dictionaries, and sets
  • Understand the differences between mutable and immutable data structures
  • Work with strings: indexing, slicing, and common methods
  • Read data from files in various formats
  • Write data to files
  • Handle errors with try-except blocks
  • Understand when to use each data structure

Week 2 is divided into three 2-hour sessions:

  • Session 1: Lists, Tuples, and Common Sequence Operations
  • Session 2: Dictionaries, Sets, and String Manipulation
  • Session 3: File Handling and Exception Handling

SESSION 1: Lists, Tuples, and Common Sequence Operations

Duration: 2 hours

1.1 Lists: Creating and Understanding

A list is an ordered, mutable collection of items. "Mutable" means you can change, add, or remove items after creating the list. Lists are one of the most commonly used data structures in Python and essential for data science. Key characteristics of lists:

  • Ordered: Items have a specific position (index)
  • Mutable: You can modify items after creation
  • Heterogeneous: Can contain different data types
  • Dynamic: Can grow or shrink as needed

Creating Lists

# Empty list
empty_list = []
print(empty_list) # Output: []

# List with initial values
numbers = [1, 2, 3, 4, 5]
print(numbers) # Output: [1, 2, 3, 4, 5]

# List with different data types
mixed_list = [1, "Alice", 3.14, True, None]
print(mixed_list)
# Output: [1, 'Alice', 3.14, True, None]

# List with repeated values
zeros = [0] * 5
print(zeros) # Output: [0, 0, 0, 0, 0]

# Convert other types to list
string_to_list = list("ABC")
print(string_to_list) # Output: ['A', 'B', 'C']

# List of lists (nested)
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
print(matrix)
# Output: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

Accessing List Elements (Indexing)

Lists use zero-based indexing, meaning the first element is at index 0.

fruits = ["apple", "banana", "cherry", "date", "elderberry"]

# Access by positive index (from beginning)
print(fruits[0]) # Output: apple
print(fruits[1]) # Output: banana
print(fruits[4]) # Output: elderberry

# Access by negative index (from end)
print(fruits[-1]) # Output: elderberry (last item)
print(fruits[-2]) # Output: date (second to last)
print(fruits[-5]) # Output: apple (first item)

# Access nested list element
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
print(matrix[0]) # Output: [1, 2, 3]
print(matrix[0][1]) # Output: 2
print(matrix[2][2]) # Output: 9

# Get list length
print(len(fruits)) # Output: 5
print(len(matrix)) # Output: 3

Slicing Lists

Slicing extracts a portion of a list using the syntax [start:stop:step].

numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

# Basic slicing [start:stop] - stop is excluded
print(numbers[2:5]) # Output: [2, 3, 4]
print(numbers[0:3]) # Output: [0, 1, 2]

# From beginning to index
print(numbers[:4]) # Output: [0, 1, 2, 3]

# From index to end
print(numbers[7:]) # Output: [7, 8, 9]

# With step
print(numbers[::2]) # Every 2nd element: [0, 2, 4, 6, 8]
print(numbers[1::2]) # Every 2nd element starting at index 1: [1, 3, 5, 7, 9]

# Reverse a list
print(numbers[::-1]) # Output: [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

# Slice with negative indices
print(numbers[-3:]) # Last 3 elements: [7, 8, 9]
print(numbers[:-2]) # All except last 2: [0, 1, 2, 3, 4, 5, 6, 7]

# Real-world: Get first 10 items from a long list
data = list(range(1000)) # List of 0-999
first_10 = data[:10]
print(first_10) # Output: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Modifying Lists

Since lists are mutable, you can change, add, or remove elements.

numbers = [1, 2, 3, 4, 5]

# Change a single element
numbers[2] = 30
print(numbers) # Output: [1, 2, 30, 4, 5]

# Change multiple elements with slicing
numbers[1:3] = [20, 300]
print(numbers) # Output: [1, 20, 300, 4, 5]

# Add single element at end
numbers.append(6)
print(numbers) # Output: [1, 20, 300, 4, 5, 6]

# Add multiple elements
numbers.extend([7, 8, 9])
print(numbers) # Output: [1, 20, 300, 4, 5, 6, 7, 8, 9]

# Insert at specific position
numbers.insert(0, 0) # Insert 0 at beginning
print(numbers) # Output: [0, 1, 20, 300, 4, 5, 6, 7, 8, 9]

# Remove by value
numbers.remove(20)
print(numbers) # Output: [0, 1, 300, 4, 5, 6, 7, 8, 9]

# Remove by index
del numbers[2]
print(numbers) # Output: [0, 1, 4, 5, 6, 7, 8, 9]

# Pop removes and returns element at index
last = numbers.pop() # Remove last element
print(last) # Output: 9
print(numbers) # Output: [0, 1, 4, 5, 6, 7, 8]

# Clear all elements
numbers.clear()
print(numbers) # Output: []

Common List Methods

# index() and count()
fruits = ["apple", "banana", "cherry", "banana"]
print(fruits.index("banana")) # Output: 1 (first position)
print(fruits.count("banana")) # Output: 2

# sort()
numbers = [3, 1, 4, 1, 5, 9, 2, 6]
numbers.sort()
print(numbers) # Output: [1, 1, 2, 3, 4, 5, 6, 9]

# reverse()
numbers.reverse()
print(numbers) # Output: [9, 6, 5, 4, 3, 2, 1, 1]

# copy() - important for avoiding unintended changes
original = [1, 2, 3]
copy_list = original.copy()
copy_list[0] = 99
print(original) # Output: [1, 2, 3] (unchanged)
print(copy_list) # Output: [99, 2, 3]

1.2 Tuples: Immutable Sequences

A tuple is an ordered, immutable collection of items. "Immutable" means once created, you cannot change, add, or remove elements. Tuples are useful when you want to ensure data doesn't change accidentally. Key characteristics:

  • Ordered: Items have specific positions
  • Immutable: Cannot modify after creation
  • Lightweight: Often faster and use less memory than lists
  • Hashable: Can be used as dictionary keys (more on this later)
# Create tuples
empty_tuple = ()
print(empty_tuple) # Output: ()

single_item = (1,) # Note the comma - important!
print(single_item) # Output: (1,)

coordinates = (10, 20, 30)
print(coordinates) # Output: (10, 20, 30)

mixed_tuple = (1, "Alice", 3.14, True)
print(mixed_tuple) # Output: (1, 'Alice', 3.14, True)

# Tuple unpacking
x, y, z = coordinates
print(x, y, z) # Output: 10 20 30

# Accessing elements (same as lists)
print(coordinates[0]) # Output: 10
print(coordinates[-1]) # Output: 30

# Slicing (same as lists)
print(coordinates[1:]) # Output: (20, 30)

# Tuple methods
numbers = (1, 2, 3, 2, 4, 2)
print(numbers.count(2)) # Output: 3
print(numbers.index(3)) # Output: 2

# Converting between list and tuple
list_data = [1, 2, 3]
tuple_data = tuple(list_data)
print(tuple_data) # Output: (1, 2, 3)

# Back to list
list_again = list(tuple_data)
print(list_again) # Output: [1, 2, 3]

# Real-world: Return multiple values as tuple
def get_user_info():
name = "Alice"
age = 25
city = "Lagos"
return name, age, city # Returns tuple

user = get_user_info()
print(user) # Output: ('Alice', 25, 'Lagos')
name, age, city = get_user_info()
print(f"{name} is {age} and lives in {city}")

When to Use Lists vs Tuples

Use Lists when:

  • You need to modify the data (add, remove, change elements)
  • You're working with a collection that might grow or shrink
  • You need performance for frequently modified data

Use Tuples when:

  • Data should not change (immutable protection)
  • Using the data as dictionary keys (lists cannot be keys)
  • Returning multiple values from functions
  • Memory efficiency is important
  • You want to prevent accidental modifications

SESSION 2: Dictionaries, Sets, and String Manipulation

Duration: 2 hours

2.1 Dictionaries: Key-Value Pairs

A dictionary is a mutable, unordered collection of key-value pairs. Instead of accessing items by position like lists, dictionaries use keys to access values. Dictionaries are incredibly useful for organizing structured data. Key characteristics:

  • Unordered: Order of items doesn't matter (in older Python)
  • Mutable: Can add, remove, or change items
  • Key-value pairs: Each value is associated with a unique key
  • Keys must be unique and immutable (usually strings or numbers)
# Create dictionaries
empty_dict = {}
print(empty_dict) # Output: {}

# Dictionary with initial values
student = {"name": "Alice", "age": 25, "grade": "A"}
print(student)
# Output: {'name': 'Alice', 'age': 25, 'grade': 'A'}

# Dictionary with mixed key types
mixed = {1: "one", "two": 2, 3.0: "three"}
print(mixed)
# Output: {1: 'one', 'two': 2, 3.0: 'three'}

# Accessing values by key
print(student["name"]) # Output: Alice
print(student["age"]) # Output: 25

# Using get() method (safer than direct access)
print(student.get("name")) # Output: Alice
print(student.get("email", "N/A")) # Output: N/A (key doesn't exist)

# Adding new key-value pairs
student["email"] = "alice@example.com"
print(student)
# Output: {'name': 'Alice', 'age': 25, 'grade': 'A', 'email': 'alice@example.com'}

# Modifying existing values
student["age"] = 26
print(student)
# Output: {'name': 'Alice', 'age': 26, 'grade': 'A', 'email': 'alice@example.com'}

# Removing key-value pairs
del student["grade"]
print(student)

# pop() removes and returns value
email = student.pop("email")
print(email) # Output: alice@example.com

# Clear all items
student.clear()
print(student) # Output: {}

Dictionary Methods

person = {"name": "Bob", "age": 30, "city": "Lagos"}

# keys(), values(), items()
print(person.keys()) # Output: dict_keys(['name', 'age', 'city'])
print(person.values()) # Output: dict_values(['Bob', 30, 'Lagos'])
print(person.items()) # Output: dict_items([('name', 'Bob'), ('age', 30), ('city', 'Lagos')])

# Iterating through dictionary
for key in person:
print(f"{key}: {person[key]}")

for key, value in person.items():
print(f"{key}: {value}")

# update()
person.update({"age": 31, "email": "bob@example.com"})
print(person)
# Output: {'name': 'Bob', 'age': 31, 'city': 'Lagos', 'email': 'bob@example.com'}

# Real-world: Store student grades
grades = {
"Alice": 85,
"Bob": 92,
"Charlie": 78,
"Diana": 95
}

# Find student with highest grade
top_student = max(grades, key=grades.get)
print(f"Top student: {top_student} with grade {grades[top_student]}")
# Output: Top student: Diana with grade 95

Nested Dictionaries

# Dictionary containing dictionaries
company = {
"employees": {
"emp1": {"name": "Alice", "dept": "Data Science", "salary": 60000},
"emp2": {"name": "Bob", "dept": "Engineering", "salary": 70000},
"emp3": {"name": "Charlie", "dept": "Sales", "salary": 50000}
},
"departments": ["Data Science", "Engineering", "Sales"],
"founded": 2020
}

# Accessing nested values
print(company["employees"]["emp1"]["name"]) # Output: Alice
print(company["employees"]["emp2"]["salary"]) # Output: 70000

# Iterating through nested structure
for emp_id, emp_info in company["employees"].items():
print(f"{emp_id}: {emp_info['name']} - {emp_info['dept']}")

# Real-world: Store customer data
customers = {
"cust001": {
"name": "Alice Smith",
"email": "alice@example.com",
"purchases": [100, 200, 150]
},
"cust002": {
"name": "Bob Jones",
"email": "bob@example.com",
"purchases": [300, 250]
}
}

# Calculate total spent by customer
for cust_id, cust_data in customers.items():
total = sum(cust_data["purchases"])
print(f"{cust_data['name']} spent: {total}")

2.2 Sets: Unique Collections

A set is an unordered collection of unique items. Sets are useful when you need to:

  • Remove duplicates from a collection
  • Test membership (is an item in the set?)
  • Perform mathematical set operations (union, intersection, difference)

Key characteristics:

  • Unordered: Order doesn't matter
  • Unique: Duplicates are automatically removed
  • Mutable: Can add and remove items
  • Cannot contain unhashable types (like lists or dicts)
# Create sets
empty_set = set() # Note: {} creates dict, not set
print(empty_set) # Output: set()

numbers = {1, 2, 3, 4, 5}
print(numbers) # Output: {1, 2, 3, 4, 5}

# Create from list
duplicates = [1, 2, 2, 3, 3, 3, 4]
unique = set(duplicates)
print(unique) # Output: {1, 2, 3, 4}

# Adding elements
numbers.add(6)
print(numbers) # Output: {1, 2, 3, 4, 5, 6}

# Adding multiple elements
numbers.update([7, 8, 9])
print(numbers) # Output: {1, 2, 3, 4, 5, 6, 7, 8, 9}

# Removing elements
numbers.remove(9) # Raises error if not found
print(numbers)

# discard() - doesn't raise error if not found
numbers.discard(10) # No error even though 10 isn't in set
numbers.discard(8)
print(numbers)

# Membership testing
print(5 in numbers) # Output: True
print(10 in numbers) # Output: False

# Set operations
set_a = {1, 2, 3, 4}
set_b = {3, 4, 5, 6}

# Union: all elements from both sets
union = set_a | set_b
print(union) # Output: {1, 2, 3, 4, 5, 6}

# Intersection: elements in both sets
intersection = set_a & set_b
print(intersection) # Output: {3, 4}

# Difference: elements in set_a but not in set_b
difference = set_a - set_b
print(difference) # Output: {1, 2}

# Real-world: Find common programming languages between developers
dev1_languages = {"Python", "JavaScript", "Java", "C++"}
dev2_languages = {"Python", "Ruby", "Java", "Go"}

common = dev1_languages & dev2_languages
print(f"Languages they both know: {common}")
# Output: Languages they both know: {'Java', 'Python'}

2.3 String Manipulation

Strings are immutable sequences of characters. Understanding string operations is essential for data science, especially for cleaning and processing text data.

String Indexing and Slicing

text = "Data Science"

# Indexing
print(text[0]) # Output: D
print(text[5]) # Output: S
print(text[-1]) # Output: e

# Slicing
print(text[0:4]) # Output: Data
print(text[5:]) # Output: Science
print(text[:4]) # Output: Data
print(text[::2]) # Every 2nd character: DtSine
print(text[::-1]) # Reverse: ecneicS ataD

# String length
print(len(text)) # Output: 12

Common String Methods

text = " Hello, World! "

# Case operations
print(text.upper()) # Output: HELLO, WORLD!
print(text.lower()) # Output: hello, world!
print(text.capitalize()) # Output: hello, world! (weird!)
print(text.title()) # Output: Hello, World!

# strip() removes whitespace
clean = text.strip()
print(clean) # Output: Hello, World!

# replace()
message = "I like apples. Apples are great."
new_message = message.replace("apples", "oranges")
print(new_message)
# Output: I like oranges. Apples are great.

# split() and join()
sentence = "Python is awesome"
words = sentence.split()
print(words) # Output: ['Python', 'is', 'awesome']

rejoined = " ".join(words)
print(rejoined) # Output: Python is awesome

# find()
text = "Hello, World!"
index = text.find("World")
print(index) # Output: 7

# startswith() and endswith()
print(sentence.startswith("Python")) # Output: True
print(sentence.endswith("awesome")) # Output: True

# Type checking
print("123".isdigit()) # Output: True
print("abc".isalpha()) # Output: True
print("12a".isdigit()) # Output: False

# Real-world: Process CSV-like data
csv_line = "Alice, 25, Lagos, Data Science"
fields = [field.strip() for field in csv_line.split(",")]
print(fields)
# Output: ['Alice', '25', 'Lagos', 'Data Science']

String Formatting

# f-strings (Python 3.6+) - Recommended
name = "Alice"
age = 25
city = "Lagos"

print(f"Name: {name}, Age: {age}, City: {city}")
# Output: Name: Alice, Age: 25, City: Lagos

# With expressions in f-strings
x = 10
y = 20
print(f"Sum of {x} and {y} is {x + y}")
# Output: Sum of 10 and 20 is 30

# Formatting numbers
pi = 3.14159
print(f"Pi rounded: {pi:.2f}") # Output: Pi rounded: 3.14
print(f"Percentage: {0.875:.1%}") # Output: Percentage: 87.5%

# format() method
template = "Hello {}, you are {} years old"
message = template.format("Bob", 30)
print(message) # Output: Hello Bob, you are 30 years old

# Older % formatting (still seen in legacy code)
message = "Hello %s, you are %d years old" % ("Charlie", 28)
print(message) # Output: Hello Charlie, you are 28 years old

# Real-world: Create formatted output for report
data = {"name": "Diana", "score": 92.5, "percentile": 0.95}
report = f"""
Student Report:
Name: {data['name']}
Score: {data['score']:.1f}
Percentile: {data['percentile']:.0%}
"""
print(report)

SESSION 3: File Handling and Exception Handling

Duration: 2 hours

3.1 Reading and Writing Files

File handling is essential for data science. You need to read data from files and save results. Python provides simple methods to work with files.

Opening and Closing Files

# Basic file operations
# Modes: 'r' (read), 'w' (write), 'a' (append), 'rb' (read binary), etc.

# Method 1: Manual close (not recommended)
file = open("data.txt", "r")
content = file.read()
file.close()

# Method 2: with statement (RECOMMENDED)
# Automatically closes file even if error occurs
with open("data.txt", "r") as file:
content = file.read()

print(content)

# After 'with' block, file is automatically closed

Reading Files

# Create a sample file first
with open("sample.txt", "w") as f:
f.write("Line 1: Hello\n")
f.write("Line 2: World\n")
f.write("Line 3: Python")

# Read entire file as string
with open("sample.txt", "r") as f:
content = f.read()
print(content)

# Output:
# Line 1: Hello
# Line 2: World
# Line 3: Python

# Read one line at a time
with open("sample.txt", "r") as f:
line1 = f.readline()
line2 = f.readline()
print(line1) # Output: Line 1: Hello
print(line2) # Output: Line 2: World

# Read all lines as list
with open("sample.txt", "r") as f:
lines = f.readlines()
print(lines)
# Output: ['Line 1: Hello\n', 'Line 2: World\n', 'Line 3: Python']

# Iterate through lines (memory efficient for large files)
with open("sample.txt", "r") as f:
for line in f:
print(line.strip()) # strip() removes newline character

# Output:
# Line 1: Hello
# Line 2: World
# Line 3: Python

Writing and Appending Files

# Write mode: overwrites existing file
with open("output.txt", "w") as f:
f.write("First line\n")
f.write("Second line\n")

# Append mode: adds to end of file
with open("output.txt", "a") as f:
f.write("Third line (appended)\n")

# Write multiple lines at once
lines = ["Data Science\n", "Machine Learning\n", "Artificial Intelligence\n"]

with open("output.txt", "w") as f:
f.writelines(lines)

# Read back what we wrote
with open("output.txt", "r") as f:
content = f.read()
print(content)

# Output:
# Data Science
# Machine Learning
# Artificial Intelligence

# Real-world: Save analysis results
results = [
"Analysis Results\n",
"==================\n",
"Total records: 1000\n",
"Valid records: 950\n",
"Invalid records: 50\n"
]

with open("results.txt", "w") as f:
f.writelines(results)

Working with CSV Files

# Create sample CSV file
csv_content = """name,age,city
Alice,25,Lagos
Bob,30,Accra
Charlie,28,Nairobi
Diana,32,Johannesburg"""

with open("people.csv", "w") as f:
f.write(csv_content)

# Read CSV manually (without pandas)
with open("people.csv", "r") as f:
# Skip header
header = f.readline().strip().split(",")
print("Header:", header) # Output: Header: ['name', 'age', 'city']

# Read data rows
for line in f:
fields = line.strip().split(",")
name, age, city = fields
print(f"{name}: {age} years old, from {city}")

# Output:
# Alice: 25 years old, from Lagos
# Bob: 30 years old, from Accra
# Charlie: 28 years old, from Nairobi
# Diana: 32 years old, from Johannesburg

# Better: Use list comprehension to parse CSV
def read_csv(filename):
"""Read CSV file and return list of dictionaries"""
with open(filename, "r") as f:
header = f.readline().strip().split(",")
data = []
for line in f:
values = line.strip().split(",")
row = dict(zip(header, values))
data.append(row)
return data

people = read_csv("people.csv")
for person in people:
print(person)

# Output:
# {'name': 'Alice', 'age': '25', 'city': 'Lagos'}
# {'name': 'Bob', 'age': '30', 'city': 'Accra'}
# etc.

3.2 Exception Handling with Try-Except

Exception handling allows your program to handle errors gracefully instead of crashing. This is essential for robust data science applications.

Basic Try-Except

# Without exception handling - crashes
# result = 10 / 0 # ZeroDivisionError!

# With exception handling
try:
result = 10 / 0
except ZeroDivisionError:
print("Error: Cannot divide by zero")

# Output: Error: Cannot divide by zero

# Catching generic exceptions
try:
x = int("not_a_number")
except ValueError:
print("Error: Could not convert to integer")

# Output: Error: Could not convert to integer

# Catch multiple specific exceptions
try:
# Some operation that might fail
data = [1, 2, 3]
print(data[10]) # IndexError
except IndexError:
print("Error: Index out of range")
except KeyError:
print("Error: Key not found")

# Output: Error: Index out of range

# Catch any exception
try:
# Unknown operation
result = 10 / 0
except Exception as e:
print(f"An error occurred: {e}")

# Output: An error occurred: division by zero

Try-Except-Else-Finally

# Complete try-except-else-finally structure
try:
# Code that might raise an exception
num = int(input("Enter a number: "))
result = 10 / num
except ValueError:
print("Error: Invalid input, please enter a number")
except ZeroDivisionError:
print("Error: Cannot divide by zero")
else:
# Executed if no exception occurs
print(f"Result: {result}")
finally:
# Always executed, whether exception or not
print("Operation completed")

# Real-world: Reading file safely
def read_data_file(filename):
try:
with open(filename, "r") as f:
data = f.read()
return data
except FileNotFoundError:
print(f"Error: File '{filename}' not found")
return None
except IOError:
print(f"Error: Could not read file '{filename}'")
return None

content = read_data_file("data.txt")
if content:
print("File contents:")
print(content)

Common Exception Types

Raising Exceptions

# Raise exceptions manually
def validate_age(age):
if age < 0:
raise ValueError("Age cannot be negative")
if age > 150:
raise ValueError("Age seems unrealistic")
return f"Age {age} is valid"

# Using the function
try:
print(validate_age(-5))
except ValueError as e:
print(f"Validation error: {e}")

# Output: Validation error: Age cannot be negative

# Real-world: Data validation
def process_data(data):
if not data:
raise ValueError("Data cannot be empty")
if not isinstance(data, list):
raise TypeError("Data must be a list")
if len(data) == 0:
raise ValueError("Data list is empty")
return sum(data) / len(data) # Average

try:
avg = process_data([10, 20, 30])
print(f"Average: {avg}")
except (ValueError, TypeError) as e:
print(f"Error: {e}")

Week 2 Summary

By completing Week 2, you have learned:

  • Lists: creating, indexing, slicing, and modifying
  • List methods: append, extend, insert, remove, sort, etc.
  • Tuples: immutable sequences and when to use them
  • Tuple unpacking for returning multiple values
  • Dictionaries: key-value pairs and common operations
  • Nested dictionaries for structured data
  • Sets: unique collections and set operations
  • String operations: indexing, slicing, methods
  • String formatting with f-strings
  • Reading and writing files with proper file handling
  • Working with CSV files manually
  • Exception handling with try-except blocks
  • Raising and handling different exception types

Week 2 Assignments

Assignment 1: Data Structure Manipulation

Create a program that:

  • Stores student data in a list of dictionaries (name, ID, grades list)
  • Calculates average grade for each student
  • Finds the student with the highest average
  • Sorts students by average grade
  • Displays results in formatted text

Assignment 2: File Operations and Data Processing

Write a program that:

  • Creates a CSV file with sample data (at least 5 rows)
  • Reads the CSV file line by line
  • Processes the data (e.g., calculate statistics)
  • Writes results to a new output file
  • Handles potential errors gracefully

Assignment 3: Text Processing

Develop a text processing tool that:

  • Reads a text file
  • Counts words and unique words
  • Finds longest and shortest words
  • Converts all words to lowercase and counts frequencies
  • Saves analysis results to a file
  • Create a function that flattens a nested list into a single list
  • Write a program that counts character frequencies in a string
  • Create a simple phonebook using a dictionary (phone number lookup)
  • Write a program that removes duplicates from a list while preserving order
  • Create a function that converts a list of dictionaries to CSV format
  • Write a program that finds and displays lines in a file matching a pattern
  • Create a student grade tracker that reads from file, processes, and saves results
  • Write a function that validates data and raises appropriate exceptions

Additional Resources

Books

  • Automate the Boring Stuff with Python by Al Sweigart - Great for file handling
  • Chapters 3-4 from "Python for Data Analysis" by Wes McKinney

Online Resources

  • Python Documentation on built-in types: https://docs.python.org/3/library/stdtypes.html
  • Real Python: String Manipulation - https://realpython.com/python-strings/
  • Real Python: Working with Files - https://realpython.com/working-with-files-in-python/