AI in Medicine — Summer Course 2026
Python for
Data Science
A hands-on workshop for biomedical professionals —
from theory to real data analysis in ~2.5 hours
27 July 2026
1400–1715
Google Colab
🧑💻
Your Instructor
Koay Hong Vin
Education
B.Eng. Electrical Engineering
PhD in Artificial Intelligence — Autonomous Vehicles
B.Eng. Electrical Engineering
PhD in Artificial Intelligence — Autonomous Vehicles
Current
Risk Modelling and Data Scientist
Ant International
Risk Modelling and Data Scientist
Ant International
01 / Agenda
What We'll Cover Today
01
Foundations & Python Refresher
~25 min
02
NumPy Essentials
~20 min
03
Pandas Fundamentals
~35 min
04
Data Visualization
~20 min
05
Mini Project: Malaysia Births
~40 min
06
Wrap-up & Next Steps
~10 min
02 / Context
Why Python for Biomedical Data?
- Free & Open Source — no license fees, unlike SAS or SPSS. Anyone can use it, anywhere.
- Reproducible Research — scripted analysis that anyone can re-run. No more "which Excel cell did I click?"
- Handles Real Data — Excel chokes on 1M rows. Pandas handles 100M+ rows and files like CSV, Excel, Parquet, SQL.
- Rich Ecosystem — stats (SciPy), ML (scikit-learn), bioinformatics (Biopython), imaging (scikit-image). All in one language.
- Industry Standard — used in NIH, Oxford, Nature-published research, and every major pharma company.
03 / Discipline
What is Data Science?
- The field — turning raw data into decisions by combining statistics, programming, and domain knowledge.
- Three pillars — maths & stats, computer programming, and biomedical expertise. The most valuable insights sit where they overlap.
- It’s a cycle, not a step — ask a question, get data, clean it, explore, analyse, communicate, then ask a sharper question.
- Why now in medicine? — EHRs, wearables, omics, imaging. The bottleneck is no longer data; it’s people who can interpret it.
~80%
of a data scientist’s time is spent cleaning & preparing data — not modelling.
3 + 1
pillars: stats, code, domain — plus communication to make it count.
04 / Workflow
The Data Analysis Workflow
❓
01 · Ask
Define a sharp question: “Are live births lower on weekends?”
📥
02 · Get
Load the data. CSV, Excel, SQL, or an API. We’ll use
read_csv().🧹
03 · Clean
Missing values, wrong types, duplicates, outliers. Often 80% of the work.
🔍
04 · Explore
Summarise & plot. Look before you leap — patterns appear in pictures.
📈
05 · Analyse
Correlation, t-tests, models. Put numbers on what the eye sees.
📣
06 · Share
Charts + a reproducible notebook. Insight that isn’t read is wasted.
“Today we walk through all six steps — once you see the loop, every dataset is just another lap.”
05 / Python
Python Refresher
- Variables & types —
x = 5(int),weight = 70.2(float),name = "Ali"(str),diabetic = True(bool). Python infers the type. - Lists — ordered & mutable:
[5.2, 4.8, 6.1]. Indexing starts at 0, sovalues[0]is the first element. - Dictionaries — key → value pairs:
{"age": 45, "bp": 130}. Look up by name, not position — much like a patient record. - Functions — reusable blocks:
def square(x): return x*x. Or lean on built-ins likelen()andprint(). - Imports — borrow power:
import numpy as np. The community’s libraries do the heavy lifting for you.
# A patient record as a dict
patient = {"name": "Ali", "age": 45}
# A function we'll reuse
def bmi(weight_kg, height_m):
return weight_kg / (height_m ** 2)
print(bmi(70, 1.75)) # 22.86
patient = {"name": "Ali", "age": 45}
# A function we'll reuse
def bmi(weight_kg, height_m):
return weight_kg / (height_m ** 2)
print(bmi(70, 1.75)) # 22.86
06 / Data Types
Types of Data
🔢
Numerical — Continuous
Measured on a smooth scale — any value within a range is possible, in theory down to infinite decimals.
Everyday — your height (1.753 m), the temperature (27.4°C), the time it takes to drive to work (23.7 min).
Biomedical — blood pressure 122.3 mmHg, BMI 22.86, serum glucose 5.4 mmol/L, tumour diameter 3.2 cm.
Valid ops — mean, difference, ratio (“twice as much”), regression on the raw number.
Everyday — your height (1.753 m), the temperature (27.4°C), the time it takes to drive to work (23.7 min).
Biomedical — blood pressure 122.3 mmHg, BMI 22.86, serum glucose 5.4 mmol/L, tumour diameter 3.2 cm.
Valid ops — mean, difference, ratio (“twice as much”), regression on the raw number.
🧮
Numerical — Discrete
Counted in whole numbers — you can’t have 2.3 of them. Arises from counting events, not measuring.
Everyday — number of children in a family (3), cars in the carpark (47), goals in a match (2).
Biomedical — pregnancies (gravida 4), admissions per day (12), lesions on a CT (3), metastatic lymph nodes (2 of 15).
Valid ops — mean is meaningful but the value itself is a count; ratios (“2× the polyps”) need care.
Everyday — number of children in a family (3), cars in the carpark (47), goals in a match (2).
Biomedical — pregnancies (gravida 4), admissions per day (12), lesions on a CT (3), metastatic lymph nodes (2 of 15).
Valid ops — mean is meaningful but the value itself is a count; ratios (“2× the polyps”) need care.
🏷️
Categorical — Nominal
Named groups with no inherent order. The numbers we assign (e.g. A=1, B=2) are just labels — they don’t add or rank.
Everyday — blood type, brand of phone, favourite colour, postcode, marital status.
Biomedical — ABO blood group, sex, ICD-10 code, hospital site, eye colour. The simplest case is binary: alive/dead, smoker/non-smoker, pregnant/not.
Valid ops — count per category, proportions, chi-square. Never average the codes.
Everyday — blood type, brand of phone, favourite colour, postcode, marital status.
Biomedical — ABO blood group, sex, ICD-10 code, hospital site, eye colour. The simplest case is binary: alive/dead, smoker/non-smoker, pregnant/not.
Valid ops — count per category, proportions, chi-square. Never average the codes.
📊
Categorical — Ordinal
Named groups with a meaningful order — but the gaps between steps aren’t equal. Stage II → III isn’t the same jump as III → IV.
Everyday — S/M/L t-shirt sizes, education level (primary/secondary/degree), movie ratings (1–5 stars), Hot–Medium–Mild spice.
Biomedical — cancer stage I–IV, NYHA heart-failure class I–IV, Likert pain 0–10, Gleason score 6–10, ASA physical-status class I–V.
Valid ops — median, order statistics, rank-based tests. Means are common but technically a stretch.
Everyday — S/M/L t-shirt sizes, education level (primary/secondary/degree), movie ratings (1–5 stars), Hot–Medium–Mild spice.
Biomedical — cancer stage I–IV, NYHA heart-failure class I–IV, Likert pain 0–10, Gleason score 6–10, ASA physical-status class I–V.
Valid ops — median, order statistics, rank-based tests. Means are common but technically a stretch.
The data type dictates the analysis you can do and the plots that make sense — means are nonsense on nominal codes; bar charts are wrong for continuous values. Picking the wrong tool is the most common beginner mistake.
07 / Summaries
Descriptive Statistics
- Centre — mean, median, mode. The mean is sensitive to outliers; the median is robust. Use median for skewed data like income or length-of-stay.
- Spread — range, std, variance, IQR. Two groups can share the same mean but differ wildly in spread.
- Position — percentiles & quartiles. The 95th percentile of blood pressure is clinically more useful than the mean.
- Always visualise too — summary numbers hide shape. Anscombe’s quartet: four datasets with identical stats, totally different plots.
import numpy as np
# 20 systolic BP readings (mmHg)
bp = np.array([118, 125, 142, 160, 134,
122, 150, 128, 137, 145,
119, 130, 141, 156, 121,
133, 138, 127, 144, 135])
print(f"mean = {bp.mean():.2f}") # 135.25
print(f"median = {np.median(bp):.2f}") # 134.50
print(f"std = {bp.std():.2f}") # 11.67
print(f"IQR = {np.percentile(bp, [25,75])}")
# 20 systolic BP readings (mmHg)
bp = np.array([118, 125, 142, 160, 134,
122, 150, 128, 137, 145,
119, 130, 141, 156, 121,
133, 138, 127, 144, 135])
print(f"mean = {bp.mean():.2f}") # 135.25
print(f"median = {np.median(bp):.2f}") # 134.50
print(f"std = {bp.std():.2f}") # 11.67
print(f"IQR = {np.percentile(bp, [25,75])}")
08 / Inference
Distributions & Significance
- Distribution — the shape of your data. The normal (bell) curve fits many biological traits (height, BP), but CRP and length-of-stay are skewed.
- Null hypothesis (H₀) — the default claim: “no difference”. A p-value is the probability of seeing your result if H₀ were true.
- p < 0.05 — a convention, not a law. With 38,000 rows, even trivial differences become “significant”. Always pair it with an effect size.
- Correlation ≠ causation — ice-cream sales and drownings both rise in summer; heat drives both. Design — not maths — establishes causation.
- Clinical vs statistical — significance asks “did it exist?”; clinical significance asks “does it matter to the patient?”
import numpy as np
from scipy import stats
# ── 1 · SHAPE — is the data symmetric? (bullet 1)
weekday = np.array([1800,1820,1770,1850,1790,
1880,1810,1840,1760,1890])
print(f"skew = {stats.skew(weekday):.2f}") # 0.23 → near-symmetric
# ── 2 · H₀ test — “no difference” (bullets 2 & 3)
weekend = np.array([1620,1580,1650,1540,1610,
1570,1690,1600,1640,1580])
t, p = stats.ttest_ind(weekday, weekend)
print(f"t = {t:.2f} p = {p:.2e}") # 10.86 2.5e-09 ⇒ reject H₀
# but how big is the effect? (bullet 5)
print(f"diff = {weekday.mean()-weekend.mean():.0f} births/day") # 213
# ── 3 · Correlation ≠ causation (bullet 4)
temp = np.array([27,28,29,29,29,28,28,28,27])
births = np.array([1750,1780,1820,1830,1810,1790,1770,1760,1750])
r, p2 = stats.pearsonr(temp, births)
print(f"r = {r:.3f} p = {p2:.4f}") # r=0.934 — strong link, but heat ≠ cause
from scipy import stats
# ── 1 · SHAPE — is the data symmetric? (bullet 1)
weekday = np.array([1800,1820,1770,1850,1790,
1880,1810,1840,1760,1890])
print(f"skew = {stats.skew(weekday):.2f}") # 0.23 → near-symmetric
# ── 2 · H₀ test — “no difference” (bullets 2 & 3)
weekend = np.array([1620,1580,1650,1540,1610,
1570,1690,1600,1640,1580])
t, p = stats.ttest_ind(weekday, weekend)
print(f"t = {t:.2f} p = {p:.2e}") # 10.86 2.5e-09 ⇒ reject H₀
# but how big is the effect? (bullet 5)
print(f"diff = {weekday.mean()-weekend.mean():.0f} births/day") # 213
# ── 3 · Correlation ≠ causation (bullet 4)
temp = np.array([27,28,29,29,29,28,28,28,27])
births = np.array([1750,1780,1820,1830,1810,1790,1770,1760,1750])
r, p2 = stats.pearsonr(temp, births)
print(f"r = {r:.3f} p = {p2:.4f}") # r=0.934 — strong link, but heat ≠ cause
09 / NumPy
NumPy — The Foundation
- Arrays — fast, vectorized containers for numerical data. Think Excel column, but in memory.
- No Loops Needed — operations apply to entire arrays at once. 10,000 patient readings? One line.
- Built-in Stats — mean, median, std, percentiles. Critical for lab values, vitals, clinical measurements.
import numpy as np
# 20 fasting glucose readings (mmol/L)
glucose = np.array([5.2, 4.8, 6.1, 5.5, 5.9,
4.7, 6.3, 5.0, 5.8, 5.3,
5.6, 4.9, 6.0, 5.4, 5.7])
print(f"mean = {glucose.mean():.2f}") # 5.48
print(f"std = {glucose.std():.2f}") # 0.48
print(f"P95 = {np.percentile(glucose, 95):.2f}") # 6.16
# 20 fasting glucose readings (mmol/L)
glucose = np.array([5.2, 4.8, 6.1, 5.5, 5.9,
4.7, 6.3, 5.0, 5.8, 5.3,
5.6, 4.9, 6.0, 5.4, 5.7])
print(f"mean = {glucose.mean():.2f}") # 5.48
print(f"std = {glucose.std():.2f}") # 0.48
print(f"P95 = {np.percentile(glucose, 95):.2f}") # 6.16
10 / Pandas
Pandas — DataFrames
- DataFrame — 2D table with named columns. The central object in data analysis.
- Read Anything — CSV, Excel, Parquet, SQL, JSON. Load from file or URL.
- Filter & Group — SQL-like operations: filter rows, group by categories, aggregate.
- Handle Dates — datetime parsing, extract year/month/day, time-series analysis.
import pandas as pd
# Load directly from web
df = pd.read_csv('https://raw.githubusercontent.com/datasets/population/master/data/population.csv')
df.head() # first rows
df.describe() # summary stats
df.groupby('Year')['Value'].sum()
# Load directly from web
df = pd.read_csv('https://raw.githubusercontent.com/datasets/population/master/data/population.csv')
df.head() # first rows
df.describe() # summary stats
df.groupby('Year')['Value'].sum()
11 / Visualization
Matplotlib & Seaborn
📈
Line Plots
Trends over time. Births per year, disease incidence, lab values over treatment course.
📊
Bar Charts
Compare categories. Births by month, drug efficacy by group, demographics.
📦
Box Plots
Show distributions. Compare decades, treatment arms, identify outliers.
📉
Histograms
Frequency distributions. Daily birth counts, patient ages, biomarker levels.
import matplotlib.pyplot as plt
months = ['Jan','Feb','Mar','Apr','May','Jun']
births = [1750, 1680, 1820, 1790, 1850, 1830]
plt.figure(figsize=(8,4))
plt.plot(months, births, marker='o', color='#0d9488')
plt.title('Avg Daily Births by Month')
plt.ylabel('Births')
plt.show()
months = ['Jan','Feb','Mar','Apr','May','Jun']
births = [1750, 1680, 1820, 1790, 1850, 1830]
plt.figure(figsize=(8,4))
plt.plot(months, births, marker='o', color='#0d9488')
plt.title('Avg Daily Births by Month')
plt.ylabel('Births')
plt.show()
12 / Dataset
Our Data: Malaysia Daily Live Births
- Source — National Registration Department (JPN), via data.gov.my
- Time Range — 1920 to 2023 (~38,000 rows)
- Columns — date, state, births (3 columns, clean data)
- Why Biomedical? — Birth demography underpins public health planning, maternal health policy, hospital staffing.
- License — CC BY 4.0 (free to use, share, adapt)
👶
13 / Setup
How We'll Work
🌐
Google Colab
No installation needed. Python, NumPy, Pandas, Matplotlib, and Seaborn all pre-installed. Runs in your browser.
⌨️
Code-Along
I'll type, you follow. Every line of code runs and produces output immediately. You'll leave with a working notebook.
✏️
Exercises
Short exercises after each section. Try it yourself, then we'll review together. No grades — just learning.
💬
Ask Anytime
Questions encouraged throughout. If something doesn't make sense, it probably doesn't make sense to others too.
14 / Recap
The Big Picture
NumPy
Fast arrays & numerical computing
Pandas
Data manipulation & analysis
Plots
Matplotlib & Seaborn for visualization
"Together, these three form the complete data analysis pipeline — from raw numbers to publication-ready figures."
🚀
Let's Begin!
Open your notebook. Let's write some Python.