Python learning notes

Python for Data Science, Machine Learning & AI

By Imonikhe Ayeni

This is a practical Python reference for beginners. It starts with installation and moves through core syntax, data analysis, machine learning, deep learning and AI. Work through the sections in order, type the examples yourself and change the values so you can see how the code behaves.

Beginner to intermediateAbout 18 minutesLast updated August 2026

A note from the author

I’m Imonikhe Ayeni. I use Python in data analysis, research and reporting work.

I put this reference together so learners can find the main ideas and examples in one place.

No matching sections were found. Try a broader search term.
01

Installing Python and setting up your workspace

Install a current Python 3 release from the official Python website. The exact version number changes over time, so choose the latest stable release that is compatible with the packages you plan to use. After installation, confirm that Python and pip work before installing data-science packages.

Windows installation

1. Visit the official Python downloads page.
2. Download the current Python 3 installer for Windows.
3. Open the installer.
4. Select “Add python.exe to PATH” before continuing.
5. Choose “Install Now”.
6. Open Command Prompt or PowerShell after installation.
7. Run the verification commands below.

Optional:
Install Visual Studio Code and add the official Python extension.

Verify Python on Windows

py --version
py -m pip --version

# Start Python
py

# Leave the Python interpreter
exit()

macOS installation

Recommended beginner route:

1. Visit the official Python downloads page.
2. Download the signed macOS installer.
3. Open the downloaded .pkg file.
4. Follow the installation wizard.
5. Open Terminal.
6. Verify the installation with python3 and pip3.

Do not rely on an older system-provided Python installation for your projects.

Verify Python on macOS

python3 --version
python3 -m pip --version

# Start Python
python3

# Leave the interpreter
exit()

Linux installation

# Ubuntu or Debian
sudo apt update
sudo apt install python3 python3-pip python3-venv

# Fedora
sudo dnf install python3 python3-pip

# Verify
python3 --version
python3 -m pip --version

Create a project folder and virtual environment

mkdir python-learning
cd python-learning

# Windows
py -m venv .venv
.venv\Scripts\activate

# macOS or Linux
python3 -m venv .venv
source .venv/bin/activate

# Leave the environment later
deactivate

Install the main data-science packages

python -m pip install --upgrade pip

python -m pip install \
  numpy \
  pandas \
  matplotlib \
  scipy \
  scikit-learn \
  jupyterlab \
  openpyxl \
  joblib

Start JupyterLab

jupyter lab

Test the installation

import sys
import numpy as np
import pandas as pd
import sklearn

print("Python:", sys.version)
print("NumPy:", np.__version__)
print("pandas:", pd.__version__)
print("scikit-learn:", sklearn.__version__)

print("Your Python data-science environment is ready.")
02

Python fundamentals

Variables store information. Python determines the data type automatically. Common types include integers, decimal numbers, strings, Boolean values and None.

Variables and data types

name = "Imonikhe"       # string
age = 38                 # integer
height = 1.78            # float
is_learning = True       # Boolean
result = None            # no value yet

print(type(name))
print(f"{name} is {age} years old.")

Arithmetic and comparisons

a = 10
b = 3

print(a + b)    # addition
print(a - b)    # subtraction
print(a * b)    # multiplication
print(a / b)    # division
print(a // b)   # whole-number division
print(a % b)    # remainder
print(a ** b)   # power

print(a > b)
print(a == 10)
print(a != b)

Strings

message = "  Python for Data Science  "

print(message.strip())
print(message.lower())
print(message.upper())
print(message.replace("Python", "AI"))
print(message.split())
print("Data" in message)
03

Lists, tuples, dictionaries and sets

Collections allow you to store multiple values. Lists are changeable, tuples are normally fixed, dictionaries store key-value pairs, and sets keep unique values.

Lists

scores = [78, 82, 91, 65, 88]

print(scores[0])
print(scores[-1])
print(scores[1:4])

scores.append(95)
scores.remove(65)
scores.sort(reverse=True)

average = sum(scores) / len(scores)
print(average)

Dictionaries

patient = {
    "name": "John",
    "age": 65,
    "diagnosis": "Diabetes"
}

print(patient["name"])
print(patient.get("diagnosis"))

patient["hospital"] = "Cardiff"
patient["age"] = 66

for key, value in patient.items():
    print(key, value)

Tuples and sets

coordinates = (51.4816, -3.1791)
latitude, longitude = coordinates

group_a = {"Python", "SQL", "R"}
group_b = {"Python", "Power BI", "Excel"}

print(group_a | group_b)   # union
print(group_a & group_b)   # intersection
print(group_a - group_b)   # difference
04

Conditions and loops

Conditional statements make decisions. Loops repeat code. Indentation is compulsory in Python and normally uses four spaces.

If, elif and else

score = 75

if score >= 70:
    print("Distinction")
elif score >= 60:
    print("Merit")
elif score >= 50:
    print("Pass")
else:
    print("Fail")

For loops

scores = [70, 80, 90]

for score in scores:
    print(score)

for index, score in enumerate(scores):
    print(index, score)

for number in range(1, 6):
    print(number)

While, break and continue

count = 1

while count <= 5:
    if count == 3:
        count += 1
        continue

    print(count)

    if count == 4:
        break

    count += 1

List comprehensions

squares = [number ** 2 for number in range(1, 6)]
even_numbers = [number for number in range(10) if number % 2 == 0]

scores = [45, 67, 82, 39, 91]
passed = [score for score in scores if score >= 50]
05

Functions and error handling

Functions package reusable logic. Parameters provide inputs, and return sends a result back. Error handling keeps programs from failing without explanation.

Reusable functions

def calculate_mean(values: list[float]) -> float:
    if not values:
        raise ValueError("The list cannot be empty.")

    return sum(values) / len(values)


scores = [70, 80, 90]
average = calculate_mean(scores)
print(average)

Default parameters

def calculate_total(price: float, quantity: int = 1) -> float:
    return price * quantity


print(calculate_total(20))
print(calculate_total(20, 3))

Try and except

try:
    number = int("hello")
except ValueError as error:
    print(f"Conversion failed: {error}")
finally:
    print("Operation finished.")
06

Files and object-oriented basics

The with statement safely opens and closes files. Classes are templates for creating objects and are widely used by machine-learning libraries.

Reading and writing text

with open("notes.txt", "r", encoding="utf-8") as file:
    content = file.read()

with open("output.txt", "w", encoding="utf-8") as file:
    file.write("Python is useful for data science.")

A simple class

class MachineLearningModel:
    def __init__(self, name: str, accuracy: float):
        self.name = name
        self.accuracy = accuracy

    def display_result(self) -> None:
        print(f"{self.name}: {self.accuracy:.2%}")


model = MachineLearningModel("Random Forest", 0.91)
model.display_result()
07

NumPy for numerical computing

NumPy provides efficient arrays, matrices, vectorised calculations and statistical functions. Vectorised operations are normally faster and cleaner than manual loops.

Arrays and properties

import numpy as np

scores = np.array([70, 80, 90, 100])
matrix = np.array([
    [1, 2, 3],
    [4, 5, 6]
])

print(scores.shape)
print(matrix.ndim)
print(matrix.dtype)
print(matrix.size)

Creating arrays

zeros = np.zeros(5)
ones = np.ones(5)
sequence = np.arange(0, 10, 2)
evenly_spaced = np.linspace(0, 1, 5)

rng = np.random.default_rng(seed=42)
random_values = rng.random(5)
random_integers = rng.integers(1, 100, size=5)

Calculations and filtering

scores = np.array([70, 80, 90, 100])

print(scores + 5)
print(scores * 2)
print(np.mean(scores))
print(np.median(scores))
print(np.std(scores))

high_scores = scores[scores >= 90]
print(high_scores)

Missing values and reshaping

values = np.array([10, 20, np.nan, 40])

print(np.isnan(values))
print(np.nanmean(values))

numbers = np.array([1, 2, 3, 4, 5, 6])
matrix = numbers.reshape(2, 3)
print(matrix)
08

pandas for data analysis

pandas is the main Python library for working with tables. A Series represents one column, while a DataFrame represents a full table.

Create a DataFrame

import pandas as pd

data = {
    "name": ["Alice", "Ben", "Chidi", "David"],
    "age": [25, 32, 41, 29],
    "score": [85, 72, 91, 68]
}

df = pd.DataFrame(data)
print(df)

Read common file types

df_csv = pd.read_csv("patients.csv")
df_excel = pd.read_excel("patients.xlsx", sheet_name="Responses")
df_json = pd.read_json("patients.json")

Inspect a DataFrame

print(df.head())
print(df.tail())
print(df.shape)
print(df.columns)

df.info()
print(df.describe(include="all"))

Select and filter

ages = df["age"]
selected_columns = df[["name", "score"]]

high_scores = df[df["score"] >= 80]

selected = df[
    (df["score"] >= 80) &
    (df["age"] < 40)
]

rows_by_label = df.loc[0:2, ["name", "score"]]
rows_by_position = df.iloc[0:3, 0:2]

Create and rename columns

import numpy as np

df["score_percentage"] = df["score"] / 100
df["result"] = np.where(df["score"] >= 70, "Pass", "Fail")

df = df.rename(columns={
    "score": "test_score",
    "age": "participant_age"
})
09

Data cleaning and preparation

Real-world data may contain missing values, duplicates, inconsistent types and badly formatted dates. Investigate the cause before deleting or replacing information.

Missing values

print(df.isna().sum())

missing_rows = df[df.isna().any(axis=1)]

df["score"] = df["score"].fillna(df["score"].median())
df["gender"] = df["gender"].fillna("Unknown")

df_clean = df.dropna(subset=["patient_id"])

Duplicates and types

print(df.duplicated().sum())

df = df.drop_duplicates(
    subset=["patient_id"],
    keep="first"
)

df["age"] = pd.to_numeric(df["age"], errors="coerce")
df["date"] = pd.to_datetime(df["date"], errors="coerce")

Sorting and text cleaning

df = df.sort_values(
    ["hospital", "score"],
    ascending=[True, False]
)

df["hospital"] = (
    df["hospital"]
    .str.strip()
    .str.title()
)
10

Grouping, merging and reshaping

Grouping summarises categories, merging joins related tables, and pivot tables reshape data for reporting.

Group and summarise

summary = (
    df.groupby("gender")
      .agg(
          mean_score=("score", "mean"),
          median_score=("score", "median"),
          participants=("score", "count")
      )
      .reset_index()
)

print(summary)

Counts and percentages

counts = df["gender"].value_counts()
percentages = df["gender"].value_counts(normalize=True) * 100

print(counts)
print(percentages)

Merge and concatenate

combined = pd.merge(
    patients,
    hospitals,
    on="hospital_id",
    how="left"
)

all_years = pd.concat(
    [data_2025, data_2026],
    ignore_index=True
)

Pivot and cross-tabulate

pivot = pd.pivot_table(
    df,
    values="score",
    index="hospital",
    columns="gender",
    aggfunc="mean"
)

table = pd.crosstab(
    df["gender"],
    df["response_method"],
    normalize="index"
) * 100
11

Dates and time-series preparation

Date variables allow you to calculate durations, aggregate by time period and create lagged or rolling features.

Date components and durations

df["date"] = pd.to_datetime(df["date"], errors="coerce")

df["year"] = df["date"].dt.year
df["month"] = df["date"].dt.month
df["day_name"] = df["date"].dt.day_name()

df["days_waited"] = (
    df["appointment_date"] -
    df["referral_date"]
).dt.days

Time-series features

df = df.sort_values("date").set_index("date")

monthly = df["energy_use"].resample("ME").mean()
df["energy_lag_1"] = df["energy_use"].shift(1)
df["rolling_mean_7"] = df["energy_use"].rolling(7).mean()
df["percentage_change"] = df["energy_use"].pct_change() * 100

Chronological train-test split

split_position = int(len(df) * 0.8)

train = df.iloc[:split_position]
test = df.iloc[split_position:]

# Do not randomly shuffle observations when future values
# must be predicted from earlier values.
12

Data visualisation

Charts help reveal patterns, trends, distributions and unusual observations. Always label axes and provide a meaningful title.

Line chart

import matplotlib.pyplot as plt

months = ["Jan", "Feb", "Mar", "Apr"]
scores = [70, 75, 73, 82]

plt.plot(months, scores)
plt.xlabel("Month")
plt.ylabel("Score")
plt.title("Monthly Scores")
plt.show()

Bar chart

hospitals = ["A", "B", "C"]
response_rates = [65, 72, 58]

plt.bar(hospitals, response_rates)
plt.xlabel("Hospital")
plt.ylabel("Response rate (%)")
plt.title("Response Rate by Hospital")
plt.show()

Histogram and scatter plot

plt.hist(df["age"].dropna(), bins=10)
plt.xlabel("Age")
plt.ylabel("Frequency")
plt.title("Age Distribution")
plt.show()

plt.scatter(df["age"], df["score"])
plt.xlabel("Age")
plt.ylabel("Score")
plt.title("Age and Score")
plt.show()
13

Basic statistical analysis

SciPy contains common statistical tests. A p-value does not measure the size or importance of an effect, so report effect sizes and confidence intervals where possible.

Welch independent t-test

from scipy import stats

online = df.loc[
    df["response_method"] == "Online",
    "score"
].dropna()

paper = df.loc[
    df["response_method"] == "Paper",
    "score"
].dropna()

statistic, p_value = stats.ttest_ind(
    online,
    paper,
    equal_var=False
)

print(f"Statistic: {statistic:.3f}")
print(f"P-value: {p_value:.4f}")

Chi-square test

contingency_table = pd.crosstab(
    df["gender"],
    df["response_method"]
)

chi2, p_value, degrees_of_freedom, expected = (
    stats.chi2_contingency(contingency_table)
)

print(f"Chi-square: {chi2:.3f}")
print(f"P-value: {p_value:.4f}")

Correlation

correlation = df["age"].corr(df["score"])
print(f"Correlation: {correlation:.3f}")
14

Machine-learning workflow

Supervised machine learning learns a relationship between features and a known target. Keep the test data separate until the final evaluation.

Select features and target

features = [
    "age",
    "blood_pressure",
    "cholesterol"
]

X = df[features]
y = df["has_disease"]

Train-test split

from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(
    X,
    y,
    test_size=0.2,
    random_state=42,
    stratify=y
)

Regression

from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score

model = LinearRegression()
model.fit(X_train, y_train)

predictions = model.predict(X_test)

mae = mean_absolute_error(y_test, predictions)
rmse = mean_squared_error(y_test, predictions) ** 0.5
r_squared = r2_score(y_test, predictions)

print(mae, rmse, r_squared)

Classification

from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report, roc_auc_score

model = LogisticRegression(max_iter=1000)
model.fit(X_train, y_train)

predictions = model.predict(X_test)
probabilities = model.predict_proba(X_test)[:, 1]

print(classification_report(y_test, predictions))
print(roc_auc_score(y_test, probabilities))
15

Common machine-learning models

Different algorithms make different assumptions. Compare models using suitable validation rather than choosing one because it sounds more advanced.

Decision tree

from sklearn.tree import DecisionTreeClassifier

model = DecisionTreeClassifier(
    max_depth=5,
    random_state=42
)

model.fit(X_train, y_train)
predictions = model.predict(X_test)

Random forest

from sklearn.ensemble import RandomForestClassifier

model = RandomForestClassifier(
    n_estimators=200,
    max_depth=10,
    class_weight="balanced",
    random_state=42
)

model.fit(X_train, y_train)
predictions = model.predict(X_test)

importance = pd.DataFrame({
    "feature": X_train.columns,
    "importance": model.feature_importances_
}).sort_values("importance", ascending=False)

K-nearest neighbours and SVM

from sklearn.neighbors import KNeighborsClassifier
from sklearn.svm import SVC

knn_model = KNeighborsClassifier(n_neighbors=5)
svm_model = SVC(kernel="rbf", probability=True, random_state=42)
16

Preprocessing and pipelines

Pipelines apply the same preprocessing consistently during training, testing and later prediction. They also reduce the risk of data leakage.

Scaling

from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()

X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

# Fit on training data only.
# Never fit the scaler separately on the test data.

One-hot encoding

encoded = pd.get_dummies(
    df,
    columns=["gender", "region"],
    drop_first=True,
    dtype=int
)

Combined preprocessing pipeline

from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler
from sklearn.linear_model import LogisticRegression

numeric_features = ["age", "blood_pressure", "cholesterol"]
categorical_features = ["gender", "region"]

numeric_pipeline = Pipeline([
    ("imputer", SimpleImputer(strategy="median")),
    ("scaler", StandardScaler())
])

categorical_pipeline = Pipeline([
    ("imputer", SimpleImputer(strategy="most_frequent")),
    ("encoder", OneHotEncoder(handle_unknown="ignore"))
])

preprocessor = ColumnTransformer([
    ("numeric", numeric_pipeline, numeric_features),
    ("categorical", categorical_pipeline, categorical_features)
])

model = Pipeline([
    ("preprocessor", preprocessor),
    ("classifier", LogisticRegression(
        max_iter=1000,
        class_weight="balanced"
    ))
])

model.fit(X_train, y_train)
predictions = model.predict(X_test)
17

Validation, tuning and evaluation

Evaluation must match the problem. Accuracy can be misleading when classes are imbalanced. Precision, recall, F1-score, ROC-AUC and the confusion matrix provide additional information.

Cross-validation

from sklearn.model_selection import cross_val_score

scores = cross_val_score(
    model,
    X,
    y,
    cv=5,
    scoring="roc_auc"
)

print(scores)
print(scores.mean())
print(scores.std())

Grid search

from sklearn.model_selection import GridSearchCV
from sklearn.ensemble import RandomForestClassifier

parameter_grid = {
    "n_estimators": [100, 200],
    "max_depth": [5, 10, None],
    "min_samples_split": [2, 5]
}

search = GridSearchCV(
    RandomForestClassifier(random_state=42),
    param_grid=parameter_grid,
    cv=5,
    scoring="roc_auc",
    n_jobs=-1
)

search.fit(X_train, y_train)

print(search.best_params_)
best_model = search.best_estimator_

Classification metrics

from sklearn.metrics import (
    accuracy_score,
    classification_report,
    confusion_matrix,
    roc_auc_score
)

print(accuracy_score(y_test, predictions))
print(confusion_matrix(y_test, predictions))
print(classification_report(y_test, predictions))
print(roc_auc_score(y_test, probabilities))
18

Clustering and dimensionality reduction

Unsupervised learning looks for structure without a known target. Scale variables before distance-based methods such as K-means.

K-means clustering

from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler

features = df[["income", "spending_score"]]

scaler = StandardScaler()
features_scaled = scaler.fit_transform(features)

kmeans = KMeans(
    n_clusters=3,
    random_state=42,
    n_init=10
)

df["cluster"] = kmeans.fit_predict(features_scaled)

Principal component analysis

from sklearn.decomposition import PCA

pca = PCA(n_components=2)
components = pca.fit_transform(features_scaled)

df["principal_component_1"] = components[:, 0]
df["principal_component_2"] = components[:, 1]

print(pca.explained_variance_ratio_)
19

Deep learning with TensorFlow

Neural networks contain layers of connected units. The loss function measures error, while the optimiser updates the weights to reduce that error.

Binary classification network

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers

model = keras.Sequential([
    layers.Input(shape=(X_train_scaled.shape[1],)),
    layers.Dense(64, activation="relu"),
    layers.Dropout(0.3),
    layers.Dense(32, activation="relu"),
    layers.Dense(1, activation="sigmoid")
])

model.compile(
    optimizer="adam",
    loss="binary_crossentropy",
    metrics=["accuracy"]
)

history = model.fit(
    X_train_scaled,
    y_train,
    validation_split=0.2,
    epochs=30,
    batch_size=32
)

Early stopping

early_stopping = keras.callbacks.EarlyStopping(
    monitor="val_loss",
    patience=5,
    restore_best_weights=True
)

history = model.fit(
    X_train_scaled,
    y_train,
    validation_split=0.2,
    epochs=100,
    batch_size=32,
    callbacks=[early_stopping]
)

Multiclass and regression outputs

# Four-class classification output
multiclass_output = layers.Dense(4, activation="softmax")

# Regression output
regression_output = layers.Dense(1)

# Common losses
binary_loss = "binary_crossentropy"
multiclass_loss = "sparse_categorical_crossentropy"
regression_loss = "mean_squared_error"
20

CNNs and sequence models

Convolutional neural networks are widely used for images. LSTM and GRU networks are designed for sequences such as text, sensor readings and time-series data.

Convolutional neural network

model = keras.Sequential([
    layers.Input(shape=(128, 128, 3)),
    layers.Conv2D(32, kernel_size=3, activation="relu"),
    layers.MaxPooling2D(),
    layers.Conv2D(64, kernel_size=3, activation="relu"),
    layers.MaxPooling2D(),
    layers.Flatten(),
    layers.Dense(128, activation="relu"),
    layers.Dropout(0.3),
    layers.Dense(10, activation="softmax")
])

LSTM sequence model

model = keras.Sequential([
    layers.Input(shape=(sequence_length, number_of_features)),
    layers.LSTM(64),
    layers.Dense(1)
])

model.compile(
    optimizer="adam",
    loss="mean_squared_error"
)
21

Deep learning with PyTorch

PyTorch uses tensors and explicit training loops. The standard sequence is: predict, calculate loss, clear gradients, backpropagate and update parameters.

Define and train a neural network

import torch
import torch.nn as nn

features = torch.tensor(
    [[1.0, 2.0], [3.0, 4.0]]
)

targets = torch.tensor(
    [[0.0], [1.0]]
)

class NeuralNetwork(nn.Module):
    def __init__(self):
        super().__init__()

        self.network = nn.Sequential(
            nn.Linear(2, 16),
            nn.ReLU(),
            nn.Linear(16, 1),
            nn.Sigmoid()
        )

    def forward(self, inputs):
        return self.network(inputs)


model = NeuralNetwork()
loss_function = nn.BCELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)

for epoch in range(100):
    predictions = model(features)
    loss = loss_function(predictions, targets)

    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

    if epoch % 10 == 0:
        print(epoch, loss.item())
22

Natural language processing

Text must be converted into numbers before a traditional machine-learning model can use it. TF-IDF is a strong baseline for many text-classification problems.

Simple text cleaning

text = "Python is GREAT for AI!"

clean_text = (
    text.lower()
        .replace("!", "")
        .strip()
)

words = clean_text.split()
print(words)

TF-IDF text classifier

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline

text_model = Pipeline([
    ("tfidf", TfidfVectorizer(stop_words="english")),
    ("classifier", LogisticRegression(max_iter=1000))
])

text_model.fit(training_texts, training_labels)

prediction = text_model.predict([
    "The service was excellent."
])

print(prediction)

Pretrained transformer pipeline

from transformers import pipeline

sentiment_model = pipeline("sentiment-analysis")

result = sentiment_model(
    "The service was excellent."
)

print(result)
23

Embeddings, semantic search and generative AI

Embeddings represent text as numerical vectors. Similar meanings tend to have nearby vectors, which enables semantic search and retrieval-augmented generation.

Create embeddings

from sentence_transformers import SentenceTransformer

embedding_model = SentenceTransformer(
    "all-MiniLM-L6-v2"
)

sentences = [
    "Python is used for data science.",
    "Machine-learning models learn from data."
]

embeddings = embedding_model.encode(sentences)
print(embeddings.shape)

Semantic search

from sklearn.metrics.pairwise import cosine_similarity

documents = [
    "Python is a programming language.",
    "SQL is used to query databases.",
    "Machine-learning models learn patterns from data."
]

document_embeddings = embedding_model.encode(documents)

question = "How do computers learn from information?"
question_embedding = embedding_model.encode([question])

similarities = cosine_similarity(
    question_embedding,
    document_embeddings
)[0]

best_index = similarities.argmax()
print(documents[best_index])

Typical RAG flow

Documents
   ↓
Split into chunks
   ↓
Create embeddings
   ↓
Store vectors
   ↓
Retrieve relevant chunks
   ↓
Send context and question to a language model
   ↓
Generate a grounded answer
24

Saving and reusing models

Save the complete preprocessing-and-model pipeline whenever possible. This helps ensure that future data receives exactly the same transformations.

Save and load with joblib

import joblib

joblib.dump(
    model,
    "patient_risk_model.joblib"
)

loaded_model = joblib.load(
    "patient_risk_model.joblib"
)

prediction = loaded_model.predict(new_patient_data)

Export data

df.to_csv("cleaned_data.csv", index=False)
df.to_excel("cleaned_data.xlsx", index=False)

with pd.ExcelWriter("analysis_output.xlsx") as writer:
    df.to_excel(writer, sheet_name="Clean Data", index=False)
    summary.to_excel(writer, sheet_name="Summary", index=False)
25

Complete machine-learning template

This example combines missing-value handling, scaling, categorical encoding and logistic regression in one reusable pipeline.

End-to-end classification project

import joblib
import pandas as pd

from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import classification_report
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler

df = pd.read_csv("patient_data.csv")

numeric_features = [
    "age",
    "blood_pressure",
    "cholesterol"
]

categorical_features = [
    "gender",
    "smoking_status"
]

features = numeric_features + categorical_features

X = df[features]
y = df["has_disease"]

X_train, X_test, y_train, y_test = train_test_split(
    X,
    y,
    test_size=0.2,
    random_state=42,
    stratify=y
)

numeric_pipeline = Pipeline([
    ("imputer", SimpleImputer(strategy="median")),
    ("scaler", StandardScaler())
])

categorical_pipeline = Pipeline([
    ("imputer", SimpleImputer(strategy="most_frequent")),
    ("encoder", OneHotEncoder(handle_unknown="ignore"))
])

preprocessor = ColumnTransformer([
    ("numeric", numeric_pipeline, numeric_features),
    ("categorical", categorical_pipeline, categorical_features)
])

model = Pipeline([
    ("preprocessor", preprocessor),
    ("classifier", LogisticRegression(
        max_iter=1000,
        class_weight="balanced"
    ))
])

model.fit(X_train, y_train)

predictions = model.predict(X_test)
print(classification_report(y_test, predictions))

joblib.dump(model, "patient_risk_model.joblib")
26

Good coding habits and common mistakes

Readable, reproducible code is easier to test, maintain and share. Use descriptive names, small functions, fixed random seeds and explicit validation.

Good habits

RANDOM_STATE = 42
TEST_SIZE = 0.2

analysis_data = df.copy()

if "target" not in analysis_data.columns:
    raise ValueError("The target column is missing.")

print(analysis_data.shape)
print(analysis_data.head())
print(analysis_data.isna().sum())

Avoid data leakage

# Incorrect: learns from all data before splitting.
scaler.fit(X)

# Correct: fit only on the training data.
scaler.fit(X_train)
X_train_scaled = scaler.transform(X_train)
X_test_scaled = scaler.transform(X_test)

Remember the basics

• Use = for assignment and == for comparison.
• Place a colon after if, elif, else, for, while, def and class.
• Python is case-sensitive: score and Score are different names.
• Put pandas conditions in parentheses and use & or |.
• Read error messages from the final line upward.
• Test code on a small sample before running it on a large dataset.
27

Four-week learning plan

Progress comes from writing and debugging code, not only reading it. Re-type the examples, change the inputs, deliberately create errors and explain the output in your own words.

Week 1 — Python foundations

Variables, data types, collections, conditions, loops, functions, files and exceptions.

Practice projects:
• Calculator
• Grade classifier
• Expense tracker
• Simple patient-record program

Week 2 — Data analysis

NumPy, pandas, missing values, grouping, merging, dates, visualisation and basic statistics.

Practice project:
• Exploratory analysis of a public health, NHS, sales or survey dataset

Week 3 — Machine learning

Regression, classification, preprocessing, pipelines, evaluation, cross-validation and tuning.

Practice projects:
• One regression project
• One classification project

Week 4 — Deep learning and AI

Neural networks, TensorFlow or PyTorch, NLP, transformers, embeddings and semantic search.

Practice project:
• Text classifier or document-question-answering prototype