Predict Malaria Treatment Outcomes

Train an explainable ML model to predict malaria treatment outcomes in Nigeria.

Introduction

30 Second Summary

Nigeria carries 27% of the global malaria burden. Whether a patient recovers often depends on health system factors beyond their control: where they first seek care, whether the right drugs are in stock, and how far they live from a proper facility.

In this project, you will build an explainable machine learning model that predicts which malaria patients in Nigeria are likely to have poor treatment outcomes. You will use SHAP to reveal which health system factors drive those failures and visualize the results on an interactive map of Nigeria.

What You'll Build

When you finish, you will have a working ML pipeline that takes 15,000 Nigerian patient records, predicts who will fail treatment, and shows you exactly why through SHAP plots and a color-coded interactive map of Nigeria you could hand to a policymaker.

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

  • A trained Random Forest model that catches patients at risk of treatment failure, with SHAP explanations showing which Nigerian health system factors (testing gaps, drug stockouts, care-seeking patterns) drive poor outcomes.
  • A SHAP summary plot and waterfall plot you can show anyone to explain your model's reasoning in plain, actionable terms.
  • An interactive Plotly choropleth map of Nigeria where you hover over any state to see its treatment failure rate and key health metrics.
  • Secret Mission: Export your state-level analysis as a Power BI-ready dataset and build a geospatial dashboard for health policymakers.

Are there any prerequisites?

You should have Python 3.12 installed with pip, and be familiar with pandas and basic ML concepts (train/test split, accuracy, precision/recall).

You should also have scikit-learn, SHAP, Plotly, and matplotlib already installed. The only new package you will install is the Hugging Face datasets library.

Before We Start

Before we dive in, let's lock in what you're about to build and why it matters.

Load the Nigerian Malaria Dataset

The Nigerian malaria dataset lives on Hugging Face, split into three files representing low, moderate, and high burden scenarios.

You need all three combined into a single DataFrame before any analysis or modeling can begin. In this step, you'll pull the dataset using the Hugging Face datasets library and merge the splits into one 15,000-row DataFrame containing the full picture of Nigerian malaria treatment pathways.

In this step, get ready to:
  • Create a Python script to load the Nigerian malaria dataset from Hugging Face.
  • Combine three burden scenario splits into one analysis-ready DataFrame.
  • Verify the dataset shape and confirm Nigerian-specific columns.
Set up your project folder
  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir malaria-prediction
cd malaria-prediction
  • Confirm the folder exists by running:
ls

You should see the malaria-prediction folder listed on your Desktop.

Write and run the data loading script

The dataset on Hugging Face contains three splits representing different malaria transmission scenarios across Nigeria. Loading without specifying a split returns a DatasetDict containing all three, which you'll convert to pandas DataFrames and concatenate.

  • Create a file called malaria_analysis.py in your malaria-prediction folder and paste the following code:
import pandas as pd
from datasets import load_dataset

# Load all three burden scenario splits
ds = load_dataset('electricsheepafrica/africa-synth-malaria-diagnostic-treatment-pathways-all')

# Concatenate into a single DataFrame
dfs = [pd.DataFrame(ds[split]) for split in ds.keys()]
df = pd.concat(dfs, ignore_index=True)

print(f'Dataset loaded: {df.shape[0]} rows, {df.shape[1]} columns')
print('\nColumns:', df.columns.tolist())
print('\nFirst 5 rows:')
print(df.head())

What does this code do?

  • load_dataset() pulls the dataset from Hugging Face and returns a DatasetDict with three keys: one for each burden scenario (low, moderate, high).
  • The list comprehension iterates over each split and converts it to a pandas DataFrame.
  • pd.concat() stacks all three DataFrames vertically into one, with ignore_index=True resetting row numbers to 0, 1, 2, etc.
  • The print statements confirm the total size and show you what columns you're working with.

Before you run this, what do you think the total row count will be? The low burden file has 4,000 rows, moderate has 5,000, and high has 6,000.

  • Run your script by executing this command:
python malaria_analysis.py

You should see output starting with:

Dataset loaded: 15,000 rows, 37 columns

The first 5 rows should show Nigerian-specific columns including state, lga, season (harmattan/dry/rainy), treatment_seeking_first_provider, and patient_outcome.

What do these columns tell you?

This dataset captures the full malaria care-seeking journey in Nigeria. The treatment_seeking_first_provider column tells you where a patient first went for help (pharmacy, patent medicine vendor, traditional healer, private clinic, or public facility).

The patient_outcome column records what happened to them: cured, treatment failure, referred, or defaulted from care. These two columns form the core of what you'll model in later steps.

Seeing a download error?

  • Make sure you have an active internet connection. The load_dataset() function downloads the data from Hugging Face servers.
  • If you see a module error for datasets, confirm the library is installed by running pip install datasets in your terminal.

Need more help? Why is my dataset not loading from Hugging Face?

✔️ Awesome, I've got everything!

Great. Double check you've saved your file.

ⓧ I'd like to double check the full code

import pandas as pd
from datasets import load_dataset

# Load all three burden scenario splits
ds = load_dataset('electricsheepafrica/africa-synth-malaria-diagnostic-treatment-pathways-all')

# Concatenate into a single DataFrame
dfs = [pd.DataFrame(ds[split]) for split in ds.keys()]
df = pd.concat(dfs, ignore_index=True)

print(f'Dataset loaded: {df.shape[0]} rows, {df.shape[1]} columns')
print('\nColumns:', df.columns.tolist())
print('\nFirst 5 rows:')
print(df.head())

Your data is loaded and ready for exploration. Next up, you'll dig into the Nigerian care-seeking patterns hidden in these 15,000 records to understand what makes this dataset unique before building any model.

Explore the Nigerian Malaria Care Pathway

Your dataset is loaded and you can see 15,000 rows of Nigerian malaria records. Before building any model, you need to understand what makes this data uniquely Nigerian.

The treatment-seeking patterns, testing gaps, and seasonal dynamics in this dataset are specific to Nigeria's health system. This exploration reveals the story in the data and tells you which features to engineer for prediction.

In this step, get ready to:
  • Create a binary target variable separating poor outcomes from good ones.
  • Cross-tabulate Nigerian care-seeking patterns against treatment outcomes.
  • Examine the relationship between drug stockouts, dosage completeness, and treatment failure.
Examine the target variable and create a binary classification

The raw patient_outcome column has four classes: cured, treatment_failure, referred, and defaulted. For prediction, you need a simpler binary target that groups the outcomes representing a failure of the health system together.

  • In malaria_analysis.py, add the following code below the existing print(df.head()) line to examine and transform the target variable:
# --- Step 2: Explore the Nigerian Malaria Care Pathway ---

# Examine target variable
print('\npatient_outcome value counts:')
print(df['patient_outcome'].value_counts())

# Create binary target
df['poor_outcome'] = df['patient_outcome'].isin(['treatment_failure', 'defaulted'])
print('\nClass balance (poor_outcome):')
print(df['poor_outcome'].value_counts(normalize=True))

What does this code do?

First, it prints the raw counts for each of the four outcome categories so you can see the distribution.

Then it creates a new column poor_outcome that is True when a patient's outcome was treatment_failure or defaulted (they dropped out of care). The normalize=True parameter shows proportions instead of raw counts so you can see the class balance at a glance.

  • Save malaria_analysis.py and run it:
python malaria_analysis.py

In the output, look for the class balance printout. You should see roughly 80-85% good outcomes and 15-20% poor outcomes.

Seeing a KeyError?

Make sure you placed the new code below the existing print(df.head()) line. The df variable must be defined before you reference it.

Help me debug this KeyError

Cross-tabulate Nigerian care-seeking patterns

In Nigeria, most malaria patients do not go to a hospital first. They visit pharmacies, patent medicine vendors, or traditional healers. Understanding which entry points lead to worse outcomes is critical for health policy.

  • Add this code below the class balance section in malaria_analysis.py to cross-tabulate care-seeking patterns against outcomes:
# Cross-tabulations
for col in ['treatment_seeking_first_provider', 'test_performed_flag', 'presumptive_treatment_without_test', 'season']:
    print(f'\n{col} vs poor_outcome (%):')
    print(pd.crosstab(df[col], df['poor_outcome'], normalize='index') * 100)

What does this code do?

It loops through four key columns and prints a percentage cross-tabulation for each one against the poor_outcome target. The normalize='index' parameter means each row sums to 100%, showing the percentage of poor outcomes for each category.

For example, you will see what percentage of patients who first visited a patent medicine vendor ended up with a poor outcome versus those who went to a public facility.

  • Save malaria_analysis.py and run it again:
python malaria_analysis.py

In the cross-tabulation output, look for two key patterns: roughly 60% of patients first visit pharmacies or patent medicine vendors, and only about 35% receive a diagnostic test.

Why do these patterns matter?

Patent medicine vendors are the most common first point of care in rural Nigeria, but they typically lack diagnostic tools. Patients treated without a confirmed malaria diagnosis (presumptive treatment) may receive unnecessary drugs or miss a different illness entirely.

These are the kinds of actionable insights that health policymakers need. Unlike age or distance to a facility, testing practices and vendor regulation are things a government can change.

Output looks different from expected?

The exact percentages depend on the dataset's random generation. You should see clear differences between provider types, but the specific numbers may vary slightly. The important thing is that the pattern is visible.

Help me check my cross-tabulation code

Examine dosage completeness and stockout effects

Artemisinin-based combination therapy (ACT) is the frontline malaria treatment. Incomplete courses breed drug resistance, and stockouts force patients onto inferior monotherapies.

  • Add this final exploration code below the cross-tabulations loop in malaria_analysis.py:
# Dosage completeness by outcome
print('\nMean dosage_completeness_pct by poor_outcome:')
print(df.groupby('poor_outcome')['dosage_completeness_pct'].mean())

What does this code do?

It groups patients by whether they had a poor outcome or not, then calculates the average dosage completeness percentage for each group. A large gap between the two means tells you that incomplete drug courses are strongly associated with treatment failure.

Before you run this, what do you expect to see? Do you think patients with poor outcomes completed more or less of their drug course?

  • Save malaria_analysis.py and run the full script:
python malaria_analysis.py

You should see that patients with poor outcomes have noticeably lower mean dosage completeness. This confirms the clinical intuition that incomplete ACT courses are a key driver of treatment failure.

Why does dosage completeness matter so much?

When patients do not finish their full course of ACT, they kill most of the malaria parasites but leave the most drug-resistant ones alive to multiply. This is how antimalarial drug resistance develops.

Stockouts make this worse. When a clinic runs out of the correct drug midway through treatment, patients switch to inferior alternatives or simply stop. Both paths lead to treatment failure and contribute to resistance spread across the region.

Not seeing a clear difference?

If the mean dosage completeness values look similar for both groups, double-check that your poor_outcome column was created correctly. It should use .isin(['treatment_failure', 'defaulted']) with both strings spelled exactly as they appear in the data.

Help me debug my poor_outcome column

✔️ Awesome, I've got everything!

Great. Make sure you have saved malaria_analysis.py with all three exploration sections intact.

ⓧ I'd like to double check the full code

Your malaria_analysis.py should contain these sections in order. Scroll up and confirm each is present:

  • Imports and dataset loading (from Step 1).
  • The # --- Step 2 section header comment.
  • Target variable examination and poor_outcome creation.
  • Cross-tabulations loop over four columns.
  • Dosage completeness grouped by outcome.

You now have a clear picture of the data's story. Most Nigerian malaria patients bypass formal healthcare, few get tested, and incomplete drug courses strongly predict failure. Next, you will turn these insights into features and train a model to predict poor outcomes.

Engineer Features and Train the Misleading Model

Your exploration revealed the patterns hiding in this dataset. Now it is time to turn those patterns into a prediction model.

Healthcare ML with categorical features (like provider type and season) requires encoding before any algorithm can use them. You will select the most relevant columns, encode them, and train your first model.

In this step, get ready to:
  • Select and encode 14 clinical features for modeling.
  • Train a Random Forest classifier on the encoded data.
  • Evaluate the model and discover a dangerous healthcare ML trap.
Add scikit-learn imports and define your feature set

Your model needs scikit-learn's classifier, train/test splitting, and evaluation utilities. It also needs to know which of the 37 columns to use as input features.

  • In malaria_analysis.py, find this line near the top of the file:
from datasets import load_dataset
  • Add these three import lines directly below it:
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report, confusion_matrix, accuracy_score

What do these imports give you?

  • RandomForestClassifier is the algorithm that learns patterns from the data by building many decision trees.
  • train_test_split divides your data so you train on 80% and test on the unseen 20%.
  • classification_report, confusion_matrix, and accuracy_score measure how well the model performs.

Now define the feature columns and encode them. Many of your features are categorical (text-based), so they need to be converted to numbers before the model can use them.

  • Scroll to the very bottom of malaria_analysis.py and add this code below the last line:
# --- Step 3: Feature Engineering and the Misleading Model ---

FEATURES = [
    'age_years', 'sex', 'fever_duration_hours_before_treatment',
    'treatment_seeking_first_provider', 'test_performed_flag', 'treatment_type',
    'season', 'urban_rural', 'stockout_affected_treatment',
    'presumptive_treatment_without_test', 'dosage_completeness_pct',
    'distance_to_facility_km', 'caregiver_knowledge_score',
    'malaria_transmission_intensity'
]

X = pd.get_dummies(df[FEATURES])
y = df['poor_outcome']

What does this code do?

FEATURES lists the 14 columns that capture the Nigerian malaria care journey. These include patient demographics, care-seeking behavior, testing practices, treatment details, and health system factors like stockouts.

pd.get_dummies() converts categorical columns (like treatment_seeking_first_provider with values 'pharmacy', 'patent_medicine_vendor', etc.) into separate binary columns. Each category becomes its own 0/1 feature the model can learn from.

  • Save malaria_analysis.py and run the script to confirm the new code does not produce any errors:
python malaria_analysis.py

You should see all the exploration output from earlier steps print successfully, with no new errors at the end.

Seeing an ImportError?

If you see an error about sklearn not being found, confirm scikit-learn is installed by running pip install scikit-learn. If a column name error appears, double-check that the strings in your FEATURES list match the dataset columns exactly (they are case-sensitive).

help me fix this import or column name error in my malaria analysis script

Train a Random Forest and check the accuracy

With your features encoded, you can split the data into training and test sets, then let a Random Forest learn the patterns that predict poor treatment outcomes.

  • Add this code at the bottom of malaria_analysis.py, directly below the y = df['poor_outcome'] line:
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)

model = RandomForestClassifier(n_estimators=100, random_state=42)
model.fit(X_train, y_train)
y_pred = model.predict(X_test)

print('\n--- Baseline Model (no class balancing) ---')
print(f'Accuracy: {accuracy_score(y_test, y_pred):.4f}')
print('\nClassification Report:')
print(classification_report(y_test, y_pred))
print('\nConfusion Matrix:')
print(confusion_matrix(y_test, y_pred))

What does this code do?

train_test_split holds out 20% of your data for testing. The stratify=y argument ensures the train and test sets have the same proportion of poor outcomes as the full dataset.

The RandomForestClassifier builds 100 decision trees, each learning from a random subset of the data. Together they vote on whether each patient will have a poor outcome. random_state=42 makes results reproducible.

The evaluation section prints accuracy, a full classification report with precision/recall per class, and a confusion matrix showing correct vs incorrect predictions.

Before you run this, think about what you expect. Your dataset has roughly 15-20% poor outcomes and 80-85% good outcomes. What do you think the model's recall will be for the minority class (poor outcomes)?

  • Save malaria_analysis.py and run it:
python malaria_analysis.py

Scroll to the bottom of the output. You should see an accuracy around 83-87%. That looks great on the surface.

Uncover the class imbalance trap

Now look more closely at the classification report. Find the row for True (poor outcomes). The recall value is below 40%.

This means the model catches fewer than 4 out of every 10 patients who actually fail treatment. It achieves its high accuracy by predicting "good outcome" for nearly everyone. Since 80-85% of patients are cured, just guessing "cured" every time would score well on accuracy alone.

Why is this the most dangerous trap in healthcare ML?

A model that misses 60%+ of treatment failures is worse than useless. It creates false confidence. Health workers relying on it would believe their patients are fine when many are actually heading toward treatment failure or defaulting from care.

In the confusion matrix, look at how many True cases are in the "predicted False" column. Those are the patients the model failed to flag. In a Nigerian clinic with limited resources, these are exactly the patients who needed intervention most.

What is class imbalance?

Class imbalance occurs when one category vastly outnumbers another in your training data. The model learns that predicting the majority class ("cured") is the easiest path to high accuracy, so it barely tries to identify the minority class ("poor outcome"). Accuracy becomes a misleading metric because it hides this behavior.

Not seeing the expected output?

If you see an error about array shapes or data types, make sure X = pd.get_dummies(df[FEATURES]) comes before the train_test_split call. If accuracy is above 95% or below 70%, double-check that your poor_outcome column was created correctly in Step 2.

help me understand my model's unexpected accuracy or error

✔️ Awesome, I've got everything!

Great. Double check you have saved malaria_analysis.py and that your output shows both the high accuracy and the poor recall for the True class.

ⓧ I'd like to double check the full code

Here is the complete malaria_analysis.py up to this point. Compare your file against it:

import pandas as pd
from datasets import load_dataset
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report, confusion_matrix, accuracy_score

# Load all three burden scenario splits
ds = load_dataset('electricsheepafrica/africa-synth-malaria-diagnostic-treatment-pathways-all')

# Concatenate into a single DataFrame
dfs = [pd.DataFrame(ds[split]) for split in ds.keys()]
df = pd.concat(dfs, ignore_index=True)

print(f'Dataset loaded: {df.shape[0]} rows, {df.shape[1]} columns')
print('\nColumns:', df.columns.tolist())
print('\nFirst 5 rows:')
print(df.head())
# --- Step 2: Explore the Nigerian Malaria Care Pathway ---

print('\npatient_outcome value counts:')
print(df['patient_outcome'].value_counts())

df['poor_outcome'] = df['patient_outcome'].isin(['treatment_failure', 'defaulted'])
print('\nClass balance (poor_outcome):')
print(df['poor_outcome'].value_counts(normalize=True))

for col in ['treatment_seeking_first_provider', 'test_performed_flag', 'presumptive_treatment_without_test', 'season']:
    print(f'\n{col} vs poor_outcome (%):')
    print(pd.crosstab(df[col], df['poor_outcome'], normalize='index') * 100)

print('\nMean dosage_completeness_pct by poor_outcome:')
print(df.groupby('poor_outcome')['dosage_completeness_pct'].mean())
# --- Step 3: Feature Engineering and the Misleading Model ---

FEATURES = [
    'age_years', 'sex', 'fever_duration_hours_before_treatment',
    'treatment_seeking_first_provider', 'test_performed_flag', 'treatment_type',
    'season', 'urban_rural', 'stockout_affected_treatment',
    'presumptive_treatment_without_test', 'dosage_completeness_pct',
    'distance_to_facility_km', 'caregiver_knowledge_score',
    'malaria_transmission_intensity'
]

X = pd.get_dummies(df[FEATURES])
y = df['poor_outcome']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)

model = RandomForestClassifier(n_estimators=100, random_state=42)
model.fit(X_train, y_train)
y_pred = model.predict(X_test)

print('\n--- Baseline Model (no class balancing) ---')
print(f'Accuracy: {accuracy_score(y_test, y_pred):.4f}')
print('\nClassification Report:')
print(classification_report(y_test, y_pred))
print('\nConfusion Matrix:')
print(confusion_matrix(y_test, y_pred))

You have exposed the most common trap in healthcare ML. A model that looks great on paper but fails the patients who need it most. In the next step, you will fix this with class balancing and then use SHAP to explain what the model actually learns about Nigerian treatment failures.

Fix the Model and Explain with SHAP

In the last step, your baseline model achieved impressive-looking accuracy. But the classification report revealed a critical flaw: the model barely catches treatment failures because it learned to predict "good outcome" for nearly everyone.

The fix is class_weight='balanced', which forces the model to pay equal attention to poor outcomes despite their lower frequency. Once you have a model that genuinely detects treatment failures, you can use SHAP to explain which Nigerian health system factors drive those predictions.

In this step, get ready to:
  • Retrain the Random Forest with class balancing and confirm recall improves.
  • Run SHAP TreeExplainer and generate a summary plot of feature importance.
  • Generate a waterfall plot for one patient and interpret actionable vs structural factors.
Retrain with class balancing

The class_weight='balanced' parameter adjusts weights inversely proportional to class frequencies. This forces the model to penalise mistakes on the minority class (poor outcomes) just as heavily as mistakes on the majority class.

  • In malaria_analysis.py, find the existing import lines at the top of the file. Add these three new imports directly below them:
import shap
import matplotlib.pyplot as plt
import numpy as np
  • Scroll to the bottom of the file (below the confusion matrix output). Add the balanced model training code by copying this below the existing code:
# --- Step 4: Fix the Model and Reveal What Drives Poor Outcomes ---

balanced_model = RandomForestClassifier(n_estimators=100, class_weight='balanced', random_state=42)
balanced_model.fit(X_train, y_train)
y_pred_balanced = balanced_model.predict(X_test)

print('\n--- Balanced Model ---')
print(f'Accuracy: {accuracy_score(y_test, y_pred_balanced):.4f}')
print('\nClassification Report:')
print(classification_report(y_test, y_pred_balanced))
print('\nConfusion Matrix:')
print(confusion_matrix(y_test, y_pred_balanced))

What does class_weight='balanced' do?

Without balancing, the model sees 5x more "good outcome" examples than "poor outcome" examples. It learns a shortcut: always predict good. With class_weight='balanced', scikit-learn automatically increases the weight of poor-outcome samples during training so both classes contribute equally to the loss.

The trade-off is that you catch more true treatment failures but also raise more false alarms. In public health resource allocation, this trade-off is usually worth it because missing a real failure has worse consequences than a false alarm.

Before you run this, do you think the recall for poor outcomes will go up or down compared to the baseline? What about overall accuracy?

  • Save malaria_analysis.py and run it:
python malaria_analysis.py

In the new "Balanced Model" classification report, recall for True (poor outcomes) should jump above 65-70%. Overall accuracy will drop slightly because the model now flags some false positives. That is the correct behaviour for a healthcare screening model.

Recall still below 50%?

  • Confirm you typed class_weight='balanced' (not class_weight='balance' or class_weights). The parameter name is singular.
  • Make sure you are reading the "Balanced Model" section of the output, not the earlier "Baseline Model" section above it.
  • help me understand why my balanced model recall is still low
Explain with SHAP summary plot

Now that the model genuinely detects treatment failures, you can ask: which features push predictions toward poor outcomes? SHAP (SHapley Additive exPlanations) answers this by computing each feature's contribution to every individual prediction.

  • Add the SHAP explainability code at the bottom of malaria_analysis.py, below the balanced model confusion matrix:
# SHAP explainability
explainer = shap.TreeExplainer(balanced_model)
shap_values = explainer(X_test)

# SHAP summary plot
shap.summary_plot(shap_values[:, :, 1], X_test, show=False)
plt.tight_layout()
plt.savefig('shap_summary_plot.png', dpi=150, bbox_inches='tight')
plt.close()
print('\nSHAP summary plot saved as shap_summary_plot.png')

What does this code do?

  • shap.TreeExplainer(balanced_model) creates an explainer optimised for tree-based models. It uses the Tree SHAP algorithm for fast, exact computation.
  • explainer(X_test) computes SHAP values for every sample in the test set, returning an Explanation object.
  • shap_values[:, :, 1] selects the SHAP values for the positive class (poor outcome = True). For binary classifiers, index 1 corresponds to the "True" class predictions.
  • shap.summary_plot creates a beeswarm plot showing all features ranked by importance, with dots coloured by feature value (red = high, blue = low).
  • Save malaria_analysis.py and run it:
python malaria_analysis.py

You should see "SHAP summary plot saved as shap_summary_plot.png" printed at the end of the output.

  • Open shap_summary_plot.png from your project folder to view the plot.

The top features driving poor outcomes should include dosage_completeness_pct, presumptive_treatment_without_test, and stockout_affected_treatment. These are the Nigerian health system factors that matter most for predicting treatment failure.

Seeing an error about SHAP or matplotlib?

  • If you see ModuleNotFoundError: No module named 'shap', run pip install shap and try again.
  • If matplotlib throws an error about a non-interactive backend, make sure you have show=False in the summary_plot call. The code saves to a file instead of displaying interactively.
  • help me fix my SHAP or matplotlib import error
Generate a waterfall plot and interpret results

The summary plot shows global patterns. A waterfall plot shows how individual features combine for one specific patient, which is what a clinician or case worker would use for decision support.

  • Add the waterfall plot and interpretation code at the bottom of malaria_analysis.py:
# SHAP waterfall plot for a single poor-outcome patient
poor_indices = np.where(y_pred_balanced == True)[0]
if len(poor_indices) > 0:
    idx = poor_indices[0]
    shap.waterfall_plot(shap_values[idx, :, 1], show=False)
    plt.tight_layout()
    plt.savefig('shap_waterfall_single_patient.png', dpi=150, bbox_inches='tight')
    plt.close()
    print('SHAP waterfall plot saved as shap_waterfall_single_patient.png')

print('\nTop SHAP features interpretation:')
print('Actionable: dosage_completeness_pct, presumptive_treatment_without_test, stockout_affected_treatment, test_performed_flag')
print('Structural: distance_to_facility_km, age_years')

What does the waterfall plot show?

The waterfall starts from the model's base prediction (average risk) and shows how each feature pushes this patient's risk up or down. Red bars push toward poor outcome. Blue bars push toward good outcome.

The interpretation at the end distinguishes actionable factors from structural ones. Dosage completeness, testing rates, and stockout prevention are things the Nigerian health system can directly improve. Distance to facility and patient age are harder to change. This distinction is what makes the analysis useful for policymakers.

Before you run this final time, what do you expect the waterfall plot to show for a patient with a predicted poor outcome? Which features do you think will push the prediction toward failure?

  • Save malaria_analysis.py and run it:
python malaria_analysis.py

You should see both "SHAP waterfall plot saved as shap_waterfall_single_patient.png" and the feature interpretation lines printed at the end.

  • Open shap_waterfall_single_patient.png to view the individual patient explanation.

Why does explainability matter for African health systems?

A model that says "this patient will fail treatment" is not enough. Policymakers need to know why, so they can allocate resources. SHAP makes the model's reasoning transparent: if stockouts and presumptive treatment are the top drivers, the intervention is supply chain improvement and rapid diagnostic test deployment.

Without explainability, ML models in healthcare create a trust gap. Clinicians and programme officers will not act on a prediction they cannot understand.

Waterfall plot file not appearing?

  • Check that your model actually predicts some poor outcomes. The code uses np.where(y_pred_balanced == True) to find patients with predicted poor outcomes. If no patients are predicted as True, the waterfall plot is skipped.
  • Look for the if len(poor_indices) > 0 guard. If the balanced model is working correctly, it should predict poor outcomes for many patients.
  • help me debug why my SHAP waterfall plot is not being generated

✔️ Awesome, I've got everything!

Great. Double check you have saved malaria_analysis.py and confirmed both plot files exist in your project folder.

ⓧ I'd like to double check the full code

Your malaria_analysis.py is now over 30 lines. Scroll through the file and confirm you have the following sections in order:

  • Imports at the top: pandas, datasets, sklearn imports, plus shap, matplotlib.pyplot, and numpy.
  • Dataset loading and concatenation.
  • Step 2: Exploration code with cross-tabulations and poor_outcome creation.
  • Step 3: Feature engineering, get_dummies, train/test split, and baseline model.
  • Step 4: Balanced model with class_weight='balanced' and its classification report.
  • SHAP TreeExplainer, summary plot saved as shap_summary_plot.png.
  • Waterfall plot saved as shap_waterfall_single_patient.png, plus the interpretation print statements.

You now have an explainable ML model that genuinely catches treatment failures in Nigeria and tells you why they happen. The SHAP plots translate model internals into policy-relevant insights: fix stockouts, deploy rapid diagnostic tests, and improve dosage adherence.

Secret mission

Interactive Nigeria Map and Power BI Export

Policymakers do not read classification reports. They look at maps and dashboards. Transform your model's predictions into a geospatial visualization showing where in Nigeria treatment outcomes are worst, making the analysis actionable for resource allocation decisions. Then export a Power BI-ready dataset for dashboard creation.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally, so there are no ongoing costs. Decide whether to keep your files for reference, or delete them to free up disk space.

Resources you used:

  • Project folder containing malaria_analysis.py, shap_summary_plot.png, shap_waterfall_single_patient.png, and nigeria_malaria_state_summary.csv.
  • Hugging Face datasets library installed via pip.
  • Cached Hugging Face dataset stored locally by the datasets library.

Keep everything running

No action needed. Choose this if you plan to continue experimenting with the model, tweaking features, or extending the analysis.

  • Your project folder stays on your Desktop with all files intact.
  • The datasets library remains installed and available for other Hugging Face projects.
  • The cached dataset stays in your Hugging Face cache directory (typically ~/.cache/huggingface/).

Pause - I'll come back to this later

Free up disk space from the cached dataset while keeping your code and outputs. You can re-download the dataset later by running the script again.

  • Delete the Hugging Face cache to free disk space by running:

macOS

rm -rf ~/.cache/huggingface/datasets/electricsheepafrica___africa-synth-malaria-diagnostic-treatment-pathways-all

Windows

rmdir /s /q %USERPROFILE%\.cache\huggingface\datasets\electricsheepafrica___africa-synth-malaria-diagnostic-treatment-pathways-all

Your project folder, script, images, and CSV remain on your Desktop for when you return.

Delete - I don't want to use this again

Remove all project files, the cached dataset, and the installed library to start completely fresh.

  • Delete your project folder by running:

macOS

cd ~/Desktop
rm -rf malaria-analysis

Windows

cd ~\Desktop
rmdir /s /q malaria-analysis
  • Remove the cached Hugging Face dataset by running:

macOS

rm -rf ~/.cache/huggingface/datasets/electricsheepafrica___africa-synth-malaria-diagnostic-treatment-pathways-all

Windows

rmdir /s /q %USERPROFILE%\.cache\huggingface\datasets\electricsheepafrica___africa-synth-malaria-diagnostic-treatment-pathways-all
  • Uninstall the Hugging Face datasets library by running:
pip uninstall datasets -y

Your machine is now back to its original state. The other libraries (pandas, scikit-learn, SHAP, Plotly, matplotlib) were pre-existing and remain installed for your other projects.

That's a Wrap!

That's a Wrap!

Nice work! You just built an explainable machine learning pipeline that predicts malaria treatment outcomes in Nigeria and reveals the health system factors driving those outcomes.

You went from raw patient records all the way to actionable SHAP explanations and a geographic visualization that a policymaker could use to allocate resources.

You've learned how to:

  • Load and explore synthetic Nigerian malaria data capturing the full care-seeking journey, from patent medicine vendors and presumptive treatment to ACT stockouts and seasonal transmission patterns.
  • Discover the class imbalance trap in healthcare ML where misleadingly high accuracy masks a model that never catches treatment failures, and fix it with class_weight='balanced'.
  • Use SHAP explainability to distinguish actionable health system factors (testing rates, stockouts, dosage adherence) from structural ones (distance, age), making your model's predictions interpretable for policymakers.
  • Secret Mission: Created an interactive Plotly choropleth map of Nigeria showing treatment failure rates by state, and exported a Power BI-ready dataset for dashboard creation.

Ready to quiz yourself?