Module 7 Practice Sheet#

This notebook will guide you step-by-step to create a Python class to represent a patient from the Kaggle Stroke Prediction dataset.

You will:

  • Filter the dataset to the relevant columns

  • Create a class Patient

  • Add attributes and methods

  • Create objects from DataFrame rows

  • Analyze patients for stroke risk

This Patient class represents a single row in the dataset — i.e., one individual patient’s health profile.

# Setup code; make sure to run this if using Binder or Colab
import sys
import os
sys.path.insert(0, os.path.abspath(os.path.join(os.getcwd(), '..', 'shared')))
import setup_code
stroke_data = setup_code.stroke_data

Step 1: Subset the dataset#

We’re already loaded the dataset as stroke_data. Subset it to the following columns:

  • id

  • gender

  • age

  • hypertension

  • heart_disease

  • avg_glucose_level

  • bmi

  • smoking_status

# Your code here
Solution
stroke_data_filtered = stroke_data[[
    'id', 'gender', 'age', 'hypertension', 'heart_disease',
    'avg_glucose_level', 'bmi', 'smoking_status']]

stroke_data_filtered.head()

Step 2: Define the class#

Create a class called Patient that stores these 8 attributes. Each of these attributes should be an input into the constructor. Do not add methods yet.

Task:#

Use __init__ to initialize the class with 8 attributes: id, gender, age, hypertension, heart_disease, avg_glucose_level, bmi, and smoking_status.

# Your code here
Solution
class Patient:
    def __init__(self, id, gender, age, hypertension, heart_disease, avg_glucose_level, bmi, smoking_status):
        self.id = id
        self.gender = gender
        self.age = age
        self.hypertension = hypertension
        self.heart_disease = heart_disease
        self.avg_glucose_level = avg_glucose_level
        self.bmi = bmi
        self.smoking_status = smoking_status       

Step 3: Add a method to display info#

Add a method display_info(self) that prints all attributes of the patient in a readable format.

# Your code here
Solution
def display_info(self):
        print(f"Patient ID: {self.id}")
        print(f"Gender: {self.gender}")
        print(f"Age: {self.age}")
        print(f"Hypertension: {'Yes' if self.hypertension == 1 else 'No'}")
        print(f"Heart Disease: {'Yes' if self.heart_disease == 1 else 'No'}")
        print(f"Average Glucose Level: {self.avg_glucose_level}")
        print(f"BMI: {self.bmi}")
        print(f"Smoking Status: {self.smoking_status}")

Step 4: Add a method to evaluate stroke risk#

Add a method is_high_risk(self) that returns True if any of the following conditions are met:

  • age > 60

  • hypertension == 1

  • heart_disease == 1

  • avg_glucose_level > 150

  • bmi > 30 (and not missing)

# Your code here
Solution
def is_high_risk(self):
        return (
            self.age > 60 or
            self.hypertension == 1 or
            self.heart_disease == 1 or
            self.avg_glucose_level > 150 or
            (self.bmi != "N/A" and self.bmi > 30)
        )

Step 5: Create a helper function to extract data from a row#

Create a helper function outside the class that takes a row from the DataFrame and a class (default: Patient) as input, and returns an instance of that class.

This makes the function flexible and reusable with child classes later on.

The function should:

  • take in a row of the filtered stroke dataset,

  • check whether the row has a BMI value,

  • set the BMI to “N/A” if it’s missing,

  • and return an instance of the specified class using the row’s values.

# Your code here
Solution
# Helper function to extract data from row

def create_patient_from_row(row, cls=Patient):
    bmi = row['bmi']
    if pd.isna(bmi):
        bmi = "N/A"
    return cls(
        row['id'], row['gender'], row['age'], row['hypertension'],
        row['heart_disease'], row['avg_glucose_level'], bmi, row['smoking_status']
    )

Step 6: Test your class#

Now that you have all elements of your Patient class, make sure to combine them to get the code running properly. You can also click on the code below to see it.

Afterwards, use the helper function to create an instance from a DataFrame row.

Full code for Patient class
#Patient class without class method
class Patient:
    def __init__(self, id, gender, age, hypertension, heart_disease, avg_glucose_level, bmi, smoking_status):
        self.id = id
        self.gender = gender
        self.age = age
        self.hypertension = hypertension
        self.heart_disease = heart_disease
        self.avg_glucose_level = avg_glucose_level
        self.bmi = bmi
        self.smoking_status = smoking_status   

    def display_info(self):
        print(f"Patient ID: {self.id}")
        print(f"Gender: {self.gender}")
        print(f"Age: {self.age}")
        print(f"Hypertension: {'Yes' if self.hypertension == 1 else 'No'}")
        print(f"Heart Disease: {'Yes' if self.heart_disease == 1 else 'No'}")
        print(f"Average Glucose Level: {self.avg_glucose_level}")
        print(f"BMI: {self.bmi}")
        print(f"Smoking Status: {self.smoking_status}")

    def is_high_risk(self):
        return (
            self.age > 60 or
            self.hypertension == 1 or
            self.heart_disease == 1 or
            self.avg_glucose_level > 150 or
            (self.bmi != "N/A" and self.bmi > 30)
        )

# Separate function for patient row extraction
def create_patient_from_row(row, cls=Patient):
    bmi = row['bmi']
    if pd.isna(bmi):
        bmi = "N/A"
    return cls(
        row['id'],
        row['gender'],
        row['age'],
        row['hypertension'],
        row['heart_disease'],
        row['avg_glucose_level'],
        bmi,
        row['smoking_status']
    )

To test your code, use the first row of the DataFrame to create a Patient object using create_patient_from_row(row).

# Your code here
Solution

row1 = stroke_data_filtered.iloc[0]
patient1 = create_patient_from_row(row1, cls=Patient)      
patient1.display_info()
print("\nHigh Risk:", patient1.is_high_risk())

Step 7: Child Classes and Inheritance#

Now that you’ve created a Patient class, let’s explore how you can use inheritance to create specialized patient types.

  1. Create two child classes that inherit from Patient:

  • MalePatient

  • FemalePatient

  1. Override the display_info method in each child class to add a custom message:

  • For MalePatient, after displaying info, print: “Note: This is a male patient.”

  • For FemalePatient, after displaying info, print: “Note: This is a female patient.”

  1. (Optional) Let’s assume that the threshold for BMI risk might be different for female patients. Override the is_high_risk method in FemalePatient to be a bit stricter by adding this condition: Return True if BMI > 28 (instead of 30), in addition to the original conditions.

#Your code here
Solution
class MalePatient(Patient):
    def display_info(self):
        super().display_info()
        print("Note: This is a male patient.")


class FemalePatient(Patient):
    def display_info(self):
        super().display_info()
        print("Note: This is a female patient.")

    def is_high_risk(self):
        original_risk = super().is_high_risk()
        stricter_bmi = (self.bmi != "N/A" and self.bmi > 28)
        return original_risk or stricter_bmi

def create_patient_from_row(row, cls=Patient):
    bmi = row['bmi']
    if pd.isna(bmi):
        bmi = "N/A"
    return cls(
        row['id'], row['gender'], row['age'], row['hypertension'],
        row['heart_disease'], row['avg_glucose_level'], bmi, row['smoking_status']
    )

Test your child classes to make sure the code works properly.

#Your code here
Solution
row_male = stroke_data_filtered[stroke_data_filtered['gender'] == 'Male'].iloc[0]
row_female = stroke_data_filtered[stroke_data_filtered['gender'] == 'Female'].iloc[0]

male_patient = create_patient_from_row(row_male, cls=MalePatient)
female_patient = create_patient_from_row(row_female, cls=FemalePatient)

male_patient.display_info()
print("High Risk:", male_patient.is_high_risk())

female_patient.display_info()
print("High Risk:", female_patient.is_high_risk())

🌟 OPTIONAL: Create a Class Method to Improve the Code#

To make the code more structured and object-oriented, we moved the logic from the external helper function create_patient_from_row() into the class itself using a @classmethod.

Both approaches do the same job: They handle missing BMI values and construct an instance of Patient (or a child class like FemalePatient) from a row in the DataFrame.

Why a class method? It keeps the data-related logic inside the class, making the code easier to maintain and extend—especially when working with child classes.

Your tasks:

  1. Add a method to the Patient class called from_dataframe_row that takes a row from the DataFrame and returns an instance of the class.

    This will allow you to write:

    patient = Patient.from_dataframe_row(row)
    
  2. Extend this method in the child classes if needed.

Check the Hint to learn more about class methods.

💡 Hint

The @classmethod decorator allows us to create methods that are called on the class itself, not on an instance.
The first parameter, cls, refers to the class — just like self refers to an instance (a specific object).

Think of it like this:

  • self is “me” — the specific patient object

  • cls is “the blueprint” — the class that builds new patient objects


This class method is useful when you want to create an object from data that isn’t in the usual parameter format — for example, a row from a DataFrame.


How does this compare to __init__?
The __init__ method initializes the object from explicit parameters you provide.
But sometimes your input is more complex (like a DataFrame row), so the class method helps by extracting and preparing data before calling __init__.


You can learn more about the differences between instance methods, class methods, and static methods here:
Real Python: Instance, Class, and Static Methods Demystified

Step 1: Patient class with class method#

#Your code here for STEP 1
Class Method Solution
@classmethod
    def from_dataframe_row(cls, row):
        bmi = row['bmi']
        if pd.isna(bmi):
            bmi = "N/A"
        return cls(
            row['id'],
            row['gender'],
            row['age'],
            row['hypertension'],
            row['heart_disease'],
            row['avg_glucose_level'],
            bmi,
            row['smoking_status']
        )
Solution for complete new Patient class
class Patient:
    def __init__(self, id, gender, age, hypertension, heart_disease, avg_glucose_level, bmi, smoking_status):
        self.id = id
        self.gender = gender
        self.age = age
        self.hypertension = hypertension
        self.heart_disease = heart_disease
        self.avg_glucose_level = avg_glucose_level
        self.bmi = bmi
        self.smoking_status = smoking_status   

    def display_info(self):
        print(f"Patient ID: {self.id}")
        print(f"Gender: {self.gender}")
        print(f"Age: {self.age}")
        print(f"Hypertension: {'Yes' if self.hypertension == 1 else 'No'}")
        print(f"Heart Disease: {'Yes' if self.heart_disease == 1 else 'No'}")
        print(f"Average Glucose Level: {self.avg_glucose_level}")
        print(f"BMI: {self.bmi}")
        print(f"Smoking Status: {self.smoking_status}")

    def is_high_risk(self):
        return (
            self.age > 60 or
            self.hypertension == 1 or
            self.heart_disease == 1 or
            self.avg_glucose_level > 150 or
            (self.bmi != "N/A" and self.bmi > 30)
        )
    
    @classmethod
    def from_dataframe_row(cls, row):
        bmi = row['bmi']
        if pd.isna(bmi):
            bmi = "N/A"
        return cls(
            row['id'],
            row['gender'],
            row['age'],
            row['hypertension'],
            row['heart_disease'],
            row['avg_glucose_level'],
            bmi,
            row['smoking_status']
        )

Test your new Patient class to make sure it works correctly.

# Your code here
Test code for new Patient class
row1 = stroke_data_filtered.iloc[0]
patient1 = Patient.from_dataframe_row(row1)
patient1.display_info()
print("\nHigh Risk:", patient1.is_high_risk())

Step 2: extension to child classes#

Next, try to extend this to your child classes.

#Your code for STEP 2
Solution for new child classes
class FemalePatient(Patient):
    def display_info(self):
        super().display_info()
        print("Note: This is a female patient.")

    def is_high_risk(self):
        original_risk = super().is_high_risk()
        stricter_bmi = (self.bmi != "N/A" and self.bmi > 28)
        return original_risk or stricter_bmi

    @classmethod
    def from_dataframe_row(cls, row):
        return Patient.from_dataframe_row(row)

class MalePatient(Patient):
    def display_info(self):
        super().display_info()
        print("Note: This is a male patient.")

    @classmethod
    def from_dataframe_row(cls, row):
        return Patient.from_dataframe_row(row)

Test code for new child classes
row_male = stroke_data_filtered[stroke_data_filtered['gender'] == 'Male'].iloc[0]
row_female = stroke_data_filtered[stroke_data_filtered['gender'] == 'Female'].iloc[0]

male_patient = MalePatient.from_dataframe_row(row_male)
female_patient = FemalePatient.from_dataframe_row(row_female)

male_patient.display_info()
print("High Risk:", male_patient.is_high_risk())

female_patient.display_info()
print("High Risk:", female_patient.is_high_risk())