Build a Patient Cohort Filter in Python

Filter and summarize synthetic patient data using pure Python fundamentals.

Introduction

30 Second Summary

Healthcare analysts spend their days filtering patient records to find specific cohorts. Finding all diabetic patients over 65, or counting conditions by gender, is the bread and butter of clinical data work.

In this project, you will build a Python script from scratch that filters and summarizes synthetic patient data. You'll write functions that mirror the SQL queries you already know (WHERE, GROUP BY, COUNT) but in pure Python.

What You'll Build

A command-line Python script that defines 10 synthetic patient records inspired by OMOP CDM fields and provides functions to filter by condition, count by demographic, and generate formatted summary reports.

By the end of this project, you'll have:

  • A patient_cohort_filter.py file with 10 synthetic patient records using OMOP CDM-inspired fields like condition_concept_id and condition_source_value.
  • A reusable filter_patients() function that works like a SQL WHERE clause, plus a count_by_condition() function that mirrors GROUP BY + COUNT.
  • A cohort_summary() function that prints a formatted clinical report with gender and condition breakdowns.
  • A GitHub repository with your code pushed and visible as a portfolio piece.
  • Secret Mission: Add age-based filtering to identify high-risk elderly diabetic cohorts.

Are there any prerequisites?

You'll need a GitHub account, VS Code installed, and Python 3 installed on your machine. If you already use VS Code with Jupyter notebooks, you're all set.

Set Up Your Project

Before writing any Python, you need a clean workspace. A dedicated project folder, a Python file to work in, and version control from the start.

In this step, you'll verify Python 3 is working on your machine, create your project folder and main script file, and initialize Git so every change you make is tracked.

In this step, get ready to:
  • Verify Python 3 is working in your terminal.
  • Create your project folder and main Python file.
  • Initialize git and set up a .gitignore file.
Verify Python works

Python 3 is the runtime that will execute your patient cohort filter script. Let's confirm it's installed and responding.

  • Open your terminal by pressing Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal (macOS) or Command Prompt (Windows) and press Enter to open it.
  • Check your Python version by running this command:
python3 --version

✔️ I see a version number

You should see something like Python 3.x.x in the output. Python is ready to go.

ⓧ Command not found

If your terminal says command not found, Python 3 is not installed or not in your system PATH.

  • Download and install Python 3 from the official site: python.org/downloads.
  • After installation, close and reopen your terminal.
  • Run python3 --version again to confirm it works.
  • Confirm Python can execute code by running this command:
python3 -c "print('hello')"

You should see hello printed in your terminal.

Seeing an error instead of hello?

Make sure you included the quotes exactly as shown. The outer quotes are double quotes and the inner quotes around hello are single quotes.

help me fix a python3 -c command that isn't printing hello

Create your project folder and file

Every project deserves its own folder. This keeps your files organized and gives Git a clear boundary for what to track.

  • Create your project folder and move into it by running these commands:
mkdir ~/patient-cohort-filter
cd ~/patient-cohort-filter
  • Create your main Python file by running:
touch patient_cohort_filter.py
  • Confirm the file exists by running:
ls

You should see patient_cohort_filter.py in the output.

  • Open the project folder in VS Code by running:
code .

VS Code opens with your patient-cohort-filter folder in the sidebar. You should see patient_cohort_filter.py listed in the Explorer panel.

VS Code didn't open?

The code command needs to be in your PATH. In VS Code, press Cmd+Shift+P (macOS) or Ctrl+Shift+P (Windows), type shell command, and select Install 'code' command in PATH. Then try code . again.

help me get the code command working in my terminal

Initialize git

Git tracks every change you make to your code. Starting with version control from the beginning means you can always undo mistakes.

  • Initialize a git repository in your project folder by running:
git init

You should see a message saying your repository has been initialized.

What does git init do?

This command creates a hidden .git folder inside your project directory. That folder is where Git stores all the history for your project. You never need to open it directly.

  • In VS Code, create a new file called .gitignore in your patient-cohort-filter folder.
  • Click the new file icon in the Explorer sidebar (or press Cmd+N on macOS / Ctrl+N on Windows, then save immediately as .gitignore).
  • Paste this content into the file:
__pycache__/
.DS_Store

What does .gitignore do?

A .gitignore file tells Git which files to completely ignore. __pycache__/ is a folder Python creates automatically to cache compiled bytecode. .DS_Store is a macOS system file that stores folder display settings.

Neither of these belongs in your repository. They're machine-specific and irrelevant to your actual code.

  • Save .gitignore by pressing Cmd+S (macOS) or Ctrl+S (Windows).

✔️ Awesome, I've got everything!

Great. Double check you've saved your .gitignore file, and make sure you see both patient_cohort_filter.py and .gitignore in the VS Code sidebar.

ⓧ I'd like to double check the full code

Here's what your .gitignore file should contain:

__pycache__/
.DS_Store

Your patient_cohort_filter.py file should be empty at this point. You'll add code to it in the next step.

Your workspace is ready. Next up, you'll define 10 synthetic patient records and learn how to access values from Python dicts.

Define Patient Data and Access Values

Your project folder is ready and VS Code is open. Now it's time to fill patient_cohort_filter.py with actual data.

In healthcare analytics, patient records live in structured tables. You'll build a simplified version of this using Python dictionaries, with field names inspired by the OMOP CDM data model. Then you'll practice pulling values out of those dicts, the same way you'd SELECT columns in SQL.

In this step, get ready to:
  • Define 10 synthetic patient records as Python dictionaries.
  • Access individual patient values using dict key syntax.
  • Practice chained index and key access in a single expression.
Build the patient registry

In a real clinical database, patient data lives in relational tables like person and condition_occurrence. Here you'll represent each patient as a Python dictionary, with keys matching simplified OMOP CDM fields.

  • In VS Code, open patient_cohort_filter.py from the Explorer sidebar.
  • Copy the following patient data into the file:
# patient_cohort_filter.py
# Week 1 Project: Patient Cohort Filter
# Filters and summarizes synthetic patient data using pure Python

# --- DATA ---
# Synthetic patients inspired by OMOP CDM person + condition_occurrence tables
# Each dict combines person demographics with their primary condition

patients = [
    {"person_id": 1, "given_name": "Arjun", "family_name": "Shah", "year_of_birth": 1965, "gender": "M", "condition_concept_id": 201826, "condition_source_value": "E11.9", "condition_name": "Type 2 diabetes mellitus"},
    {"person_id": 2, "given_name": "Priya", "family_name": "Mehta", "year_of_birth": 1978, "gender": "F", "condition_concept_id": 320128, "condition_source_value": "I10", "condition_name": "Essential hypertension"},
    {"person_id": 3, "given_name": "Ravi", "family_name": "Kumar", "year_of_birth": 1952, "gender": "M", "condition_concept_id": 201826, "condition_source_value": "E11.9", "condition_name": "Type 2 diabetes mellitus"},
    {"person_id": 4, "given_name": "Sneha", "family_name": "Patel", "year_of_birth": 1988, "gender": "F", "condition_concept_id": 4229881, "condition_source_value": "J45.20", "condition_name": "Mild intermittent asthma"},
    {"person_id": 5, "given_name": "Vikram", "family_name": "Singh", "year_of_birth": 1970, "gender": "M", "condition_concept_id": 320128, "condition_source_value": "I10", "condition_name": "Essential hypertension"},
    {"person_id": 6, "given_name": "Ananya", "family_name": "Reddy", "year_of_birth": 1945, "gender": "F", "condition_concept_id": 201826, "condition_source_value": "E11.9", "condition_name": "Type 2 diabetes mellitus"},
    {"person_id": 7, "given_name": "Karthik", "family_name": "Nair", "year_of_birth": 1983, "gender": "M", "condition_concept_id": 4229881, "condition_source_value": "J45.20", "condition_name": "Mild intermittent asthma"},
    {"person_id": 8, "given_name": "Meera", "family_name": "Iyer", "year_of_birth": 1960, "gender": "F", "condition_concept_id": 320128, "condition_source_value": "I10", "condition_name": "Essential hypertension"},
    {"person_id": 9, "given_name": "Rohit", "family_name": "Sharma", "year_of_birth": 1975, "gender": "M", "condition_concept_id": 201826, "condition_source_value": "E11.9", "condition_name": "Type 2 diabetes mellitus"},
    {"person_id": 10, "given_name": "Divya", "family_name": "Gupta", "year_of_birth": 1992, "gender": "F", "condition_concept_id": 4229881, "condition_source_value": "J45.20", "condition_name": "Mild intermittent asthma"},
]

What does this code do?

You've created a Python list called patients containing 10 dictionaries. Each dictionary represents one patient with 8 key-value pairs.

  • person_id is the unique identifier (like a primary key in SQL).
  • condition_concept_id is the OMOP standard concept ID for the condition.
  • condition_source_value holds the ICD-10 code (the real-world clinical code like E11.9 for diabetes).
  • Three conditions are represented: Type 2 diabetes (concept 201826), Essential hypertension (concept 320128), and Mild intermittent asthma (concept 4229881).

Why OMOP field names?

In real healthcare analytics, you'll work with OMOP CDM tables daily. Using field names like condition_concept_id and condition_source_value now builds familiarity with the data model you'll encounter in production.

The data here is simplified. In a real OMOP database, person and condition_occurrence would be separate tables joined on person_id. You'll work with that relational structure later when you move to pandas.

  • Save patient_cohort_filter.py.
  • Confirm there are no syntax errors by running this in your terminal:
python3 patient_cohort_filter.py

You should see no output and no errors. The script defines data but doesn't print anything yet.

Seeing a SyntaxError?

  • Check that every dictionary entry has matching curly braces {} and that each key-value pair is separated by a comma.
  • Make sure the list ends with a closing ] after the last dictionary.
  • Verify all string values use matching double quotes.

Why is my patient registry causing a SyntaxError?

Access individual values

A Python dictionary stores data as key-value pairs. To read a value, you use the key name in square brackets. This is the Python equivalent of a SQL SELECT column statement.

  • In patient_cohort_filter.py, add the following code below the closing ] of your patients list:
# --- ACCESSING DICT VALUES ---
# SQL equivalent: SELECT given_name, family_name, condition_name FROM patients LIMIT 1

first_patient = patients[0]
print(f"First patient: {first_patient['given_name']} {first_patient['family_name']}")
print(f"Condition: {first_patient['condition_name']}")
print(f"ICD-10 code: {first_patient['condition_source_value']}")
print()

What does this code do?

  • patients[0] grabs the first item from the list (Python lists start counting at 0). This is like SELECT * FROM patients LIMIT 1.
  • first_patient["given_name"] reads the value stored under the given_name key in that dictionary. This is like SELECT given_name.
  • The f-string f"...{variable}..." lets you embed values directly inside a printed string.
  • Save patient_cohort_filter.py.
  • Run the script in your terminal:
python3 patient_cohort_filter.py

You should see output like this:

First patient: Arjun Shah
Condition: Type 2 diabetes mellitus
ICD-10 code: E11.9

Seeing a KeyError?

  • A KeyError means the key name you typed doesn't match any key in the dictionary. Check for typos in your key names (e.g. given_name not givenName).
  • Make sure you're using square brackets ["key"] and not parentheses ("key") to access dict values.

Help me fix my KeyError when accessing patient dict values.

Practice nested access

You can chain list indexing and dict key access in one expression. This is the Python equivalent of SELECT condition_source_value FROM patients LIMIT 1 OFFSET 2.

  • Add this code below the print() line you just wrote:
# Access 3rd patient's ICD-10 code directly
print(f"Patient 3 ICD-10: {patients[2]['condition_source_value']}")
print()

What does this code do?

patients[2] grabs the third patient from the list (index 2 because counting starts at 0). Then ["condition_source_value"] digs into that patient's dictionary to pull the ICD-10 code. All in one expression, no intermediate variable needed.

  • Save patient_cohort_filter.py and run it again:
python3 patient_cohort_filter.py

You should now see the original output plus a new line showing Patient 3 ICD-10: E11.9.

Seeing an IndexError?

  • An IndexError means you tried to access a position that doesn't exist in the list. Make sure you're using patients[2] (not patients[3]) for the third patient.
  • Confirm your patients list has all 10 entries. If you accidentally deleted one, the index might be out of range.

Help me fix an IndexError when accessing my patients list.

✔️ Awesome, I've got everything!

Great. Double check you've saved your file before moving on.

ⓧ I'd like to double check the full code

Here's what your complete patient_cohort_filter.py should look like at this point:

# patient_cohort_filter.py
# Week 1 Project: Patient Cohort Filter
# Filters and summarizes synthetic patient data using pure Python

# --- DATA ---
# Synthetic patients inspired by OMOP CDM person + condition_occurrence tables
# Each dict combines person demographics with their primary condition

patients = [
    {"person_id": 1, "given_name": "Arjun", "family_name": "Shah", "year_of_birth": 1965, "gender": "M", "condition_concept_id": 201826, "condition_source_value": "E11.9", "condition_name": "Type 2 diabetes mellitus"},
    {"person_id": 2, "given_name": "Priya", "family_name": "Mehta", "year_of_birth": 1978, "gender": "F", "condition_concept_id": 320128, "condition_source_value": "I10", "condition_name": "Essential hypertension"},
    {"person_id": 3, "given_name": "Ravi", "family_name": "Kumar", "year_of_birth": 1952, "gender": "M", "condition_concept_id": 201826, "condition_source_value": "E11.9", "condition_name": "Type 2 diabetes mellitus"},
    {"person_id": 4, "given_name": "Sneha", "family_name": "Patel", "year_of_birth": 1988, "gender": "F", "condition_concept_id": 4229881, "condition_source_value": "J45.20", "condition_name": "Mild intermittent asthma"},
    {"person_id": 5, "given_name": "Vikram", "family_name": "Singh", "year_of_birth": 1970, "gender": "M", "condition_concept_id": 320128, "condition_source_value": "I10", "condition_name": "Essential hypertension"},
    {"person_id": 6, "given_name": "Ananya", "family_name": "Reddy", "year_of_birth": 1945, "gender": "F", "condition_concept_id": 201826, "condition_source_value": "E11.9", "condition_name": "Type 2 diabetes mellitus"},
    {"person_id": 7, "given_name": "Karthik", "family_name": "Nair", "year_of_birth": 1983, "gender": "M", "condition_concept_id": 4229881, "condition_source_value": "J45.20", "condition_name": "Mild intermittent asthma"},
    {"person_id": 8, "given_name": "Meera", "family_name": "Iyer", "year_of_birth": 1960, "gender": "F", "condition_concept_id": 320128, "condition_source_value": "I10", "condition_name": "Essential hypertension"},
    {"person_id": 9, "given_name": "Rohit", "family_name": "Sharma", "year_of_birth": 1975, "gender": "M", "condition_concept_id": 201826, "condition_source_value": "E11.9", "condition_name": "Type 2 diabetes mellitus"},
    {"person_id": 10, "given_name": "Divya", "family_name": "Gupta", "year_of_birth": 1992, "gender": "F", "condition_concept_id": 4229881, "condition_source_value": "J45.20", "condition_name": "Mild intermittent asthma"},
]


# --- ACCESSING DICT VALUES ---
# SQL equivalent: SELECT given_name, family_name, condition_name FROM patients LIMIT 1

first_patient = patients[0]
print(f"First patient: {first_patient['given_name']} {first_patient['family_name']}")
print(f"Condition: {first_patient['condition_name']}")
print(f"ICD-10 code: {first_patient['condition_source_value']}")
print()

# Access 3rd patient's ICD-10 code directly
print(f"Patient 3 ICD-10: {patients[2]['condition_source_value']}")
print()

You've got your patient data defined and you can pull values out of it. Next up, you'll write loops and functions that filter this data by condition, just like a SQL WHERE clause.

Filter Patients by Condition

You can access individual patients and their fields. Now it's time to ask a bigger question: which patients have a specific condition?

In SQL, you'd write SELECT * FROM patients WHERE condition_concept_id = 201826. In Python, you need to loop through the list and check each dict yourself. This step teaches you three ways to do it, from explicit to compact to reusable.

In this step, get ready to:
  • Filter patients using a for loop (the Python WHERE clause).
  • Refactor the filter into a one-line list comprehension.
  • Write a reusable filter function you can call with any field and value.
Filter with a for loop

The most explicit way to filter in Python is a for loop that checks each item and appends matches to a new list. This is exactly what SQL does behind the scenes when it runs a WHERE clause.

  • In VS Code, open patient_cohort_filter.py.
  • Scroll to the bottom of the file (after the existing print() line) and add this filtering code:
# --- FILTERING WITH A FOR LOOP ---
# SQL equivalent: SELECT * FROM patients WHERE condition_concept_id = 201826

diabetes_concept_id = 201826

diabetes_patients = []
for patient in patients:
    if patient["condition_concept_id"] == diabetes_concept_id:
        diabetes_patients.append(patient)

print(f"Patients with Type 2 Diabetes (concept {diabetes_concept_id}): {len(diabetes_patients)}")
for p in diabetes_patients:
    print(f"  - {p['given_name']} {p['family_name']} (born {p['year_of_birth']})")
print()

What does this code do?

This is the Python equivalent of SELECT * FROM patients WHERE condition_concept_id = 201826.

  • diabetes_concept_id = 201826 stores the OMOP concept ID for Type 2 Diabetes so you can reference it without repeating the number.
  • diabetes_patients = [] creates an empty list to collect matches.
  • The for loop checks every patient's condition_concept_id. If it matches, .append() adds that patient to the results list.
  • The second for loop prints each matched patient's name and birth year.
  • Save patient_cohort_filter.py.
  • Run your script by entering this command in your terminal:
python3 patient_cohort_filter.py
  • You should see Patients with Type 2 Diabetes (concept 201826): 4 followed by four patient names (Arjun, Ravi, Ananya, Rohit).

Seeing 0 patients or a KeyError?

  • If you see 0 patients, check that diabetes_concept_id is set to 201826 (not a string like "201826"). The comparison uses integers.
  • If you see a KeyError, make sure the key is spelled exactly condition_concept_id with underscores.

Why is my diabetes filter returning 0 patients?

Refactor to a list comprehension

The for loop works, but Python has a more compact syntax for this exact pattern. A list comprehension does the same loop-check-append in a single line.

  • Add this code below the existing for loop's print() line:
# --- FILTERING WITH A LIST COMPREHENSION ---
# Same query, one line

diabetes_patients_v2 = [p for p in patients if p["condition_concept_id"] == diabetes_concept_id]
print(f"List comprehension result: {len(diabetes_patients_v2)} patients")
print()

What does this code do?

The list comprehension structure is: [what_to_keep for item in collection if condition].

  • p is what you keep (the entire patient dict).
  • for p in patients iterates over every patient.
  • if p["condition_concept_id"] == diabetes_concept_id is the filter condition.

It produces the same result as the for loop above, just in one line. Think of it as Python's shorthand for loop-check-append.

  • Save patient_cohort_filter.py and run the script again.
  • You should see List comprehension result: 4 patients in the output. The same 4 patients, filtered with one line instead of four.

Getting a SyntaxError?

  • Make sure the entire comprehension is on one line between the square brackets [ ].
  • Check that you have both opening and closing brackets. Missing either one causes a syntax error.

Help me fix my list comprehension syntax

Write a reusable filter function

Your list comprehension works for diabetes, but what if you want to filter by gender, or by a different condition? Instead of writing a new comprehension every time, you can wrap the logic in a function with parameters for the field and value.

  • Add this function definition and test call below your list comprehension code:
# --- REUSABLE FILTER FUNCTION ---
# SQL equivalent: SELECT * FROM patients WHERE {field} = {value}

def filter_patients(patient_list, field, value):
    """Filter patients where field equals value. Like a WHERE clause."""
    return [p for p in patient_list if p[field] == value]


female_patients = filter_patients(patients, "gender", "F")
print(f"Female patients: {len(female_patients)}")
for p in female_patients:
    print(f"  - {p['given_name']} {p['family_name']}")
print()

What does this code do?

This function is your reusable WHERE clause. Instead of hardcoding the field and value, you pass them as parameters.

  • def filter_patients(patient_list, field, value) defines a function that takes any patient list, any field name, and any value to match.
  • return [p for p in patient_list if p[field] == value] uses the list comprehension pattern you just learned, but with variable field and value.
  • You can now call filter_patients(patients, "gender", "F") for female patients, or filter_patients(patients, "condition_concept_id", 320128) for hypertension patients.

Why a function instead of repeating the comprehension?

In SQL, you write a WHERE clause once and swap the column/value each time you run the query. The filter_patients function works the same way. Write the filtering logic once, then reuse it with different arguments.

  • Save patient_cohort_filter.py and run the script:
python3 patient_cohort_filter.py
  • You should see Female patients: 5 followed by five names (Priya, Sneha, Ananya, Meera, Divya).

Seeing a NameError for filter_patients?

  • Make sure the function definition (def filter_patients) appears above the line that calls it. Python reads top to bottom.
  • Check that the def line has a colon at the end and the return line is indented with 4 spaces.

Help me debug my filter_patients function

✔️ Awesome, I've got everything!

Great. Double check you've saved your file before moving on.

ⓧ I'd like to double check the full code

Here is the complete patient_cohort_filter.py at this point. Compare it with yours and fix any differences.

# patient_cohort_filter.py
# Week 1 Project: Patient Cohort Filter
# Filters and summarizes synthetic patient data using pure Python

# --- DATA ---
# Synthetic patients inspired by OMOP CDM person + condition_occurrence tables
# Each dict combines person demographics with their primary condition

patients = [
    {"person_id": 1, "given_name": "Arjun", "family_name": "Shah", "year_of_birth": 1965, "gender": "M", "condition_concept_id": 201826, "condition_source_value": "E11.9", "condition_name": "Type 2 diabetes mellitus"},
    {"person_id": 2, "given_name": "Priya", "family_name": "Mehta", "year_of_birth": 1978, "gender": "F", "condition_concept_id": 320128, "condition_source_value": "I10", "condition_name": "Essential hypertension"},
    {"person_id": 3, "given_name": "Ravi", "family_name": "Kumar", "year_of_birth": 1952, "gender": "M", "condition_concept_id": 201826, "condition_source_value": "E11.9", "condition_name": "Type 2 diabetes mellitus"},
    {"person_id": 4, "given_name": "Sneha", "family_name": "Patel", "year_of_birth": 1988, "gender": "F", "condition_concept_id": 4229881, "condition_source_value": "J45.20", "condition_name": "Mild intermittent asthma"},
    {"person_id": 5, "given_name": "Vikram", "family_name": "Singh", "year_of_birth": 1970, "gender": "M", "condition_concept_id": 320128, "condition_source_value": "I10", "condition_name": "Essential hypertension"},
    {"person_id": 6, "given_name": "Ananya", "family_name": "Reddy", "year_of_birth": 1945, "gender": "F", "condition_concept_id": 201826, "condition_source_value": "E11.9", "condition_name": "Type 2 diabetes mellitus"},
    {"person_id": 7, "given_name": "Karthik", "family_name": "Nair", "year_of_birth": 1983, "gender": "M", "condition_concept_id": 4229881, "condition_source_value": "J45.20", "condition_name": "Mild intermittent asthma"},
    {"person_id": 8, "given_name": "Meera", "family_name": "Iyer", "year_of_birth": 1960, "gender": "F", "condition_concept_id": 320128, "condition_source_value": "I10", "condition_name": "Essential hypertension"},
    {"person_id": 9, "given_name": "Rohit", "family_name": "Sharma", "year_of_birth": 1975, "gender": "M", "condition_concept_id": 201826, "condition_source_value": "E11.9", "condition_name": "Type 2 diabetes mellitus"},
    {"person_id": 10, "given_name": "Divya", "family_name": "Gupta", "year_of_birth": 1992, "gender": "F", "condition_concept_id": 4229881, "condition_source_value": "J45.20", "condition_name": "Mild intermittent asthma"},
]


# --- ACCESSING DICT VALUES ---
# SQL equivalent: SELECT given_name, family_name, condition_name FROM patients LIMIT 1

first_patient = patients[0]
print(f"First patient: {first_patient['given_name']} {first_patient['family_name']}")
print(f"Condition: {first_patient['condition_name']}")
print(f"ICD-10 code: {first_patient['condition_source_value']}")
print()

# Access 3rd patient's ICD-10 code directly
print(f"Patient 3 ICD-10: {patients[2]['condition_source_value']}")
print()


# --- FILTERING WITH A FOR LOOP ---
# SQL equivalent: SELECT * FROM patients WHERE condition_concept_id = 201826

diabetes_concept_id = 201826

diabetes_patients = []
for patient in patients:
    if patient["condition_concept_id"] == diabetes_concept_id:
        diabetes_patients.append(patient)

print(f"Patients with Type 2 Diabetes (concept {diabetes_concept_id}): {len(diabetes_patients)}")
for p in diabetes_patients:
    print(f"  - {p['given_name']} {p['family_name']} (born {p['year_of_birth']})")
print()


# --- FILTERING WITH A LIST COMPREHENSION ---
# Same query, one line

diabetes_patients_v2 = [p for p in patients if p["condition_concept_id"] == diabetes_concept_id]
print(f"List comprehension result: {len(diabetes_patients_v2)} patients")
print()


# --- REUSABLE FILTER FUNCTION ---
# SQL equivalent: SELECT * FROM patients WHERE {field} = {value}

def filter_patients(patient_list, field, value):
    """Filter patients where field equals value. Like a WHERE clause."""
    return [p for p in patient_list if p[field] == value]


female_patients = filter_patients(patients, "gender", "F")
print(f"Female patients: {len(female_patients)}")
for p in female_patients:
    print(f"  - {p['given_name']} {p['family_name']}")
print()

You now have a reusable function that can filter patients by any field. Next up, you'll build counting and summary functions to answer questions like "how many patients have each condition?" and produce formatted reports.

Count, Summarize, and Combine

In Step 3, you wrote a reusable filter_patients function that acts like a SQL WHERE clause. Filtering is only half the story. In real clinical analytics, you also need to count patients by condition and produce formatted summary reports.

In this step, you'll write functions that aggregate and summarize your data. You'll also chain your existing filter function to apply multiple conditions at once, the Python equivalent of SQL's AND operator.

In this step, get ready to:
  • Write a counting function that groups patients by condition.
  • Combine multiple filters to narrow down specific cohorts.
  • Build a summary report function that outputs formatted percentages.
Write the count_by_condition function

Counting how many patients have each condition is something you'd do with GROUP BY condition_name plus COUNT(*) in SQL. In Python, you build this manually using a dictionary that accumulates counts as you loop through the list.

  • In patient_cohort_filter.py, scroll to the bottom of the file (below the female_patients print loop).
  • Add the following code to define your counting function and print the results:

# --- COUNTING FUNCTION ---
# SQL equivalent: SELECT condition_name, COUNT(*) FROM patients GROUP BY condition_name

def count_by_condition(patient_list):
    """Count patients per condition. Returns a dict {condition_name: count}."""
    counts = {}
    for patient in patient_list:
        condition = patient["condition_name"]
        if condition in counts:
            counts[condition] = counts[condition] + 1
        else:
            counts[condition] = 1
    return counts


condition_counts = count_by_condition(patients)
print("Condition counts:")
for condition, count in condition_counts.items():
    print(f"  {condition}: {count}")
print()

What does this code do?

The function starts with an empty dict called counts. For each patient, it grabs the condition_name and checks if that condition is already a key in the dict.

If the condition already exists as a key, it increments the count by 1. If not, it creates the key with a starting count of 1. This is the classic "accumulator" pattern for building tallies.

The for condition, count in condition_counts.items() loop unpacks each key-value pair so you can print them neatly.

  • Save patient_cohort_filter.py.
  • Run the script by entering this command in your terminal:
python3 patient_cohort_filter.py
  • Scroll to the end of the output. You should see condition counts showing Type 2 diabetes mellitus: 4, Essential hypertension: 3, and Mild intermittent asthma: 3.

Seeing a KeyError or unexpected counts?

  • A KeyError means you're using square bracket access on a key that doesn't exist. Check that you're accessing patient["condition_name"] (not patient["condition"]).
  • If your counts are all 1, make sure the if condition in counts check is indented inside the for loop, not outside it.
  • Still stuck? Help me debug my count_by_condition function
Combine filters for multi-condition queries

In SQL, you combine conditions with AND: WHERE gender = 'M' AND condition_concept_id = 201826. In Python, you achieve the same thing by nesting one filter_patients call inside another.

  • Below the condition_counts print loop you just added, add this code to filter for male diabetic patients:

# --- COMBINING FILTERS ---
# SQL equivalent: SELECT * FROM patients WHERE gender = 'M' AND condition_concept_id = 201826

male_diabetic = filter_patients(
    filter_patients(patients, "gender", "M"),
    "condition_concept_id",
    diabetes_concept_id
)
print(f"Male patients with diabetes: {len(male_diabetic)}")
for p in male_diabetic:
    print(f"  - {p['given_name']} {p['family_name']}")
print()

What does this code do?

The inner call filter_patients(patients, "gender", "M") returns only male patients. That filtered list is passed directly into the outer call, which filters again for condition_concept_id == 201826.

The result is patients who match BOTH conditions. This is function composition. Each filter narrows the list further, just like stacking WHERE clauses in SQL.

  • Save patient_cohort_filter.py and run it again:
python3 patient_cohort_filter.py
  • You should see Male patients with diabetes: 3 followed by the names Arjun Shah, Ravi Kumar, and Rohit Sharma.

Seeing 0 patients or a TypeError?

  • If you get 0 patients, check that the inner filter uses "gender" and "M" (capital M, matching the data).
  • Make sure you're using the variable diabetes_concept_id (defined earlier in the file as 201826), not a string.
  • Still stuck? Help me debug my combined filter
Write the cohort_summary function

A cohort summary is the report you'd hand to a clinical researcher. It shows the total patient count, gender breakdown with percentages, and condition breakdown with percentages. Think of it as a stored procedure that prints a formatted report.

  • Below the male_diabetic print loop, add the summary report function and call it on the full patient list:

# --- SUMMARY REPORT FUNCTION ---

def cohort_summary(patient_list):
    """Print a summary report for a list of patients."""
    total = len(patient_list)
    if total == 0:
        print("No patients in cohort.")
        return

    gender_counts = {}
    for p in patient_list:
        g = p["gender"]
        if g in gender_counts:
            gender_counts[g] = gender_counts[g] + 1
        else:
            gender_counts[g] = 1

    condition_counts = count_by_condition(patient_list)

    print("=" * 40)
    print("COHORT SUMMARY REPORT")
    print("=" * 40)
    print(f"Total patients: {total}")
    print()
    print("Gender breakdown:")
    for gender, count in gender_counts.items():
        label = "Male" if gender == "M" else "Female"
        print(f"  {label}: {count} ({count / total * 100:.1f}%)")
    print()
    print("Condition breakdown:")
    for condition, count in condition_counts.items():
        print(f"  {condition}: {count} ({count / total * 100:.1f}%)")
    print("=" * 40)


cohort_summary(patients)

What does this code do?

The function first counts total patients with len(patient_list). If the list is empty, it prints a message and returns early so the percentage math doesn't divide by zero.

It builds a gender tally using the same if/else accumulator pattern you used in count_by_condition. Then it reuses count_by_condition directly for the condition breakdown. Functions calling functions keeps your code DRY.

The f-string f"{count / total * 100:.1f}%" formats the percentage to one decimal place. The :.1f part means "one digit after the decimal, float format."

  • Save patient_cohort_filter.py and run it one more time:
python3 patient_cohort_filter.py
  • At the bottom of the output, you should see a formatted report with a line of = characters, total patients showing 10, gender and condition breakdowns with percentages.

Seeing a NameError for count_by_condition?

  • The cohort_summary function calls count_by_condition internally. Make sure count_by_condition is defined earlier in the file (above cohort_summary).
  • If your percentages look wrong, double-check that you're dividing count / total * 100 (not count / total without the * 100).
  • Still stuck? Help me debug my cohort_summary function

✔️ Awesome, I've got everything!

Great. Double check you've saved your file before moving on.

ⓧ I'd like to double check the full code

Here is the full patient_cohort_filter.py file at this point. Compare it against yours:

# patient_cohort_filter.py
# Week 1 Project: Patient Cohort Filter
# Filters and summarizes synthetic patient data using pure Python

# --- DATA ---
# Synthetic patients inspired by OMOP CDM person + condition_occurrence tables
# Each dict combines person demographics with their primary condition

patients = [
    {"person_id": 1, "given_name": "Arjun", "family_name": "Shah", "year_of_birth": 1965, "gender": "M", "condition_concept_id": 201826, "condition_source_value": "E11.9", "condition_name": "Type 2 diabetes mellitus"},
    {"person_id": 2, "given_name": "Priya", "family_name": "Mehta", "year_of_birth": 1978, "gender": "F", "condition_concept_id": 320128, "condition_source_value": "I10", "condition_name": "Essential hypertension"},
    {"person_id": 3, "given_name": "Ravi", "family_name": "Kumar", "year_of_birth": 1952, "gender": "M", "condition_concept_id": 201826, "condition_source_value": "E11.9", "condition_name": "Type 2 diabetes mellitus"},
    {"person_id": 4, "given_name": "Sneha", "family_name": "Patel", "year_of_birth": 1988, "gender": "F", "condition_concept_id": 4229881, "condition_source_value": "J45.20", "condition_name": "Mild intermittent asthma"},
    {"person_id": 5, "given_name": "Vikram", "family_name": "Singh", "year_of_birth": 1970, "gender": "M", "condition_concept_id": 320128, "condition_source_value": "I10", "condition_name": "Essential hypertension"},
    {"person_id": 6, "given_name": "Ananya", "family_name": "Reddy", "year_of_birth": 1945, "gender": "F", "condition_concept_id": 201826, "condition_source_value": "E11.9", "condition_name": "Type 2 diabetes mellitus"},
    {"person_id": 7, "given_name": "Karthik", "family_name": "Nair", "year_of_birth": 1983, "gender": "M", "condition_concept_id": 4229881, "condition_source_value": "J45.20", "condition_name": "Mild intermittent asthma"},
    {"person_id": 8, "given_name": "Meera", "family_name": "Iyer", "year_of_birth": 1960, "gender": "F", "condition_concept_id": 320128, "condition_source_value": "I10", "condition_name": "Essential hypertension"},
    {"person_id": 9, "given_name": "Rohit", "family_name": "Sharma", "year_of_birth": 1975, "gender": "M", "condition_concept_id": 201826, "condition_source_value": "E11.9", "condition_name": "Type 2 diabetes mellitus"},
    {"person_id": 10, "given_name": "Divya", "family_name": "Gupta", "year_of_birth": 1992, "gender": "F", "condition_concept_id": 4229881, "condition_source_value": "J45.20", "condition_name": "Mild intermittent asthma"},
]


# --- ACCESSING DICT VALUES ---
# SQL equivalent: SELECT given_name, family_name, condition_name FROM patients LIMIT 1

first_patient = patients[0]
print(f"First patient: {first_patient['given_name']} {first_patient['family_name']}")
print(f"Condition: {first_patient['condition_name']}")
print(f"ICD-10 code: {first_patient['condition_source_value']}")
print()

# Access 3rd patient's ICD-10 code directly
print(f"Patient 3 ICD-10: {patients[2]['condition_source_value']}")
print()


# --- FILTERING WITH A FOR LOOP ---
# SQL equivalent: SELECT * FROM patients WHERE condition_concept_id = 201826

diabetes_concept_id = 201826

diabetes_patients = []
for patient in patients:
    if patient["condition_concept_id"] == diabetes_concept_id:
        diabetes_patients.append(patient)

print(f"Patients with Type 2 Diabetes (concept {diabetes_concept_id}): {len(diabetes_patients)}")
for p in diabetes_patients:
    print(f"  - {p['given_name']} {p['family_name']} (born {p['year_of_birth']})")
print()


# --- FILTERING WITH A LIST COMPREHENSION ---
# Same query, one line

diabetes_patients_v2 = [p for p in patients if p["condition_concept_id"] == diabetes_concept_id]
print(f"List comprehension result: {len(diabetes_patients_v2)} patients")
print()


# --- REUSABLE FILTER FUNCTION ---
# SQL equivalent: SELECT * FROM patients WHERE {field} = {value}

def filter_patients(patient_list, field, value):
    """Filter patients where field equals value. Like a WHERE clause."""
    return [p for p in patient_list if p[field] == value]


female_patients = filter_patients(patients, "gender", "F")
print(f"Female patients: {len(female_patients)}")
for p in female_patients:
    print(f"  - {p['given_name']} {p['family_name']}")
print()


# --- COUNTING FUNCTION ---
# SQL equivalent: SELECT condition_name, COUNT(*) FROM patients GROUP BY condition_name

def count_by_condition(patient_list):
    """Count patients per condition. Returns a dict {condition_name: count}."""
    counts = {}
    for patient in patient_list:
        condition = patient["condition_name"]
        if condition in counts:
            counts[condition] = counts[condition] + 1
        else:
            counts[condition] = 1
    return counts


condition_counts = count_by_condition(patients)
print("Condition counts:")
for condition, count in condition_counts.items():
    print(f"  {condition}: {count}")
print()


# --- COMBINING FILTERS ---
# SQL equivalent: SELECT * FROM patients WHERE gender = 'M' AND condition_concept_id = 201826

male_diabetic = filter_patients(
    filter_patients(patients, "gender", "M"),
    "condition_concept_id",
    diabetes_concept_id
)
print(f"Male patients with diabetes: {len(male_diabetic)}")
for p in male_diabetic:
    print(f"  - {p['given_name']} {p['family_name']}")
print()


# --- SUMMARY REPORT FUNCTION ---

def cohort_summary(patient_list):
    """Print a summary report for a list of patients."""
    total = len(patient_list)
    if total == 0:
        print("No patients in cohort.")
        return

    gender_counts = {}
    for p in patient_list:
        g = p["gender"]
        if g in gender_counts:
            gender_counts[g] = gender_counts[g] + 1
        else:
            gender_counts[g] = 1

    condition_counts = count_by_condition(patient_list)

    print("=" * 40)
    print("COHORT SUMMARY REPORT")
    print("=" * 40)
    print(f"Total patients: {total}")
    print()
    print("Gender breakdown:")
    for gender, count in gender_counts.items():
        label = "Male" if gender == "M" else "Female"
        print(f"  {label}: {count} ({count / total * 100:.1f}%)")
    print()
    print("Condition breakdown:")
    for condition, count in condition_counts.items():
        print(f"  {condition}: {count} ({count / total * 100:.1f}%)")
    print("=" * 40)


cohort_summary(patients)

Your script now filters, counts, and summarizes patient data from scratch. Next up, you'll push this code to GitHub and create a portfolio piece that proves you can write Python without an LLM crutch.

Push to GitHub

Your patient cohort filter is complete. You have a script that filters, counts, and summarizes clinical data using the same patterns you know from SQL.

The last step is to push your code to GitHub so it lives in a remote repository. This gives you a shareable link and version history for your work.

In this step, get ready to:
  • Create a new repository on GitHub.
  • Commit and push your code to the remote repository.
  • Verify your files are visible on GitHub.
Create the repo on GitHub

You need a remote repository on GitHub to receive your local code. You'll create an empty one so there are no conflicts when you push.

  • Open your browser and go to github.com.
  • In the upper-right corner of the page, click the + icon, then click New repository.
  • Type patient-cohort-filter in the Repository name field.
  • Leave the repository set to Public.
  • Do NOT check any of the initialization options (no README, no .gitignore, no license). You already have these files locally.
  • Click Create repository.

Why leave the repo empty?

If you initialize with a README or .gitignore on GitHub, the remote repository will have a commit that your local repository does not. This causes a merge conflict on your first push. Starting empty means GitHub is ready to accept whatever you push from your machine.

GitHub shows you a Quick Setup page after creation. You can leave this tab open for the next substep.

Connect and push your code

Now you'll stage your files, create a commit, connect your local repo to GitHub, and push. This is the standard Git workflow: add, commit, push.

  • Stage all your project files by running this command:
git add .
  • Create your first commit by running:
git commit -m "feat: patient cohort filter with filter, count, and summary functions"

What does this commit message mean?

The feat: prefix is a common convention that labels this commit as a new feature. The rest describes what the commit contains. Clear commit messages make your Git history readable.

  • Connect your local repository to the GitHub remote by running:
git remote add origin https://github.com/[[YOUR_USERNAME="your GitHub username"]]/patient-cohort-filter.git
  • Push your code to GitHub by running:
git push -u origin main

What do these commands do?

`git add .` stages every file in the folder for the next commit. The dot means "everything in the current directory."

`git remote add origin` tells Git where the remote copy of your repo lives on GitHub. `git push -u origin main` uploads your commits to the `main` branch on that remote.

Seeing an authentication prompt?

GitHub no longer accepts passwords for HTTPS pushes. If prompted, you'll need to authenticate using a personal access token or a browser sign-in flow. Follow the prompts in your terminal to sign in with your browser.

If you see an error about `main` not existing, your Git may default to `master`. Run git branch -M main to rename your branch, then try the push again.

Help me push my code to GitHub

Verify your code on GitHub
  • Go back to your browser and refresh your GitHub repository page.
  • You should see patient_cohort_filter.py and .gitignore listed in the file view.

Your code now lives on GitHub. Anyone with the link can see your patient cohort filter and the Python skills it demonstrates.

Don't see your files?

Make sure you refreshed the page. If the page still shows the Quick Setup instructions, your push did not complete successfully. Check your terminal for error messages and try pushing again.

Why aren't my files showing up on GitHub?

Secret mission

Add Age-Based Cohort Filtering

Healthcare analysts run age-based queries every day. Can you add age calculation and range filtering to identify high-risk elderly diabetic patients? This is the kind of query that powers real clinical decision support.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally with a free GitHub repository. There are no ongoing costs or running processes to worry about.

Decide whether to keep your resources for future reference, or delete them entirely.

Resources you used:

  • Local patient-cohort-filter folder on your Desktop.
  • GitHub repository patient-cohort-filter.

Keep everything running

No action needed. Choose this if you plan to extend the script or reference it for future projects.

  • Your patient-cohort-filter folder stays on your Desktop.
  • Your GitHub repository remains online as a portfolio piece.

Pause - I'll come back to this later

Nothing is actively running, so there is nothing to pause. Your folder and GitHub repo sit idle at no cost.

  • Close VS Code if you want to free up memory.
  • Your files and repository will be exactly as you left them when you come back.

Delete - I don't want to use this again

Remove all project resources and start fresh.

  • Delete the local project folder by running this command in your terminal:
rm -rf ~/Desktop/patient-cohort-filter
  • Delete the GitHub repository:
  • Go to your patient-cohort-filter repository on GitHub.
  • Click the Settings tab at the top of the repository page.
  • Scroll down to the Danger Zone section at the bottom.
  • Click Delete this repository and follow the confirmation prompts.

Nice Work!

Nice Work!

You just wrote a Python script from scratch that filters, counts, and summarizes clinical data. No copy-pasting, no LLM crutch.

You've learned how to:

  • Filter patient data using dict access, for loops, and list comprehensions as the Python equivalents of SQL WHERE clauses.
  • Build reusable functions that count and summarize clinical cohorts using dict-building patterns (the Python equivalent of GROUP BY + COUNT).
  • Push a working script to GitHub using a basic git workflow of init, add, commit, and push.
  • Write functions that call other functions, use default parameters, and chain comparisons to identify high-risk elderly diabetic cohorts (Secret Mission).

Ready to quiz yourself?