Medical Diagnosis Bayesian Network
Build a Bayesian network that diagnoses diseases from symptoms using CPTs.
Introduction
30 Second Summary
When a doctor sees a patient with a cough and fever, they don't guess randomly. They draw on patterns from hundreds of past patients to estimate the most likely diagnosis.
In this project, you will build a medical diagnosis system that calculates disease probabilities from patient data, then uses those probabilities to diagnose new patients based on their symptoms.
What You'll Build
A Python-based Bayesian network that performs probabilistic medical diagnosis using conditional probability tables calculated from patient data.
By the end of this project, you'll have:
- A Python script that calculates conditional probabilities from raw patient counts and prints formatted CPT tables.
- A working Bayesian network with 2 diseases and 2 symptoms that can diagnose patients from their symptoms using Variable Elimination.
- A clear demonstration of the zero-probability problem and how Laplace smoothing fixes it.
- Secret Mission: Expand the network to handle 3 diseases and 3 symptoms with comparative diagnosis queries.
Are there any prerequisites?
You only need basic Python knowledge (variables, print statements, and lists). No probability or statistics background is required.
Set Up Python and pgmpy
Before you can build your medical diagnosis network, you need two things: Python (the programming language that runs your code) and pgmpy (a library designed specifically for building Bayesian networks).
In this step, you'll install both tools and create the project file where your diagnosis system will live.
In this step, get ready to:
- Install Python 3.13 and verify it works in your terminal.
- Install the pgmpy library for Bayesian networks.
- Create your project folder and file, and confirm pgmpy imports successfully.
Install Python 3.13
Python is the language your diagnosis script runs in. This project uses Python 3.13, so let's check if you have it.
- Open a terminal by pressing Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Terminal (macOS) or PowerShell (Windows) and press Enter to open it.
- Check your Python version by running this command:
macOS
python3 --version
Windows
python --version
✔️ I see version 3.13 or higher
You're all set. Move on to the next substep.
ⓧ I see an older version
Your system has an older Python that won't work for this project. You need 3.13 or higher.
On macOS, the system python3 often points to an older version even after you install a newer one. The python.org installer places the new version in /usr/local/bin which should take precedence, but you may need to close and reopen your terminal for the change to take effect.
- Download the Python 3.13 installer from python.org/downloads.
- Run the installer. On Windows, check Add python.exe to PATH on the first screen.
- Close and reopen your terminal after installation.
- Run the version check command again. You should now see 3.13 or higher.
macOS still showing the old version?
Try running python3.13 --version to confirm the new version installed. If that works, use python3.13 instead of python3 for the rest of this project.
ⓧ Command not found
Python is not installed on your machine yet. Let's install it.
- Go to python.org/downloads and download the Python 3.13 installer for your operating system.
- Run the installer. On Windows, check Add python.exe to PATH on the first screen before clicking Install Now.
- Close and reopen your terminal after installation completes.
- Run the version check command again. You should see Python 3.13.x printed.
Still seeing command not found?
On Windows, the PATH setting may not have applied. Uninstall Python from Settings > Apps, then reinstall with the Add python.exe to PATH checkbox checked.
On macOS, try python3.13 --version instead. The python.org installer places the binary in /usr/local/bin/python3.13.
help me troubleshoot my Python installation
Install pgmpy
pgmpy is the Python library you'll use to define your Bayesian network structure and run diagnostic inference queries.
- Install pgmpy by running this command in your terminal:
macOS
pip3 install pgmpy==1.1.2
Windows
pip install pgmpy==1.1.2
The installation may take a minute as it downloads pgmpy and its dependencies (numpy, pandas, etc.).
What does this command do?
pip is Python's package manager. The ==1.1.2 part pins the exact version so your results match this project's instructions.
Seeing a permission error or install failure?
- If you see a permissions error on macOS, try adding --user to the end of the command.
- If pip cannot find the package, make sure you typed pgmpy (not pgmy or pympy).
- If you see a version conflict, make sure your Python version is 3.10 or higher with the version check from the previous substep.
help me fix my pgmpy installation error
Create your project file and verify the setup
Now you'll create the folder and file where all your Bayesian network code will live.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir medical-diagnosis
cd medical-diagnosis
- Confirm the folder exists by running:
ls
You should see the medical-diagnosis folder listed. Since you already moved into it, the folder will appear empty for now.
- Open your code editor by pressing Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Visual Studio Code and press Enter to open it.
- In VS Code, click File in the top menu bar.
- Click Open Folder.
- Navigate to your Desktop and select the medical-diagnosis folder, then click Open.
- Create a new file by clicking File > New File.
- Save it immediately with Cmd+S (macOS) or Ctrl+S (Windows). Name it medical_diagnosis.py.
- Paste the following code into medical_diagnosis.py to test that pgmpy is working:
# Medical Diagnosis Bayesian Network
# Learning Conditional Probability Tables (CPTs) from scratch
from pgmpy.models import DiscreteBayesianNetwork
print("pgmpy imported successfully")
What does this code do?
- The first two lines are comments describing what this project builds.
- from pgmpy.models import DiscreteBayesianNetwork loads the class you'll use to define your network's structure in a later step.
- The print() line confirms the import worked. If pgmpy wasn't installed correctly, this line would never run.
- Save the file with Cmd+S (macOS) or Ctrl+S (Windows).
- Run the script in your terminal by running this command:
macOS
python3 medical_diagnosis.py
Windows
python medical_diagnosis.py
You should see pgmpy imported successfully printed in your terminal.
Seeing ModuleNotFoundError?
- Make sure you installed pgmpy with the same Python version you're using to run the script. If you used pip3 to install but python to run, they may point to different Python installations.
- Try running pip3 show pgmpy (macOS) or pip show pgmpy (Windows) to confirm pgmpy is installed.
- Make sure your terminal is inside the medical-diagnosis folder where medical_diagnosis.py lives.
help me fix ModuleNotFoundError for pgmpy
✔️ 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
# Medical Diagnosis Bayesian Network
# Learning Conditional Probability Tables (CPTs) from scratch
from pgmpy.models import DiscreteBayesianNetwork
print("pgmpy imported successfully")
Your environment is ready. Next up, you'll calculate conditional probability tables from real patient data by hand.
Calculate CPTs by Hand from Patient Counts
Your environment is ready. Now it's time to build the mathematical foundation of your diagnosis system.
A Conditional Probability Table (CPT) answers the question: "Given that a patient has disease X, what is the probability they show symptom Y?" You will calculate these probabilities from raw patient counts, exactly how real diagnostic systems learn from data.
In this step, get ready to:
- Calculate P(Wheeze | Asthma) from a dataset of 10 patients.
- Build multi-disease CPT tables for Asthma and Pneumonia.
- Discover why zero probabilities break inference and fix them with Laplace smoothing.
Calculate your first conditional probability
Imagine you observe 10 asthma patients and record whether they wheeze. 8 of them do, 2 don't. The conditional probability P(Wheeze=yes | Asthma=yes) is simply 8 divided by 10.
- In medical_diagnosis.py, select all existing code and replace it with the following:
# Medical Diagnosis Bayesian Network
# Learning Conditional Probability Tables (CPTs) from scratch
# ============================================================
# STEP 2: Calculate CPTs by Hand from Patient Counts
# ============================================================
print("=" * 60)
print("STEP 2.1: Basic CPT Calculation from Counts")
print("=" * 60)
# Dataset: 10 asthma patients observed for wheezing
asthma_total = 10
asthma_wheeze_yes = 8
asthma_wheeze_no = 2
# Calculate conditional probabilities
p_wheeze_yes_given_asthma = asthma_wheeze_yes / asthma_total
p_wheeze_no_given_asthma = asthma_wheeze_no / asthma_total
print(f"\nDataset: {asthma_total} asthma patients")
print(f" - {asthma_wheeze_yes} wheeze")
print(f" - {asthma_wheeze_no} don't wheeze")
print(f"\nCPT: P(Wheeze | Asthma)")
print(f" P(Wheeze=yes | Asthma=yes) = {asthma_wheeze_yes}/{asthma_total} = {p_wheeze_yes_given_asthma}")
print(f" P(Wheeze=no | Asthma=yes) = {asthma_wheeze_no}/{asthma_total} = {p_wheeze_no_given_asthma}")
print(f" Sum = {p_wheeze_yes_given_asthma + p_wheeze_no_given_asthma} (must equal 1.0)")
What does this code do?
You define a tiny dataset of 10 asthma patients, then divide the symptom count by the total to get a probability. This is the core formula behind every CPT: P(Symptom | Disease) = count / total.
The sum check at the end verifies that both outcomes (wheeze=yes, wheeze=no) add up to 1.0. Every row in a CPT must sum to exactly 1.0 because a patient either wheezes or doesn't.
- Save medical_diagnosis.py.
- Run the script by executing this command in your terminal:
python medical_diagnosis.py
- You should see a formatted CPT showing P(Wheeze=yes | Asthma=yes) = 0.8 and P(Wheeze=no | Asthma=yes) = 0.2, with a sum of 1.0.
Seeing a SyntaxError?
Make sure you replaced all the existing code (including the old from pgmpy.models import DiscreteBayesianNetwork line). The file should start with the comment # Medical Diagnosis Bayesian Network.
Also check that f-string curly braces {} are not accidentally escaped or missing.
Why isn't my CPT printing correctly?
What is a CPT?
A Conditional Probability Table is a table that has one probability for every possible combination of parent and child states. In this case, the parent is "Disease" (Asthma) and the child is "Symptom" (Wheeze). The table tells you how likely each symptom is for a given disease.
Expand to multi-disease CPT tables
A real diagnostic system tracks multiple diseases and multiple symptoms. You will now add Pneumonia as a second disease and Fever as a second symptom, then print full CPT tables for each combination.
- In medical_diagnosis.py, add the following code at the bottom of the file:
# ============================================================
print("\n" + "=" * 60)
print("STEP 2.2: Multi-Disease CPT Tables")
print("=" * 60)
# Full dataset
data = {
"Asthma": {"total": 10, "wheeze": 8, "fever": 1},
"Pneumonia": {"total": 12, "wheeze": 7, "fever": 10},
}
print("\n--- CPT: P(Wheeze | Disease) ---")
print(f"{'Disease':<12} {'P(Wheeze=yes)':<16} {'P(Wheeze=no)':<16}")
print("-" * 44)
for disease, counts in data.items():
p_yes = counts["wheeze"] / counts["total"]
p_no = 1 - p_yes
print(f"{disease:<12} {p_yes:<16.4f} {p_no:<16.4f}")
print("\n--- CPT: P(Fever | Disease) ---")
print(f"{'Disease':<12} {'P(Fever=yes)':<16} {'P(Fever=no)':<16}")
print("-" * 44)
for disease, counts in data.items():
p_yes = counts["fever"] / counts["total"]
p_no = 1 - p_yes
print(f"{disease:<12} {p_yes:<16.4f} {p_no:<16.4f}")
What does this code do?
The data dictionary stores patient counts for two diseases. Each disease entry holds the total number of patients observed and how many had each symptom.
The two for loops iterate through both diseases and calculate P(Symptom=yes | Disease) by dividing symptom count by total. Notice that Pneumonia has 10 out of 12 patients with fever (0.8333), while Asthma has only 1 out of 10 (0.1000). These differences are what let the network distinguish diseases.
- Save medical_diagnosis.py and run it again:
python medical_diagnosis.py
- You should now see two formatted CPT tables at the bottom of the output. One for Wheeze and one for Fever, each showing probabilities for Asthma and Pneumonia.
Seeing a KeyError?
Check that your data dictionary keys match exactly: "total", "wheeze", "fever". Python dictionary keys are case-sensitive.
Help me fix my multi-disease CPT tables.
Fix zero probabilities with Laplace smoothing
What happens if zero patients with Asthma ever showed a fever in your dataset? The probability P(Fever=yes | Asthma) becomes 0.0. This seems harmless, but it destroys your entire inference system.
When you multiply probabilities during diagnosis, any zero in the chain forces the entire result to zero. Your model becomes 100% certain asthma is impossible just because of a gap in your tiny sample.
- Add the following code at the bottom of medical_diagnosis.py to demonstrate the zero-probability problem:
# ============================================================
print("\n" + "=" * 60)
print("STEP 2.3: The Zero-Probability Problem & Laplace Smoothing")
print("=" * 60)
# Scenario: What if NO asthma patients had fever?
asthma_fever_zero = 0
p_fever_given_asthma_zero = asthma_fever_zero / asthma_total
print(f"\nProblem scenario: 0 out of {asthma_total} asthma patients have fever")
print(f" P(Fever=yes | Asthma=yes) = {asthma_fever_zero}/{asthma_total} = {p_fever_given_asthma_zero}")
print(f"\n WHY THIS IS DANGEROUS:")
print(f" If we multiply probabilities during inference:")
print(f" P(Asthma | Wheeze=yes, Fever=yes) involves P(Fever=yes | Asthma) = 0")
print(f" Any number * 0 = 0, so Asthma gets ZERO probability!")
print(f" The model becomes 100% certain the patient doesn't have asthma,")
print(f" just because we never SAW an asthma patient with fever in our tiny sample.")
Why is a zero probability catastrophic?
Bayesian inference multiplies probabilities together. If any single probability in the chain is 0, the entire product is 0. The model becomes absolutely certain that Asthma is impossible for any patient with fever, regardless of how strong the other evidence is.
In real medical datasets, zero counts are common. You might have 50 asthma patients in your sample and none happened to have a fever. That doesn't mean it's medically impossible. You need a way to keep small probabilities non-zero.
- Save and run your script. You should see the "WHY THIS IS DANGEROUS" explanation printed in your terminal.
Now add the fix. Laplace smoothing adds 1 to every count before calculating probabilities. This ensures no probability ever reaches zero.
- Add the calculate_cpt_with_smoothing function at the bottom of your file:
def calculate_cpt_with_smoothing(count_yes, total, num_categories=2, alpha=1):
"""
Apply Laplace smoothing to probability estimation.
Formula: P(x) = (count + alpha) / (total + alpha * num_categories)
Args:
count_yes: observed count for this outcome
total: total observations
num_categories: number of possible outcomes (e.g., 2 for yes/no)
alpha: smoothing parameter (1 = Laplace smoothing)
"""
p_yes = (count_yes + alpha) / (total + alpha * num_categories)
p_no = (total - count_yes + alpha) / (total + alpha * num_categories)
return p_yes, p_no
What does this function do?
Instead of P(x) = count / total, the formula becomes P(x) = (count + 1) / (total + 2). The "+1" in the numerator prevents any count from being zero. The "+2" in the denominator (for binary yes/no outcomes) keeps everything balanced so probabilities still sum to 1.0.
The alpha parameter controls how much smoothing to apply. When alpha=1, this is classic Laplace smoothing. The num_categories parameter accounts for how many possible outcomes exist (2 for yes/no).
- Now add the code that uses this function to demonstrate the fix and calculate smoothed CPTs for your entire dataset:
print(f"\n SOLUTION: Laplace Smoothing (alpha = 1)")
print(f" Formula: P(x) = (count + 1) / (total + 1 * num_categories)")
p_smoothed_yes, p_smoothed_no = calculate_cpt_with_smoothing(0, 10, num_categories=2, alpha=1)
print(f" P(Fever=yes | Asthma) = (0 + 1) / (10 + 1*2) = 1/12 = {p_smoothed_yes:.4f}")
print(f" P(Fever=no | Asthma) = (10 + 1) / (10 + 1*2) = 11/12 = {p_smoothed_no:.4f}")
print(f" Sum = {p_smoothed_yes + p_smoothed_no:.4f}")
print(f"\n Now Asthma has a SMALL but non-zero probability of fever.")
print(f" The model stays open-minded instead of making absolute claims.")
# Calculate all smoothed CPTs for use in the network
print("\n--- Smoothed CPTs for Network ---")
smoothed_data = {}
for disease, counts in data.items():
wheeze_yes, wheeze_no = calculate_cpt_with_smoothing(counts["wheeze"], counts["total"])
fever_yes, fever_no = calculate_cpt_with_smoothing(counts["fever"], counts["total"])
smoothed_data[disease] = {
"wheeze_yes": wheeze_yes,
"wheeze_no": wheeze_no,
"fever_yes": fever_yes,
"fever_no": fever_no,
}
print(f"\n{disease} (smoothed):")
print(f" P(Wheeze=yes) = {wheeze_yes:.4f}, P(Wheeze=no) = {wheeze_no:.4f}")
print(f" P(Fever=yes) = {fever_yes:.4f}, P(Fever=no) = {fever_no:.4f}")
What does this code do?
First, it demonstrates the fix on the zero-count scenario. P(Fever=yes | Asthma) goes from 0.0 to 0.0833. Small, but not zero.
Then it loops through both diseases and applies smoothing to all symptom counts. The smoothed_data dictionary stores these corrected probabilities. You will feed this directly into your Bayesian network in the next step.
- Save medical_diagnosis.py and run it:
python medical_diagnosis.py
- You should see the full output: the basic CPT, multi-disease tables, the zero-probability explanation, the Laplace smoothing solution showing 1/12 = 0.0833, and a "Smoothed CPTs for Network" section with four probabilities per disease.
Seeing a NameError for asthma_total?
The variable asthma_total is defined in Step 2.1 at the top of the file. If you accidentally deleted it or moved the Step 2.3 code into a separate file, Python won't find it. Make sure all code is in one file in sequential order.
Help me fix my Laplace smoothing code.
✔️ 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 medical_diagnosis.py at this point:
# Medical Diagnosis Bayesian Network
# Learning Conditional Probability Tables (CPTs) from scratch
# ============================================================
# STEP 2: Calculate CPTs by Hand from Patient Counts
# ============================================================
print("=" * 60)
print("STEP 2.1: Basic CPT Calculation from Counts")
print("=" * 60)
# Dataset: 10 asthma patients observed for wheezing
asthma_total = 10
asthma_wheeze_yes = 8
asthma_wheeze_no = 2
# Calculate conditional probabilities
p_wheeze_yes_given_asthma = asthma_wheeze_yes / asthma_total
p_wheeze_no_given_asthma = asthma_wheeze_no / asthma_total
print(f"\nDataset: {asthma_total} asthma patients")
print(f" - {asthma_wheeze_yes} wheeze")
print(f" - {asthma_wheeze_no} don't wheeze")
print(f"\nCPT: P(Wheeze | Asthma)")
print(f" P(Wheeze=yes | Asthma=yes) = {asthma_wheeze_yes}/{asthma_total} = {p_wheeze_yes_given_asthma}")
print(f" P(Wheeze=no | Asthma=yes) = {asthma_wheeze_no}/{asthma_total} = {p_wheeze_no_given_asthma}")
print(f" Sum = {p_wheeze_yes_given_asthma + p_wheeze_no_given_asthma} (must equal 1.0)")
# ============================================================
print("\n" + "=" * 60)
print("STEP 2.2: Multi-Disease CPT Tables")
print("=" * 60)
# Full dataset
data = {
"Asthma": {"total": 10, "wheeze": 8, "fever": 1},
"Pneumonia": {"total": 12, "wheeze": 7, "fever": 10},
}
print("\n--- CPT: P(Wheeze | Disease) ---")
print(f"{'Disease':<12} {'P(Wheeze=yes)':<16} {'P(Wheeze=no)':<16}")
print("-" * 44)
for disease, counts in data.items():
p_yes = counts["wheeze"] / counts["total"]
p_no = 1 - p_yes
print(f"{disease:<12} {p_yes:<16.4f} {p_no:<16.4f}")
print("\n--- CPT: P(Fever | Disease) ---")
print(f"{'Disease':<12} {'P(Fever=yes)':<16} {'P(Fever=no)':<16}")
print("-" * 44)
for disease, counts in data.items():
p_yes = counts["fever"] / counts["total"]
p_no = 1 - p_yes
print(f"{disease:<12} {p_yes:<16.4f} {p_no:<16.4f}")
# ============================================================
print("\n" + "=" * 60)
print("STEP 2.3: The Zero-Probability Problem & Laplace Smoothing")
print("=" * 60)
# Scenario: What if NO asthma patients had fever?
asthma_fever_zero = 0
p_fever_given_asthma_zero = asthma_fever_zero / asthma_total
print(f"\nProblem scenario: 0 out of {asthma_total} asthma patients have fever")
print(f" P(Fever=yes | Asthma=yes) = {asthma_fever_zero}/{asthma_total} = {p_fever_given_asthma_zero}")
print(f"\n WHY THIS IS DANGEROUS:")
print(f" If we multiply probabilities during inference:")
print(f" P(Asthma | Wheeze=yes, Fever=yes) involves P(Fever=yes | Asthma) = 0")
print(f" Any number * 0 = 0, so Asthma gets ZERO probability!")
print(f" The model becomes 100% certain the patient doesn't have asthma,")
print(f" just because we never SAW an asthma patient with fever in our tiny sample.")
def calculate_cpt_with_smoothing(count_yes, total, num_categories=2, alpha=1):
"""
Apply Laplace smoothing to probability estimation.
Formula: P(x) = (count + alpha) / (total + alpha * num_categories)
Args:
count_yes: observed count for this outcome
total: total observations
num_categories: number of possible outcomes (e.g., 2 for yes/no)
alpha: smoothing parameter (1 = Laplace smoothing)
"""
p_yes = (count_yes + alpha) / (total + alpha * num_categories)
p_no = (total - count_yes + alpha) / (total + alpha * num_categories)
return p_yes, p_no
print(f"\n SOLUTION: Laplace Smoothing (alpha = 1)")
print(f" Formula: P(x) = (count + 1) / (total + 1 * num_categories)")
p_smoothed_yes, p_smoothed_no = calculate_cpt_with_smoothing(0, 10, num_categories=2, alpha=1)
print(f" P(Fever=yes | Asthma) = (0 + 1) / (10 + 1*2) = 1/12 = {p_smoothed_yes:.4f}")
print(f" P(Fever=no | Asthma) = (10 + 1) / (10 + 1*2) = 11/12 = {p_smoothed_no:.4f}")
print(f" Sum = {p_smoothed_yes + p_smoothed_no:.4f}")
print(f"\n Now Asthma has a SMALL but non-zero probability of fever.")
print(f" The model stays open-minded instead of making absolute claims.")
# Calculate all smoothed CPTs for use in the network
print("\n--- Smoothed CPTs for Network ---")
smoothed_data = {}
for disease, counts in data.items():
wheeze_yes, wheeze_no = calculate_cpt_with_smoothing(counts["wheeze"], counts["total"])
fever_yes, fever_no = calculate_cpt_with_smoothing(counts["fever"], counts["total"])
smoothed_data[disease] = {
"wheeze_yes": wheeze_yes,
"wheeze_no": wheeze_no,
"fever_yes": fever_yes,
"fever_no": fever_no,
}
print(f"\n{disease} (smoothed):")
print(f" P(Wheeze=yes) = {wheeze_yes:.4f}, P(Wheeze=no) = {wheeze_no:.4f}")
print(f" P(Fever=yes) = {fever_yes:.4f}, P(Fever=no) = {fever_no:.4f}")
You now have hand-calculated CPTs backed by real numbers and a smoothing function that prevents catastrophic zeros. Next up, you will feed these probabilities into a working Bayesian network and ask it to diagnose patients from their symptoms.
Build the Bayesian Network in pgmpy
You have a complete set of smoothed conditional probabilities calculated from patient data. Now it is time to use those probabilities to build a working diagnostic system.
In this step, you will construct a Bayesian network in pgmpy that connects diseases to symptoms. Then you will feed it your smoothed CPTs and ask it diagnostic questions like "given these symptoms, what disease is most likely?"
In this step, get ready to:
- Define a Bayesian network structure with Disease, Wheeze, and Fever nodes.
- Create TabularCPD objects using your smoothed probabilities and validate the model.
- Run inference queries that diagnose a patient from their symptoms.
Define the network structure and prior probability
A Bayesian network has two parts: a structure (which nodes connect to which) and parameters (the probability tables at each node). Your network has one parent node (Disease) that causes two child nodes (Wheeze and Fever).
- In medical_diagnosis.py, scroll to the bottom of the file (after the smoothed CPT printout).
- Add the following code to import pgmpy, define the network structure, and create the Disease prior:
# ============================================================
# STEP 3: Build the Bayesian Network in pgmpy
# ============================================================
print("\n" + "=" * 60)
print("STEP 3: Build the Bayesian Network in pgmpy")
print("=" * 60)
from pgmpy.models import DiscreteBayesianNetwork
from pgmpy.factors.discrete import TabularCPD
from pgmpy.inference import VariableElimination
# Step 3.1: Define network structure
# Disease -> Wheeze, Disease -> Fever
model = DiscreteBayesianNetwork([("Disease", "Wheeze"), ("Disease", "Fever")])
# Prior probability of each disease (assume equal for simplicity)
cpd_disease = TabularCPD(
variable="Disease",
variable_card=2,
values=[[0.5], [0.5]],
state_names={"Disease": ["Asthma", "Pneumonia"]},
)
What does this code do?
DiscreteBayesianNetwork takes a list of edges as tuples. Each tuple ("Disease", "Wheeze") means Disease causes Wheeze. This matches the real-world relationship: having a disease causes symptoms, not the other way around.
The cpd_disease is the prior probability of each disease before seeing any symptoms. Setting both to 0.5 means we assume equal likelihood of Asthma or Pneumonia before observing anything.
Define symptom CPTs using your smoothed values
Each symptom node needs a CPT that describes how likely that symptom is given each disease. You already calculated these smoothed probabilities in Step 2.
- Add the following code directly below the cpd_disease definition:
# CPT for Wheeze given Disease (using smoothed values)
cpd_wheeze = TabularCPD(
variable="Wheeze",
variable_card=2,
values=[
[smoothed_data["Asthma"]["wheeze_yes"], smoothed_data["Pneumonia"]["wheeze_yes"]],
[smoothed_data["Asthma"]["wheeze_no"], smoothed_data["Pneumonia"]["wheeze_no"]],
],
evidence=["Disease"],
evidence_card=[2],
state_names={"Wheeze": ["yes", "no"], "Disease": ["Asthma", "Pneumonia"]},
)
# CPT for Fever given Disease (using smoothed values)
cpd_fever = TabularCPD(
variable="Fever",
variable_card=2,
values=[
[smoothed_data["Asthma"]["fever_yes"], smoothed_data["Pneumonia"]["fever_yes"]],
[smoothed_data["Asthma"]["fever_no"], smoothed_data["Pneumonia"]["fever_no"]],
],
evidence=["Disease"],
evidence_card=[2],
state_names={"Fever": ["yes", "no"], "Disease": ["Asthma", "Pneumonia"]},
)
What does TabularCPD expect?
The values array is organized by columns, where each column corresponds to a parent state. Column 1 is Asthma, column 2 is Pneumonia. Row 1 is "yes" for the symptom, row 2 is "no."
The evidence parameter tells pgmpy which node is the parent. The state_names parameter maps numeric indices to human-readable labels so your queries can use strings like "yes" instead of numbers.
- Save medical_diagnosis.py.
Seeing a NameError for smoothed_data?
The cpd_wheeze and cpd_fever definitions reference the smoothed_data dictionary from Step 2. Make sure the Step 3 code is placed AFTER the code that builds smoothed_data, not above it.
Help me debug a NameError in my Bayesian network code
Add CPDs and validate the model
Before running inference, pgmpy needs all CPDs attached to the model. The check_model() method verifies that every column in your CPTs sums to 1.0 and that the structure is consistent.
- Add the following code below the cpd_fever definition:
# Step 3.2: Add CPDs and validate
model.add_cpds(cpd_disease, cpd_wheeze, cpd_fever)
print("\nModel valid:", model.check_model())
print("\n--- CPD: Disease (Prior) ---")
print(cpd_disease)
print("\n--- CPD: Wheeze | Disease ---")
print(cpd_wheeze)
print("\n--- CPD: Fever | Disease ---")
print(cpd_fever)
What does check_model() verify?
check_model() confirms three things: every node in the structure has a CPD attached, every CPD's columns sum to exactly 1.0, and the variable cardinalities match between edges and CPDs. If anything is wrong, it raises an error with a specific message telling you which CPD failed.
- Save medical_diagnosis.py and run it:
python medical_diagnosis.py
- Scroll to the Step 3 output in your terminal. You should see Model valid: True followed by three printed CPD tables.
Seeing Model valid: False or an error?
Double check that your values arrays in each TabularCPD have exactly 2 rows and 2 columns. Each column must sum to 1.0. If you used raw (unsmoothed) probabilities, rounding errors could cause a mismatch.
Help me fix my Bayesian network model validation error
Run inference to diagnose from symptoms
The whole point of building this network is to answer diagnostic questions. Variable Elimination is an algorithm that computes exact probabilities by summing out variables you did not observe.
- Add the first inference query below the CPD print statements:
# Step 3.3: Run inference
print("\n" + "=" * 60)
print("INFERENCE: Given Wheeze=yes and Fever=yes, what disease?")
print("=" * 60)
infer = VariableElimination(model)
result = infer.query(variables=["Disease"], evidence={"Wheeze": "yes", "Fever": "yes"})
print(result)
print("\nInterpretation:")
print(" Pneumonia is more likely because it commonly causes BOTH")
print(" wheezing (7/12 patients) and fever (10/12 patients).")
print(" Asthma commonly causes wheezing (8/10) but rarely fever (1/10).")
print(" So a patient with both symptoms points toward Pneumonia.")
What does this query do?
infer.query() asks: "Given that I observed Wheeze=yes and Fever=yes, what is the probability distribution over Disease?" The algorithm multiplies the relevant CPT values and normalizes them so the result sums to 1.0.
Pneumonia scores higher because it frequently causes BOTH symptoms (7/12 wheeze, 10/12 fever). Asthma causes wheezing (8/10) but rarely fever (1/10), so having both symptoms makes Pneumonia the better explanation.
- Now add a second query that tests a different symptom combination:
# Additional query: Wheeze=yes, Fever=no
print("\n" + "=" * 60)
print("INFERENCE: Given Wheeze=yes and Fever=no, what disease?")
print("=" * 60)
result2 = infer.query(variables=["Disease"], evidence={"Wheeze": "yes", "Fever": "no"})
print(result2)
print("\nInterpretation:")
print(" Now Asthma becomes more likely! Asthma patients almost always")
print(" wheeze (8/10) but rarely have fever (1/10). This symptom")
print(" profile matches Asthma much better than Pneumonia.")
Why does removing fever flip the diagnosis?
When fever is absent, the network removes Pneumonia's strongest signal (10/12 pneumonia patients have fever). Asthma patients almost never have fever (1/10), so "no fever" is actually EVIDENCE for Asthma. The network combines both observations and correctly shifts the probability.
- Save medical_diagnosis.py and run it one final time:
python medical_diagnosis.py
- Scroll to the inference section of the output. You should see two probability tables: one where Pneumonia has a higher probability, and one where Asthma has a higher probability.
Getting a KeyError or unexpected results?
Make sure the evidence dictionary uses the exact state names you defined: "yes" and "no" (lowercase). These must match the state_names in your TabularCPD definitions.
Help me fix my pgmpy inference query error
✔️ Awesome, I've got everything!
Great. Double check you have saved medical_diagnosis.py and that the script runs end-to-end without errors.
ⓧ I'd like to double check the full code
# Medical Diagnosis Bayesian Network
# Learning Conditional Probability Tables (CPTs) from scratch
# ============================================================
# STEP 2: Calculate CPTs by Hand from Patient Counts
# ============================================================
print("=" * 60)
print("STEP 2.1: Basic CPT Calculation from Counts")
print("=" * 60)
# Dataset: 10 asthma patients observed for wheezing
asthma_total = 10
asthma_wheeze_yes = 8
asthma_wheeze_no = 2
# Calculate conditional probabilities
p_wheeze_yes_given_asthma = asthma_wheeze_yes / asthma_total
p_wheeze_no_given_asthma = asthma_wheeze_no / asthma_total
print(f"\nDataset: {asthma_total} asthma patients")
print(f" - {asthma_wheeze_yes} wheeze")
print(f" - {asthma_wheeze_no} don't wheeze")
print(f"\nCPT: P(Wheeze | Asthma)")
print(f" P(Wheeze=yes | Asthma=yes) = {asthma_wheeze_yes}/{asthma_total} = {p_wheeze_yes_given_asthma}")
print(f" P(Wheeze=no | Asthma=yes) = {asthma_wheeze_no}/{asthma_total} = {p_wheeze_no_given_asthma}")
print(f" Sum = {p_wheeze_yes_given_asthma + p_wheeze_no_given_asthma} (must equal 1.0)")
# ============================================================
print("\n" + "=" * 60)
print("STEP 2.2: Multi-Disease CPT Tables")
print("=" * 60)
# Full dataset
data = {
"Asthma": {"total": 10, "wheeze": 8, "fever": 1},
"Pneumonia": {"total": 12, "wheeze": 7, "fever": 10},
}
print("\n--- CPT: P(Wheeze | Disease) ---")
print(f"{'Disease':<12} {'P(Wheeze=yes)':<16} {'P(Wheeze=no)':<16}")
print("-" * 44)
for disease, counts in data.items():
p_yes = counts["wheeze"] / counts["total"]
p_no = 1 - p_yes
print(f"{disease:<12} {p_yes:<16.4f} {p_no:<16.4f}")
print("\n--- CPT: P(Fever | Disease) ---")
print(f"{'Disease':<12} {'P(Fever=yes)':<16} {'P(Fever=no)':<16}")
print("-" * 44)
for disease, counts in data.items():
p_yes = counts["fever"] / counts["total"]
p_no = 1 - p_yes
print(f"{disease:<12} {p_yes:<16.4f} {p_no:<16.4f}")
# ============================================================
print("\n" + "=" * 60)
print("STEP 2.3: The Zero-Probability Problem & Laplace Smoothing")
print("=" * 60)
# Scenario: What if NO asthma patients had fever?
asthma_fever_zero = 0
p_fever_given_asthma_zero = asthma_fever_zero / asthma_total
print(f"\nProblem scenario: 0 out of {asthma_total} asthma patients have fever")
print(f" P(Fever=yes | Asthma=yes) = {asthma_fever_zero}/{asthma_total} = {p_fever_given_asthma_zero}")
print(f"\n WHY THIS IS DANGEROUS:")
print(f" If we multiply probabilities during inference:")
print(f" P(Asthma | Wheeze=yes, Fever=yes) involves P(Fever=yes | Asthma) = 0")
print(f" Any number * 0 = 0, so Asthma gets ZERO probability!")
print(f" The model becomes 100% certain the patient doesn't have asthma,")
print(f" just because we never SAW an asthma patient with fever in our tiny sample.")
def calculate_cpt_with_smoothing(count_yes, total, num_categories=2, alpha=1):
"""
Apply Laplace smoothing to probability estimation.
Formula: P(x) = (count + alpha) / (total + alpha * num_categories)
Args:
count_yes: observed count for this outcome
total: total observations
num_categories: number of possible outcomes (e.g., 2 for yes/no)
alpha: smoothing parameter (1 = Laplace smoothing)
"""
p_yes = (count_yes + alpha) / (total + alpha * num_categories)
p_no = (total - count_yes + alpha) / (total + alpha * num_categories)
return p_yes, p_no
print(f"\n SOLUTION: Laplace Smoothing (alpha = 1)")
print(f" Formula: P(x) = (count + 1) / (total + 1 * num_categories)")
p_smoothed_yes, p_smoothed_no = calculate_cpt_with_smoothing(0, 10, num_categories=2, alpha=1)
print(f" P(Fever=yes | Asthma) = (0 + 1) / (10 + 1*2) = 1/12 = {p_smoothed_yes:.4f}")
print(f" P(Fever=no | Asthma) = (10 + 1) / (10 + 1*2) = 11/12 = {p_smoothed_no:.4f}")
print(f" Sum = {p_smoothed_yes + p_smoothed_no:.4f}")
print(f"\n Now Asthma has a SMALL but non-zero probability of fever.")
print(f" The model stays open-minded instead of making absolute claims.")
# Calculate all smoothed CPTs for use in the network
print("\n--- Smoothed CPTs for Network ---")
smoothed_data = {}
for disease, counts in data.items():
wheeze_yes, wheeze_no = calculate_cpt_with_smoothing(counts["wheeze"], counts["total"])
fever_yes, fever_no = calculate_cpt_with_smoothing(counts["fever"], counts["total"])
smoothed_data[disease] = {
"wheeze_yes": wheeze_yes,
"wheeze_no": wheeze_no,
"fever_yes": fever_yes,
"fever_no": fever_no,
}
print(f"\n{disease} (smoothed):")
print(f" P(Wheeze=yes) = {wheeze_yes:.4f}, P(Wheeze=no) = {wheeze_no:.4f}")
print(f" P(Fever=yes) = {fever_yes:.4f}, P(Fever=no) = {fever_no:.4f}")
# ============================================================
# STEP 3: Build the Bayesian Network in pgmpy
# ============================================================
print("\n" + "=" * 60)
print("STEP 3: Build the Bayesian Network in pgmpy")
print("=" * 60)
from pgmpy.models import DiscreteBayesianNetwork
from pgmpy.factors.discrete import TabularCPD
from pgmpy.inference import VariableElimination
# Step 3.1: Define network structure
# Disease -> Wheeze, Disease -> Fever
model = DiscreteBayesianNetwork([("Disease", "Wheeze"), ("Disease", "Fever")])
# Prior probability of each disease (assume equal for simplicity)
cpd_disease = TabularCPD(
variable="Disease",
variable_card=2,
values=[[0.5], [0.5]],
state_names={"Disease": ["Asthma", "Pneumonia"]},
)
# CPT for Wheeze given Disease (using smoothed values)
cpd_wheeze = TabularCPD(
variable="Wheeze",
variable_card=2,
values=[
[smoothed_data["Asthma"]["wheeze_yes"], smoothed_data["Pneumonia"]["wheeze_yes"]],
[smoothed_data["Asthma"]["wheeze_no"], smoothed_data["Pneumonia"]["wheeze_no"]],
],
evidence=["Disease"],
evidence_card=[2],
state_names={"Wheeze": ["yes", "no"], "Disease": ["Asthma", "Pneumonia"]},
)
# CPT for Fever given Disease (using smoothed values)
cpd_fever = TabularCPD(
variable="Fever",
variable_card=2,
values=[
[smoothed_data["Asthma"]["fever_yes"], smoothed_data["Pneumonia"]["fever_yes"]],
[smoothed_data["Asthma"]["fever_no"], smoothed_data["Pneumonia"]["fever_no"]],
],
evidence=["Disease"],
evidence_card=[2],
state_names={"Fever": ["yes", "no"], "Disease": ["Asthma", "Pneumonia"]},
)
# Step 3.2: Add CPDs and validate
model.add_cpds(cpd_disease, cpd_wheeze, cpd_fever)
print("\nModel valid:", model.check_model())
print("\n--- CPD: Disease (Prior) ---")
print(cpd_disease)
print("\n--- CPD: Wheeze | Disease ---")
print(cpd_wheeze)
print("\n--- CPD: Fever | Disease ---")
print(cpd_fever)
# Step 3.3: Run inference
print("\n" + "=" * 60)
print("INFERENCE: Given Wheeze=yes and Fever=yes, what disease?")
print("=" * 60)
infer = VariableElimination(model)
result = infer.query(variables=["Disease"], evidence={"Wheeze": "yes", "Fever": "yes"})
print(result)
print("\nInterpretation:")
print(" Pneumonia is more likely because it commonly causes BOTH")
print(" wheezing (7/12 patients) and fever (10/12 patients).")
print(" Asthma commonly causes wheezing (8/10) but rarely fever (1/10).")
print(" So a patient with both symptoms points toward Pneumonia.")
# Additional query: Wheeze=yes, Fever=no
print("\n" + "=" * 60)
print("INFERENCE: Given Wheeze=yes and Fever=no, what disease?")
print("=" * 60)
result2 = infer.query(variables=["Disease"], evidence={"Wheeze": "yes", "Fever": "no"})
print(result2)
print("\nInterpretation:")
print(" Now Asthma becomes more likely! Asthma patients almost always")
print(" wheeze (8/10) but rarely have fever (1/10). This symptom")
print(" profile matches Asthma much better than Pneumonia.")
Secret mission
Expand to a Three-Disease Network
Your 2-disease network can distinguish Asthma from Pneumonia. But real diagnostic systems handle many more conditions. Can you expand your network to handle a third disease and a third symptom, then run comparative queries that show how the network reasons differently across symptom profiles?
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely locally, so there are no ongoing costs. Decide whether to keep your resources for future experimentation or delete them entirely.
Resources you used:
- pgmpy Python package.
- Project folder containing medical_diagnosis.py.
Keep everything running
No action needed. Choose this if you plan to keep experimenting with your Bayesian network or want to extend it further.
- Your medical_diagnosis.py file stays on your Desktop ready to run anytime.
- The pgmpy package remains installed and available for other projects.
Pause - I'll come back to this later
Since this project is a local Python script with no running processes, there is nothing to pause. Your files and packages will be ready whenever you return.
- No servers or background processes are running.
- Your script and installed packages persist until you explicitly remove them.
Delete - I don't want to use this again
Remove the project files and uninstall the pgmpy package to clean up completely.
- Uninstall pgmpy by running this command in your terminal:
pip uninstall pgmpy
- Type y and press Enter when prompted to confirm.
- Delete your project folder from the Desktop by running:
macOS
cd ~/Desktop
rm -rf medical-diagnosis
Windows
cd ~/Desktop
rmdir /s /q medical-diagnosis
- If you named your project folder something different, replace medical-diagnosis with your actual folder name.
Nice Work!
Nice Work!
Congratulations! You built a probabilistic diagnosis system from the ground up.
You've learned how to:
- Calculate conditional probabilities by hand from raw patient counts and verify them in Python.
- Demonstrate why zero probabilities break inference and fix them with Laplace smoothing.
- Build a working Bayesian network in pgmpy and run diagnostic inference queries using Variable Elimination.
- Expand the network to handle 3 diseases and 3 symptoms with comparative diagnosis queries.
Ready to quiz yourself?