0% found this document useful (0 votes)
2 views25 pages

Topic 3 Python For Data Science

Uploaded by

johjooh0
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)
2 views25 pages

Topic 3 Python For Data Science

Uploaded by

johjooh0
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

DATA SCIENCE

Unit Lecture Notes

Python for Data Science


Uses of Python • Environment Setup • Jupyter Notebook • Data Types
Tuples • Lists • Dictionaries • Operators • Functions

Topic Content Overview


1. Uses of Python Why Python, key libraries, Python vs R
2. Environment Setup Anaconda/pip, virtual environments, VS Code
3. Jupyter Notebook Cells, keyboard shortcuts, magic commands
4. Python Data Types int, float, str, bool, None — operations and pitfalls
5. Tuples Immutable sequences, indexing, unpacking, use cases
6. Lists Mutable sequences, methods, sorting, comprehensions
7. Dictionaries Key-value pairs, methods, nesting, comprehensions
8. Operators Arithmetic, comparison, logical, membership, identity
9. Functions def, parameters, return values, scope, lambda, docstrings
1. Uses of Python in Data Science

1.1 Why Python?


Three reasons Python dominates Data Science:
 Readability: indentation-based blocks, minimal boilerplate, and clear syntax mean analysts can
be productive in weeks. A for loop in Python is one line; the same loop in Java is five.
 Ecosystem: Python has the richest collection of data science, ML, and scientific computing
libraries of any language — NumPy, Pandas, Matplotlib, scikit-learn, TensorFlow, and PyTorch are
all Python-first.
 Community: the largest data science community of any language. Almost any problem you
encounter has a Stack Overflow answer, a GitHub repository, or a tutorial in Python.

1.2 What Python is Used For


Domain What Python Does Key Libraries
Data Wrangling Cleaning, reshaping, filtering, merging pandas, NumPy
datasets
Data Visualisation Charts, plots, interactive dashboards Matplotlib, Seaborn, Plotly
Statistical Analysis Hypothesis testing, regression, SciPy, statsmodels
distributions
Machine Learning Classification, regression, clustering, scikit-learn
evaluation
Deep Learning Neural networks, CNNs, transformers TensorFlow, PyTorch, Keras
NLP Text cleaning, sentiment analysis, NLTK, spaCy, Transformers
language models
Web Scraping Extracting data from websites BeautifulSoup, requests
Automation Repetitive tasks, file processing os, shutil, pathlib
Databases Querying SQL and NoSQL SQLAlchemy, psycopg2,
pymongo
APIs Fetching data from REST APIs requests, httpx

Python vs R
R excels at statistical modelling and is preferred in academic statistics and bioinformatics.
Python is preferred in industry, ML engineering, and production systems. For this unit, Python is the primary
tool because of its versatility and direct path into ML and AI applications.
2. Environment Setup

2.1 Installing Python


Option A — Anaconda
 What it includes: Python, conda package manager, 250+ pre-installed scientific packages, Jupyter
Notebook, Spyder IDE, and Anaconda Navigator GUI.
 Download: [Link]
 Best for: students who want everything working immediately, without manual package
installation.

Option B — Miniconda
 What it includes: Python and conda only. Install only what you need.
 Best for: users who want a lighter installation and will install specific packages as required.

Option C — Standard Python + pip


 What it includes: Base Python only. Use pip to install libraries from PyPI.
 Best for: users already comfortable with the command line.

2.2 Installing Packages with pip


pip is Python's standard package installer. It downloads packages from PyPI (the Python Package
Index at [Link]).
▶ Python: Installing packages with pip
# Install a single package
pip install numpy

# Install multiple packages at once


pip install pandas matplotlib seaborn scikit-learn

# Install a specific version


pip install pandas==2.1.0

# Upgrade an existing package


pip install --upgrade pandas

# Install from a requirements file


pip install -r [Link]

# List all installed packages


pip list

# Show details about one package


pip show pandas

2.3 Virtual Environments


A virtual environment is an isolated Python installation for a single project. It keeps project
dependencies separate, preventing version conflicts between projects.
Why they matter: Project A may need pandas 1.5 and Project B may need pandas 2.1. Without
virtual environments, only one version can be installed at a time. Virtual environments solve this by
giving each project its own isolated package space.
▶ Python: Creating and activating a virtual environment
# 1. Create a virtual environment named 'ds_env'
python -m venv ds_env

# 2. Activate it
# Windows:
ds_env\Scripts\activate

# macOS / Linux:
source ds_env/bin/activate

# 3. Your prompt now shows (ds_env) — all installs go here


(ds_env) pip install pandas numpy matplotlib jupyter

# 4. Save all package versions to a file


(ds_env) pip freeze > [Link]

# 5. Deactivate when done


deactivate

# Using conda instead:


conda create --name ds_env python=3.11
conda activate ds_env
conda install pandas numpy matplotlib
conda deactivate

2.4 VS Code Setup


 Install VS Code: Download from [Link] — free and lightweight.
 Python extension: Open Extensions (Ctrl+Shift+X) → search 'Python' → install Microsoft's Python
extension.
 Select interpreter: Ctrl+Shift+P → 'Python: Select Interpreter' → choose your virtual
environment.
 Jupyter extension: Search 'Jupyter' in Extensions → Install. Run .ipynb notebooks directly in VS
Code.
3. Jupyter Notebook Overview

3.1 What is Jupyter?


Jupyter Notebook is an interactive computing environment that combines live code, output, text,
equations, and visualisations in a single document (file extension: .ipynb). It is the standard working
environment for data scientists worldwide.
Why Jupyter suits Data Science: data science is exploratory — you try something, inspect the output,
adjust, and try again. Jupyter's cell-by-cell execution perfectly matches this workflow. You can run a
single cell to test a hypothesis without re-running the whole script.

3.2 Launching Jupyter


▶ Python: Starting Jupyter from the command line
# Activate your environment first, then:
jupyter notebook # classic interface, opens in browser

# OR — JupyterLab (modern interface, recommended)


jupyter lab

# Opens at [Link]
# Navigate to your project folder
# Click New → Python 3 to create a notebook

3.3 Cell Types


Cell Type Purpose How to Set
Code Write and run Python. Output appears directly Default — just type code
below the cell.
Markdown Formatted text: headings, bold, tables, LaTeX Esc → M or Cell menu → Markdown
equations. Does NOT execute Python.
Raw Plain text, not executed or rendered. Rarely Esc → R
used.

3.4 Essential Keyboard Shortcuts


Shortcut Action Mode
Shift + Enter Run cell and advance to next cell Both
Ctrl + Enter Run cell and stay in same cell Both
Alt + Enter Run cell and insert new cell below Both
Esc Enter Command mode (cell border turns Edit → Command
blue)
Enter Enter Edit mode (cursor inside cell) Command → Edit
A (command) Insert new cell Above current cell Command
B (command) Insert new cell Below current cell Command
D D (command) Delete current cell (press D twice) Command
Z (command) Undo cell deletion Command
M (command) Convert cell to Markdown Command
Y (command) Convert cell to Code Command
Ctrl + S Save notebook Both
Ctrl + Shift + - Split cell at cursor position Edit

3.5 Magic Commands


Magic commands are special Jupyter commands starting with % (line magic) or %% (cell magic). They
control Jupyter's behaviour rather than executing Python code.
▶ Python: Jupyter magic commands
# Time a single line (one run)
%time x = [i**2 for i in range(100000)]

# Time average over many runs (more accurate)


%timeit x = [i**2 for i in range(100000)]
# 10.2 ms ± 42.3 µs per loop (mean ± std. dev. of 7 runs)

# List all variables in the namespace


%whos

# Run an external Python script


%run my_script.py

# Show matplotlib plots inline


%matplotlib inline

# %%time — time the ENTIRE cell


%%time
total = 0
for i in range(1_000_000):
total += i

# Show all outputs from a cell (not just the last line)
from [Link] import InteractiveShell
InteractiveShell.ast_node_interactivity = 'all'

Best Practices for Notebooks


One logical task per cell: keep cells focused for easier debugging.
Restart and Run All regularly: Kernel → Restart & Run All confirms cells work in order. Cells run out
of order create hidden bugs.
Document with Markdown: explain your analysis between code cells. A notebook should tell a story,
not just run code.
4. Python Data Types

4.1 Overview
Every value in Python has a type. Python is dynamically typed — you do not declare types. Python
infers the type from the value assigned. The five core built-in types are: int, float, str, bool, and
NoneType.
▶ Python: Checking data types with type()
x = 42; print(type(x)) # <class 'int'>
y = 3.14; print(type(y)) # <class 'float'>
s = 'hello'; print(type(s)) # <class 'str'>
b = True; print(type(b)) # <class 'bool'>
n = None; print(type(n)) # <class 'NoneType'>

4.2 Integers (int)


Integers are whole numbers — positive, negative, or zero. In Python 3, integers have unlimited
precision (no maximum size).
▶ Python: Integer operations
a, b = 17, 5
print(a + b) # 22 — addition
print(a - b) # 12 — subtraction
print(a * b) # 85 — multiplication
print(a ** b) # 1419857 — exponentiation (17^5)
print(a // b) # 3 — floor (integer) division
print(a % b) # 2 — modulo (remainder)

# Conversion to int
int(3.9) # 3 — truncates (does NOT round)
int('42') # 42 — string to int
int(True) # 1 — bool to int

# Very large integers work fine


googol = 10 ** 100
print(googol) # 100-digit number

4.3 Floats (float)


Floats represent numbers with a decimal point. Python uses 64-bit double-precision (IEEE 754), giving
approximately 15–17 significant decimal digits.
▶ Python: Float operations and the precision trap
pi = 3.14159; e = 2.71828
print(pi + e) # 5.85987
print(pi * 2) # 6.28318

# Scientific notation
avogadro = 6.022e23 # 6.022 × 10²³
electron = 9.109e-31 # mass of electron in kg

# ⚠ FLOATING-POINT PRECISION TRAP


print(0.1 + 0.2) # 0.30000000000000004 (NOT 0.3!)
print(0.1 + 0.2 == 0.3) # False

# Solution: use round() for display, or the decimal module for money
print(round(0.1 + 0.2, 2)) # 0.3

⚠ Floating-Point Warning
Never use == to compare floats. Due to binary representation limits, 0.1 + 0.2 is not exactly 0.3. Instead,
check if the difference is smaller than a tiny threshold: abs(a - b) < 1e-9. This is critical in data
science when comparing computed results.

4.4 Strings (str)


Strings are sequences of characters enclosed in single, double, or triple quotes. Strings are
immutable — you cannot change a character in place; you create a new string instead.
▶ Python: String operations and methods
# Creation
s1 = 'Hello'; s2 = "World"
s3 = '''Multi-line
string here'''

# Concatenation and repetition


print(s1 + ' ' + s2) # 'Hello World'
print('Ha' * 3) # 'HaHaHa'
print(len(s1)) # 5

# Indexing (zero-based) and slicing


s = 'Python'
print(s[0]) # 'P' — first character
print(s[-1]) # 'n' — last character
print(s[0:3]) # 'Pyt' — slice [start:stop]
print(s[::-1]) # 'nohtyP' — reversed

# Key methods
text = ' Hello, Data Science! '
print([Link]()) # remove whitespace
print([Link]()) # lowercase
print([Link]()) # uppercase
print([Link]('Hello','Hi'))# replace substring
print([Link](',')) # split → list

# f-strings (Python 3.6+) — most readable way to format


name = 'Alice'; score = 95.7
print(f'Student: {name}, Score: {score:.1f}%')
# Student: Alice, Score: 95.7%

# Membership testing
print('Data' in 'Data Science') # True

4.5 Booleans (bool)


Booleans represent True or False. They are a subtype of integers: True == 1 and False == 0. Most
Python objects can be evaluated as booleans, which is used extensively in conditionals.
▶ Python: Boolean operations and truthiness
# Logical operators
print(True and False) # False
print(True or False) # True
print(not True) # False

# FALSY values — evaluate to False in a boolean context:


bool(0) # False
bool('') # False — empty string
bool([]) # False — empty list
bool(None) # False

# TRUTHY values — everything else:


bool(1) # True
bool('hi') # True
bool([1,2]) # True

# Practical filtering: remove zeros from a list


scores = [85, 0, 92, 0, 78]
non_zero = [s for s in scores if s] # [85, 92, 78]

4.6 None
None is Python's null value — it represents the absence of a value. It is its own type (NoneType) and
there is only one None object in Python. Functions that return nothing implicitly return None.
▶ Python: Working with None
x = None
print(type(x)) # <class 'NoneType'>

# Always use 'is None' to check — NOT '== None'


if x is None:
print('x has no value assigned')

# In Data Science, None represents missing data.


# pandas uses NaN for missing numeric data and None for missing objects.
student_grade = None # grade not yet submitted
5. Tuples

5.1 What is a Tuple?


A tuple is an ordered, immutable sequence of elements. Once created, a tuple cannot be modified
— you cannot add, remove, or change elements. Tuples are defined with parentheses () or just
commas.
▶ Python: Creating tuples
# Empty tuple
t0 = ()

# Single element — the comma is ESSENTIAL


t1 = (42,) # This IS a tuple
t2 = (42) # This is just the integer 42, NOT a tuple!

# Multi-element tuples
coords = (10.5, 20.3) # latitude, longitude
person = ('Alice', 30, 'F') # name, age, gender
rgb = (255, 128, 0) # red-green-blue colour
mixed = (1, 'hello', True)

# Tuple packing — parentheses are optional


point = 3, 4 # same as (3, 4)

# Convert other sequences to tuple


from_list = tuple([1, 2, 3]) # (1, 2, 3)
from_str = tuple('abc') # ('a', 'b', 'c')

print(type(coords)) # <class 'tuple'>

5.2 Indexing and Slicing


▶ Python: Accessing tuple elements
t = (10, 20, 30, 40, 50)

# Positive indexing (0-based)


print(t[0]) # 10 — first element
print(t[2]) # 30 — third element
print(t[-1]) # 50 — last element
print(t[-2]) # 40 — second from last

# Slicing [start:stop:step] — stop is EXCLUSIVE


print(t[1:4]) # (20, 30, 40)
print(t[:3]) # (10, 20, 30) — from beginning
print(t[2:]) # (30, 40, 50) — to end
print(t[::2]) # (10, 30, 50) — every 2nd element
print(t[::-1]) # (50, 40, 30, 20, 10) — reversed

# Nested tuple access


matrix = ((1, 2), (3, 4), (5, 6))
print(matrix[1]) # (3, 4)
print(matrix[1][0]) # 3

5.3 Tuple Unpacking


Tuple unpacking assigns tuple elements to individual variables in one line — one of Python's most
elegant features.
▶ Python: Tuple unpacking
# Basic unpacking
coords = (48.8, 2.35) # Paris: lat, lon
lat, lon = coords
print(lat) # 48.8
print(lon) # 2.35

# Swap two variables — no temporary variable needed


a, b = 10, 20
a, b = b, a
print(a, b) # 20 10

# Extended unpacking with *


first, *rest = (1, 2, 3, 4, 5)
print(first) # 1
print(rest) # [2, 3, 4, 5]

*start, last = (1, 2, 3, 4, 5)


print(last) # 5

# Unpacking in a for loop — very common pattern


students = [('Alice', 90), ('Bob', 85), ('Carol', 92)]
for name, score in students:
print(f'{name}: {score}')
# Alice: 90 Bob: 85 Carol: 92

5.4 Tuple Methods and Operations


▶ Python: Tuple methods
t = (1, 2, 3, 2, 4, 2, 5)

# count() — how many times a value appears


print([Link](2)) # 3

# index() — position of first occurrence


print([Link](3)) # 2
print([Link](2)) # 1 (first occurrence only)

# Membership testing
print(3 in t) # True
print(9 in t) # False

# Concatenation and repetition (creates new tuple)


print((1, 2) + (3, 4)) # (1, 2, 3, 4)
print((1, 2) * 3) # (1, 2, 1, 2, 1, 2)

# Trying to modify a tuple raises TypeError:


# t[0] = 99 → TypeError: 'tuple' object does not support item assignment

5.5 When to Use Tuples vs Lists


Criterion Tuple List
Mutability Immutable — cannot be changed Mutable — can be changed
Syntax Parentheses () or just commas Square brackets []
Performance Slightly faster, less memory Slightly slower, more memory
Can be dict key Yes (hashable) No (not hashable)
Typical use Fixed data: coordinates, DB records, Variable collections that grow or shrink
RGB values

6. Lists
6.1 What is a List?
A list is an ordered, mutable sequence of elements. It is the most versatile and commonly used data
structure in Python. Lists are defined with square brackets [].

▶ Python: Creating lists


# Empty list
empty = []

# Homogeneous lists
scores = [85, 92, 78, 95, 88]
names = ['Alice', 'Bob', 'Carol', 'Dave']
prices = [19.99, 34.50, 5.99]

# Mixed-type (Python allows this)


record = ['Alice', 30, True, 95.5, None]

# Nested list (2D matrix)


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

# Create from other sequences


from_range = list(range(5)) # [0, 1, 2, 3, 4]
from_tuple = list((1, 2, 3)) # [1, 2, 3]
from_string = list('hello') # ['h','e','l','l','o']

6.2 Indexing, Slicing, and Mutability


▶ Python: Indexing, slicing, and modifying lists
fruits = ['apple', 'banana', 'cherry', 'date', 'elderberry']

# Indexing
print(fruits[0]) # 'apple'
print(fruits[-1]) # 'elderberry'

# Slicing (returns a new list)


print(fruits[1:3]) # ['banana', 'cherry']
print(fruits[:2]) # ['apple', 'banana']
print(fruits[::2]) # ['apple', 'cherry', 'elderberry']
print(fruits[::-1]) # reversed list

# MUTABILITY — unlike tuples, you CAN change elements


fruits[0] = 'avocado'
print(fruits) # ['avocado', 'banana', 'cherry', 'date', 'elderberry']

# Accessing nested list elements


matrix = [[1,2,3],[4,5,6],[7,8,9]]
print(matrix[1][2]) # 6 (row 1, column 2)

6.3 List Methods — Modifying


▶ Python: Adding and removing elements
nums = [3, 1, 4, 1, 5, 9]

# append() — add ONE element to the end


[Link](2)
print(nums) # [3, 1, 4, 1, 5, 9, 2]

# insert(index, value) — insert at a specific position


[Link](0, 0) # insert 0 at index 0

# extend() — add all elements from another iterable


[Link]([6, 5])
# equivalent to: nums += [6, 5]

# remove() — remove FIRST occurrence of a value


[Link](1) # removes the first 1

# pop(index) — remove AND return element at index


last = [Link]() # removes last element
third = [Link](2) # removes element at index 2

# del — delete by index or slice


del nums[0]
del nums[1:3]

# clear() — remove all elements


[Link]() # nums is now []

6.4 List Methods — Searching and Sorting


▶ Python: Searching and sorting lists
fruits = ['banana', 'apple', 'cherry', 'apple', 'date']

# index() — first position of a value


print([Link]('apple')) # 1

# count() — how many times a value appears


print([Link]('apple')) # 2

# sort() — sort IN PLACE (modifies the list)


[Link]()
print(fruits) # ['apple', 'apple', 'banana', 'cherry', 'date']
[Link](reverse=True) # descending

# sorted() — returns a NEW sorted list, original unchanged


nums = [5, 2, 8, 1, 9, 3]
sorted_nums = sorted(nums) # [1, 2, 3, 5, 8, 9]
print(nums) # [5, 2, 8, 1, 9, 3] — unchanged

# Sort by a custom key


students = [('Bob', 85), ('Alice', 92), ('Carol', 78)]
[Link](key=lambda s: s[1], reverse=True)
# [('Alice', 92), ('Bob', 85), ('Carol', 78)]

# Other useful operations


print(min(nums), max(nums), sum(nums)) # 1 9 28
print(len(nums)) # 6

6.5 List Comprehensions


List comprehensions are a concise, Pythonic way to build new lists. They are faster than equivalent
for loops and are central to idiomatic Python.
Syntax: [ expression for item in iterable if condition ]

▶ Python: List comprehensions — from basic to advanced


# Squares of 0–9
squares = [x**2 for x in range(10)]
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

# Equivalent for loop (slower, more verbose):


squares = []
for x in range(10):
[Link](x**2)

# With condition: only even squares


even_squares = [x**2 for x in range(10) if x % 2 == 0]
# [0, 4, 16, 36, 64]

# Transform strings
names = ['alice', 'bob', 'carol']
upper = [[Link]() for name in names]
# ['ALICE', 'BOB', 'CAROL']

# Filter a list
scores = [85, 42, 91, 67, 38, 95]
passing = [s for s in scores if s >= 50]
# [85, 91, 67, 95]

# Conditional expression (ternary) inside comprehension


grades = ['Pass' if s >= 50 else 'Fail' for s in scores]
# ['Pass', 'Fail', 'Pass', 'Pass', 'Fail', 'Pass']

# Flatten a 2D matrix into a 1D list


matrix = [[1,2,3],[4,5,6],[7,8,9]]
flat = [val for row in matrix for val in row]
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
7. Dictionaries

7.1 What is a Dictionary?


A dictionary is an unordered collection of key-value pairs. Each key maps to exactly one value. Keys
must be unique and immutable (strings, numbers, or tuples). Values can be anything. Ordered by
insertion since Python 3.7.
▶ Python: Creating dictionaries
# Empty dict
d = {} # or: d = dict()

# Dictionary literal
student = {'name': 'Alice', 'age': 22, 'gpa': 3.8}

# Mixed value types


record = {
'id': 12345,
'name': 'Alice Doe',
'scores': [85, 92, 78], # list as value
'active': True,
'email': None
}

# From keyword arguments


d2 = dict(name='Bob', age=25, city='Kigali')

# From a list of (key, value) pairs


pairs = [('a', 1), ('b', 2), ('c', 3)]
d3 = dict(pairs) # {'a': 1, 'b': 2, 'c': 3}

7.2 Accessing and Modifying Values


▶ Python: CRUD operations on dictionaries
student = {'name': 'Alice', 'age': 22, 'gpa': 3.8}

# READ — access by key


print(student['name']) # 'Alice'

# KeyError if key doesn't exist:


# student['email'] → KeyError: 'email'

# Safe access with get() — returns None (or a default) if missing


print([Link]('email')) # None
print([Link]('email', 'N/A')) # 'N/A'

# CREATE / UPDATE — add or change a key


student['email'] = 'alice@[Link]' # add new key
student['age'] = 23 # update existing key

# Update multiple keys at once


[Link]({'age': 24, 'gpa': 3.9})

# DELETE
del student['email']
gpa = [Link]('gpa') # removes and returns the value

# Check key existence


print('name' in student) # True
print('gpa' in student) # False (we just removed it)
7.3 Iterating over Dictionaries
▶ Python: Looping over keys, values, and items
grades = {'Alice': 90, 'Bob': 85, 'Carol': 92, 'Dave': 78}

# Iterate over KEYS (default)


for name in grades: # same as: for name in [Link]()
print(name)

# Iterate over VALUES


for score in [Link]():
print(score)
average = sum([Link]()) / len(grades) # 86.25

# Iterate over KEY-VALUE PAIRS — most common


for name, score in [Link]():
print(f'{name}: {score}')

# Convert to lists
print(list([Link]())) # ['Alice', 'Bob', 'Carol', 'Dave']
print(list([Link]())) # [90, 85, 92, 78]

# setdefault() — get value, or set default if key missing


[Link]('Eve', 0) # adds Eve:0 only if Eve not present

# Merge two dicts (Python 3.9+)


extra = {'Frank': 88}
merged = grades | extra

7.4 Nested Dictionaries


Dictionaries can contain other dictionaries, creating hierarchical data — the same structure as JSON
(the standard format for API data).
▶ Python: Nested dictionaries — student database example
students = {
'STU001': {
'name': 'Alice Doe',
'school': 'SBITE',
'grades': {'Data Science': 88, 'ML': 92},
'active': True
},
'STU002': {
'name': 'Bob Smith',
'school': 'SNHS',
'grades': {'Biology': 79, 'Chemistry': 83},
'active': False
}
}

# Access nested values


print(students['STU001']['name']) # 'Alice Doe'
print(students['STU001']['grades']['ML']) # 92

# Safe nested access


ml = [Link]('STU001',{}).get('grades',{}).get('ML','N/A')

# Iterate over all students


for sid, info in [Link]():
print(f"{sid}: {info['name']} — Active: {info['active']}")

7.5 Dictionary Comprehensions


Syntax: { key_expr : value_expr for item in iterable if condition
}
▶ Python: Dictionary comprehensions
# Map each number to its square
squares = {x: x**2 for x in range(6)}
# {0:0, 1:1, 2:4, 3:9, 4:16, 5:25}

# Invert a dictionary (swap keys and values)


original = {'a': 1, 'b': 2, 'c': 3}
inverted = {v: k for k, v in [Link]()}
# {1: 'a', 2: 'b', 3: 'c'}

# Filter: keep only passing students


grades = {'Alice': 90, 'Bob': 45, 'Carol': 82, 'Dave': 35}
passing = {name: s for name, s in [Link]() if s >= 50}
# {'Alice': 90, 'Carol': 82}

# Normalise scores to 0–1 scale


top = max([Link]()) # 90
normalised = {n: round(s/top, 3) for n, s in [Link]()}
# {'Alice': 1.0, 'Bob': 0.5, 'Carol': 0.911, 'Dave': 0.389}
8. Operators

8.1 Arithmetic Operators


Operator Operation Example Result
+ Addition 7+3 10
- Subtraction 7-3 4
* Multiplication 7*3 21
/ Division (always float) 7/2 3.5
// Floor division 7 // 2 3
% Modulo (remainder) 7%3 1
** Exponentiation 2 ** 10 1024

▶ Python: Arithmetic operators and augmented assignment


a, b = 17, 5
print(a / b) # 3.4 — always returns float
print(a // b) # 3 — floor division (rounds down)
print(a % b) # 2 — remainder (useful for even/odd check)
print(a ** b) # 1419857 — 17 to the power 5

# Check if n is even
n = 42
print(n % 2 == 0) # True

# Augmented assignment operators


x = 10
x += 5 # x = 15 (same as x = x + 5)
x -= 3 # x = 12
x *= 2 # x = 24
x //= 5 # x = 4
x **= 3 # x = 64

8.2 Comparison Operators


Comparison operators compare two values and return a Boolean (True or False).

Operator Meaning Example Result


== Equal to 5 == 5 True
!= Not equal to 5 != 3 True
> Greater than 7>3 True
< Less than 3<7 True
>= Greater than or equal 5 >= 5 True
<= Less than or equal 3 <= 7 True

▶ Python: Comparison operators and chaining


print(10 > 5) # True
print(3 == 3.0) # True — int and float compare equal
print('abc' == 'ABC') # False — case sensitive

# Python allows CHAINED comparisons — very readable


age = 25
print(18 <= age <= 65) # True — working age bracket
print(0 < 7 < 10) # True

# Comparing strings — lexicographic (alphabetical) order


print('apple' < 'banana') # True

# Comparing lists — element by element


print([1, 2, 3] < [1, 2, 4]) # True (3 < 4 at index 2)

8.3 Logical Operators


Operator Meaning Example Result
and Both must be True True and False False
or At least one True False or True True
not Inverts Boolean not True False

▶ Python: Logical operators in practice


age = 22
income = 50000
has_id = True

# Compound condition
eligible = age >= 18 and income > 30000 and has_id
print(eligible) # True

# Short-circuit evaluation: 'and' stops at first False


def risky(): raise Exception('called!')
print(False and risky()) # False — risky() never called

# 'or' stops at first True


print(True or risky()) # True — risky() never called

# Practical: provide a default value


name = '' or 'Anonymous' # '' is falsy → uses 'Anonymous'
print(name) # 'Anonymous'

# Operator precedence: not > and > or


print(True or False and False) # True (and evaluated first)
print((True or False) and False) # False (brackets change order)

8.4 Membership and Identity Operators


▶ Python: in, not in, is, is not
# Membership operators — test if value is in a sequence
fruits = ['apple', 'banana', 'cherry']
print('apple' in fruits) # True
print('mango' not in fruits) # True

# Works on strings
print('ello' in 'Hello') # True

# Works on dicts — checks KEYS (not values)


d = {'a': 1, 'b': 2}
print('a' in d) # True
print(1 in d) # False — 1 is a value, not a key
print(1 in [Link]()) # True

# Identity operators — test if same OBJECT in memory


# Use 'is' ONLY for None, True, False
x = None
print(x is None) # True — correct way to check for None

# WARNING: 'is' vs '=='


a = [1, 2, 3]
b = [1, 2, 3]
c = a
print(a == b) # True — same VALUE
print(a is b) # False — different objects in memory
print(a is c) # True — c points to the SAME object as a
9. Functions

9.1 Defining and Calling Functions


A function is a named, reusable block of code. Functions are defined with the def keyword. The
DRY principle — Don't Repeat Yourself — says: if you write the same code more than twice, put it in a
function.
▶ Python: Basic function definition and calling
# Function with no parameters
def greet():
print('Hello, Data Science!')

greet() # call the function → Hello, Data Science!

# Function with parameters


def greet_person(name):
print(f'Hello, {name}!')

greet_person('Alice') # Hello, Alice!


greet_person('Bob') # Hello, Bob!

# Function with return value


def square(n):
return n ** 2

result = square(7)
print(result) # 49

# Function with multiple parameters


def calculate_bmi(weight_kg, height_m):
bmi = weight_kg / (height_m ** 2)
return round(bmi, 1)

print(calculate_bmi(70, 1.75)) # 22.9

9.2 Parameters and Arguments


▶ Python: Default parameters, keyword arguments, *args, **kwargs
# Default parameter values
def power(base, exponent=2): # exponent defaults to 2
return base ** exponent

print(power(3)) # 9 — uses default exponent=2


print(power(3, 3)) # 27 — overrides default
print(power(2, 10)) # 1024

# Keyword arguments — pass by name, any order


def describe(name, age, school):
return f'{name}, age {age}, at {school}'

describe(age=22, school='SBITE', name='Alice') # order irrelevant

# *args — accept ANY number of positional arguments


def total(*numbers):
return sum(numbers)

print(total(1, 2, 3)) # 6
print(total(10, 20, 30, 40)) # 100

# **kwargs — accept ANY number of keyword arguments


def show_info(**details):
for key, value in [Link]():
print(f' {key}: {value}')
show_info(name='Alice', score=92, city='Kigali')
# name: Alice
# score: 92
# city: Kigali

9.3 Return Values


▶ Python: Single and multiple return values
# Functions without return → return None automatically
def print_hello():
print('Hello')

result = print_hello() # prints Hello


print(result) # None

# Return multiple values — returned as a tuple


def min_max(numbers):
return min(numbers), max(numbers)

lo, hi = min_max([3, 1, 4, 1, 5, 9, 2, 6])


print(f'Min: {lo}, Max: {hi}') # Min: 1, Max: 9

# Early return — exit the function before reaching the end


def safe_divide(a, b):
if b == 0:
return None # early return avoids ZeroDivisionError
return a / b

print(safe_divide(10, 2)) # 5.0


print(safe_divide(10, 0)) # None

9.4 Variable Scope — LEGB Rule


Python uses the LEGB rule: Local → Enclosing → Global → Built-in. Variables created inside a
function are local to that function and cannot be accessed from outside.
▶ Python: Scope demonstration — LEGB rule
x = 'global'

def outer():
x = 'enclosing'

def inner():
x = 'local'
print(x) # 'local' — uses innermost x

inner()
print(x) # 'enclosing'

outer()
print(x) # 'global'

# global keyword — modify a global variable inside a function


count = 0
def increment():
global count
count += 1

increment(); increment()
print(count) # 2

# Best practice: avoid global variables.


# Pass values as arguments and return results instead.
9.5 Lambda Functions
Lambda functions are small, anonymous functions defined with the lambda keyword. They can
have any number of arguments but only ONE expression. Most useful as an argument to sorted(),
map(), or filter().

Syntax: lambda parameters : expression

▶ Python: Lambda functions


# Equivalent normal and lambda function
def square(x): return x**2
square_l = lambda x: x**2

print(square(5)) # 25
print(square_l(5)) # 25

# Lambda with multiple parameters


add = lambda a, b: a + b
print(add(3, 4)) # 7

# Most common use: key in sorted()


students = [('Alice', 92), ('Bob', 85), ('Carol', 78), ('Dave', 95)]

# Sort by score descending


by_score = sorted(students, key=lambda s: s[1], reverse=True)
# [('Dave', 95), ('Alice', 92), ('Bob', 85), ('Carol', 78)]

# With map() — apply function to every element


scores = [85, 92, 78, 95]
scaled = list(map(lambda s: round(s * 0.9, 1), scores))
# [76.5, 82.8, 70.2, 85.5]

# With filter() — keep elements where function returns True


passing = list(filter(lambda s: s >= 80, scores))
# [85, 92, 95]

9.6 Docstrings
Docstrings document a function. Placed immediately after the def line in triple quotes, they describe
what the function does, its parameters, and return value. Good docstrings are a professional habit
and enable the help() system.
▶ Python: Function with a complete docstring
def calculate_stats(numbers):
'''
Calculate basic descriptive statistics for a list of numbers.

Parameters
----------
numbers : list of int or float

Returns
-------
dict with keys: count, mean, median, min, max

Example
-------
>>> calculate_stats([1, 2, 3, 4, 5])
{'count': 5, 'mean': 3.0, 'median': 3, 'min': 1, 'max': 5}
'''
if not numbers:
return None
n = len(numbers)
srt = sorted(numbers)
mid = n // 2
med = srt[mid] if n % 2 else (srt[mid-1] + srt[mid]) / 2
return {'count': n, 'mean': sum(numbers)/n,
'median': med, 'min': min(numbers), 'max': max(numbers)}
# Access the docstring
help(calculate_stats)

# Test it
print(calculate_stats([85, 92, 78, 95, 88]))
# {'count': 5, 'mean': 87.6, 'median': 88, 'min': 78, 'max': 95}
Summary and Quick Reference

Data Structure Comparison


Structure Syntax Ordered Mutable Duplicate Keys Best For
Tuple (1, 2, 3) Yes No ✗ N/A Fixed records, coordinates,
dict keys
List [1, 2, 3] Yes Yes ✓ Yes Any ordered, changeable
collection
Dictionary {'a': 1} Yes (3.7+) Yes ✓ No (keys Key-value lookup, JSON-like
unique) data

Operator Quick Reference


Category Operators Example
Arithmetic + − * / // % ** 7//2 → 3, 7%3 → 1, 2**8 → 256
Comparison == != > < >= <= 5 >= 5 → True, 'a' < 'b' → True
Logical and or not age>=18 and has_id → True
Membership in not in 'x' in 'text' → True
Identity is is not x is None → True/False
Augmented += −= *= /= **= //= x += 1 is shorthand for x = x + 1

Python Best Practices for Data Science


Use descriptive variable names: df_sales not d; total_revenue not tr.
Write docstrings: every function should document inputs, outputs, and an example.
Use list/dict comprehensions: faster and more Pythonic than equivalent for loops.
Prefer tuples for fixed data: signals to readers that the data will not change.
Never compare floats with ==: use abs(a-b) < 1e-9 instead.
Use .get() on dicts: avoids KeyError when a key may be absent.
Restart and Run All in Jupyter: always test your notebook in order before submitting.
Use virtual environments: one per project to prevent dependency conflicts.

Data Science — Python Foundations Lecture Notes | 2025–2026


Uses of Python • Environment Setup • Jupyter • Data Types • Tuples • Lists • Dictionaries • Operators •
Functions

You might also like