Predict Financial Inclusion in Africa

Build an ML pipeline to predict bank account access across East Africa.

Introduction

30 Second Summary

Across East Africa, millions of people still do not have a bank account. Understanding who is excluded and why is the first step toward closing that gap.

In this project, you will build an end-to-end machine learning pipeline that predicts whether individuals in East Africa have a bank account. Starting with real survey data from four countries, you will explore demographics, clean the data, and train two classification models to find out which factors drive financial access.

What You'll Build

Imagine running your completed Jupyter notebook top to bottom and watching it load survey data from ~33,600 individuals across four countries, produce charts revealing who has bank access, and print accuracy metrics from a trained model that predicts financial inclusion.

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

  • A trained prediction model you can run on real survey data, with accuracy scores and a confusion matrix proving it correctly identifies who has a bank account and who does not.
  • A set of data visualizations showing how financial inclusion differs across countries, locations, and education levels, with a deep dive into Tanzania.
  • A concrete business recommendation backed by your model's feature importances that you can present on a resume.
  • Secret Mission: Engineer new features and tune your model to push accuracy even higher.

Are there any prerequisites?

This project assumes basic Python knowledge (loops, functions, and variables). You should already have Python 3.12 or 3.13, VS Code with the Jupyter extension, a Kaggle account, and experience using pip.

Before We Start

Before we dive into any code, let's lock in the business problem you're about to solve and why it matters.

You're about to work with real survey data from East Africa to predict who has access to a bank account. Understanding this problem first will guide every decision you make in the notebook, from which columns you explore to the business recommendation you write at the end.

Set Up Your Environment and Get the Data

You have a clear business problem to solve. Before you can run any analysis, you need the right Python libraries installed and the actual survey data on your machine.

In this step, you will install four data science libraries, download real Financial Inclusion survey data from Kaggle, and load it into a Jupyter notebook to confirm everything connects.

In this step, get ready to:
  • Install pandas, scikit-learn, matplotlib, and seaborn.
  • Download the Financial Inclusion in Africa dataset from Kaggle.
  • Create a Jupyter notebook and load the dataset.
Install the data science libraries

Your project depends on four Python libraries. Each one handles a different part of the data science workflow.

  • Open a terminal window. Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal (macOS) or Command Prompt (Windows) and press Enter to open it.
  • Install all four libraries at once by running this command:
pip install pandas scikit-learn matplotlib seaborn

What do these libraries do?

  • pandas loads, filters, and transforms tabular data. Think of it as Excel for Python.
  • scikit-learn provides ready-to-use machine learning models, data splitting, and evaluation metrics.
  • matplotlib creates charts and plots from your data.
  • seaborn builds on matplotlib to produce cleaner statistical visualizations with less code.

You should see installation output ending with Successfully installed messages for the packages.

  • Confirm all four libraries are importable by running this command:
python -c "import pandas; import sklearn; import matplotlib; import seaborn; print('All libraries loaded successfully')"

You should see All libraries loaded successfully printed in your terminal.

Seeing 'ModuleNotFoundError'?

  • Make sure you ran the pip install command in the same terminal session. If you have multiple Python versions installed, try pip3 instead of pip.
  • On macOS, the default python command may point to an older version. Try using python3 instead.
  • help me fix my library installation error
Download the dataset and create your project folder

The Financial Inclusion in Africa dataset contains survey responses from approximately 33,600 individuals across Kenya, Rwanda, Tanzania, and Uganda. It is hosted on Kaggle as a single CSV file.

  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir financial-inclusion
cd financial-inclusion
  • Open the Kaggle dataset page in your browser by visiting this URL:

https://www.kaggle.com/datasets/sarahmabrouk/financial-inclusion-in-africa

  • Sign in to your Kaggle account if prompted.
  • Click the Download button near the top of the dataset page.

What will download?

Kaggle downloads a zip file containing Financial_inclusion_dataset.csv (2.86 MB). After the download finishes, extract the zip file and move the CSV into your financial-inclusion folder on your Desktop.

  • After extracting and moving the file, switch back to your terminal and confirm the file is in place by running:
ls

You should see Financial_inclusion_dataset.csv listed in the output.

Don't see the CSV file?

  • Make sure you extracted the zip file first. The file Kaggle downloads is a .zip archive, not the CSV itself.
  • Confirm the file is named exactly Financial_inclusion_dataset.csv with that exact capitalization. The filename is case-sensitive.
  • Make sure you moved the CSV into the financial-inclusion folder on your Desktop, not a subfolder inside it.
  • help me find and move my downloaded dataset file
Create the notebook and load the data
  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type VS Code and press Enter to open it.
  • Click File in the top menu bar.
  • Click Open Folder.
  • Select the financial-inclusion folder on your Desktop, then click Open.

You should see the financial-inclusion folder name in the Explorer sidebar on the left, with Financial_inclusion_dataset.csv listed inside it.

  • Open the Command Palette by pressing Cmd+Shift+P (macOS) or Ctrl+Shift+P (Windows).
  • Type Create: New Jupyter Notebook and select it from the dropdown.
  • Save the new notebook immediately by pressing Cmd+S (macOS) or Ctrl+S (Windows).
  • Name the file financial_inclusion.ipynb and save it inside the financial-inclusion folder.

What is a .ipynb file?

A .ipynb file is a Jupyter notebook. It lets you write and run Python code in individual cells, mixing code with markdown notes. Each cell runs independently, which makes it perfect for exploring data step by step.

Now you will add the code to your notebook. The first cell imports all the libraries you will use throughout this project.

  • In the first cell of your notebook, paste the following import code:
# Cell 1: Import libraries
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score, classification_report, ConfusionMatrixDisplay

What does this code do?

  • pandas is imported as pd for loading and manipulating tabular data.
  • numpy is imported as np for numerical operations you will use later.
  • matplotlib.pyplot and seaborn handle all your visualizations.
  • The sklearn imports bring in machine learning tools: train_test_split for splitting data, two model classes, and three evaluation utilities.
  • Run the cell by pressing Shift+Enter.

You should see no output and no errors. The cursor moves to a new empty cell below.

Seeing an import error in the notebook?

  • Your notebook might be using a different Python environment than the one where you installed the libraries. Click the kernel name in the top right corner of the notebook and select the Python version where you ran pip install.
  • If you see a prompt to install ipykernel, click Install to allow VS Code to run notebook cells.
  • help me fix my Jupyter notebook import errors

Before you run the next cell, predict: how many rows and columns do you think a dataset covering 33,600 individuals across four countries might have?

  • In the next cell, paste this code to load the dataset and preview it:
# Cell 2: Load the data
df = pd.read_csv("Financial_inclusion_dataset.csv")
print(f"Dataset shape: {df.shape}")
df.head()

What does this code do?

  • pd.read_csv() reads the CSV file into a DataFrame called df. A DataFrame is a table of rows and columns that pandas uses to represent your data.
  • df.shape returns the number of rows and columns as a tuple.
  • df.head() displays the first 5 rows so you can see the column names and sample values.
  • Run the cell by pressing Shift+Enter.

You should see the dataset shape printed (approximately 33,600 rows and 13 columns) followed by a table showing the first 5 rows of survey data with columns like country, bank_account, location_type, and age_of_respondent.

Seeing 'FileNotFoundError'?

  • Make sure Financial_inclusion_dataset.csv is inside your financial-inclusion folder, the same folder you opened in VS Code.
  • Check that the filename matches exactly, including the capital F at the start. Python file paths are case-sensitive.
  • help me fix the FileNotFoundError in my notebook

✔️ Awesome, I've got everything!

Great. Double check you have saved your notebook by pressing Cmd+S (macOS) or Ctrl+S (Windows).

ⓧ I'd like to double check the full code

Here is the complete code across both cells in your notebook. Make sure each cell matches exactly.

# Cell 1: Import libraries
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score, classification_report, ConfusionMatrixDisplay

# Cell 2: Load the data
df = pd.read_csv("Financial_inclusion_dataset.csv")
print(f"Dataset shape: {df.shape}")
df.head()

Your data is loaded and ready to explore. Next up, you will dig into the data to discover financial inclusion patterns across East Africa.

Explore the Data and Discover Patterns

Your environment is set up and your dataset is loaded into a DataFrame with thousands of rows. But a wall of raw numbers won't tell you what's actually happening with financial inclusion across East Africa.

Before building any model, you need to understand the shape of the data and look for patterns. Do some countries have much higher bank account access than others? Does living in a city versus a rural area make a difference? In this step, you'll use pandas inspection tools and matplotlib visualizations to answer these questions.

In this step, get ready to:
  • Inspect the dataset structure, check for missing values, and examine the target variable distribution.
  • Create visualizations comparing bank account access across countries, locations, and education levels.
  • Filter to Tanzania and explore what drives financial inclusion there.
Inspect the dataset structure and target variable

The first thing any data scientist does with a new dataset is check its structure. You need to know the data types, whether any values are missing, and how the thing you're trying to predict is distributed.

  • In your notebook, create a new code cell below Cell 2 by clicking the + Code button at the top of the notebook.
  • Inspect the dataset structure and target variable by pasting this code into the new cell:
# Cell 3: Inspect the data
print(df.info())
print("\n--- Missing values ---")
print(df.isnull().sum())
print("\n--- Target distribution ---")
print(df["bank_account"].value_counts())
print(f"\n% with bank account: {df['bank_account'].value_counts(normalize=True)['Yes']*100:.1f}%")

What does this code do?

  • df.info() shows every column's name, data type, and how many non-null entries it has. This tells you at a glance which columns are text and which are numbers.
  • df.isnull().sum() counts missing values per column. Missing data can break your model later, so you want to catch it now.
  • The bank_account column is your target variable. value_counts() shows how many people answered Yes vs No, and the normalized version shows the percentage.
  • Run the cell by pressing Shift+Enter.

You should see a summary of all 13 columns, zero missing values, and a target distribution showing that only about 14% of individuals have a bank account. The vast majority (around 86%) do not.

Why does the class balance matter?

When one class massively outnumbers the other, your model can get high accuracy just by predicting "No" every time. This lopsided split is called class imbalance. Keep this in mind. It will come back to haunt your first model later.

Seeing a KeyError on 'Yes'?

Make sure you loaded the correct CSV file. The target column bank_account should contain the values Yes and No. Confirm by running df["bank_account"].unique() in a new cell.

Help me debug a KeyError in my financial inclusion notebook

Now let's turn that target distribution into a visual. Numbers are good, but a chart makes the imbalance impossible to miss.

  • Create a new code cell and visualize the target distribution by pasting this code:
# Cell 4: Visualize target distribution
fig, ax = plt.subplots(figsize=(6, 4))
df["bank_account"].value_counts().plot(kind="bar", color=["#e74c3c", "#2ecc71"], ax=ax)
ax.set_title("Bank Account Distribution")
ax.set_xlabel("Has Bank Account")
ax.set_ylabel("Count")
plt.tight_layout()
plt.show()

What does this code do?

This creates a bar chart using matplotlib. The red bar represents people without a bank account and the green bar represents people with one. plt.tight_layout() prevents labels from getting clipped at the edges.

  • Run the cell with Shift+Enter.

You should see a bar chart where the "No" bar towers over the "Yes" bar. This is the class imbalance in action.

Create visualizations comparing demographics

You know the overall split. Now you need to compare bank account access across different groups. Which countries are ahead? Does living in an urban area help?

  • Create a new code cell and compare bank access across the four countries by pasting this code:
# Cell 5: Bank access by country
fig, ax = plt.subplots(figsize=(8, 5))
country_bank = df.groupby("country")["bank_account"].value_counts(normalize=True).unstack()
country_bank.plot(kind="bar", ax=ax, color=["#e74c3c", "#2ecc71"])
ax.set_title("Bank Account Access by Country")
ax.set_xlabel("Country")
ax.set_ylabel("Proportion")
ax.legend(["No", "Yes"])
plt.xticks(rotation=0)
plt.tight_layout()
plt.show()

What does this code do?

  • groupby("country") splits the data by country, then value_counts(normalize=True) calculates the proportion of Yes vs No within each country.
  • .unstack() pivots the result so each value (No, Yes) becomes its own column. This is what lets matplotlib draw grouped bars side by side.
  • Run the cell with Shift+Enter.

You should see a grouped bar chart with Kenya, Rwanda, Tanzania, and Uganda side by side. Notice how Kenya has the highest proportion of bank account holders, while Tanzania and Uganda lag behind.

Chart looks empty or shows no bars?

Make sure the column name in your code matches the dataset exactly. Run df.columns.tolist() to see all column names. They are case-sensitive.

Help me fix my grouped bar chart that isn't displaying properly

Next, compare rural versus urban bank access. The pattern here will be important for your business recommendation later.

  • Create a new code cell and compare bank access by location type by pasting this code:
# Cell 6: Bank access by location type
fig, ax = plt.subplots(figsize=(6, 4))
location_bank = df.groupby("location_type")["bank_account"].value_counts(normalize=True).unstack()
location_bank.plot(kind="bar", ax=ax, color=["#e74c3c", "#2ecc71"])
ax.set_title("Bank Account Access: Rural vs Urban")
ax.set_xlabel("Location Type")
ax.set_ylabel("Proportion")
ax.legend(["No", "Yes"])
plt.xticks(rotation=0)
plt.tight_layout()
plt.show()

What's happening here?

This follows the same pattern as the country chart, but groups by location_type instead. You'll see two clusters: Rural and Urban.

  • Run the cell with Shift+Enter.

You should see that urban areas have a noticeably higher proportion of bank account holders compared to rural areas. This gap is a key insight for your later recommendation.

One more comparison. Education is often linked to financial access, and this chart will show you how strong that relationship is.

  • Create a new code cell and compare bank access by education level by pasting this code:
# Cell 7: Bank access by education level
fig, ax = plt.subplots(figsize=(10, 5))
edu_bank = df.groupby("education_level")["bank_account"].value_counts(normalize=True).unstack()
edu_bank.plot(kind="bar", ax=ax, color=["#e74c3c", "#2ecc71"])
ax.set_title("Bank Account Access by Education Level")
ax.set_xlabel("Education Level")
ax.set_ylabel("Proportion")
ax.legend(["No", "Yes"])
plt.xticks(rotation=45, ha="right")
plt.tight_layout()
plt.show()

What does this chart reveal?

Notice how plt.xticks(rotation=45, ha="right") tilts the x-axis labels so the longer education category names don't overlap each other.

  • Run the cell with Shift+Enter.

You should see a clear staircase pattern. People with tertiary education have the highest bank account access, while those with no formal education have the lowest.

Filter to Tanzania and explore

The country comparison gave you the big picture. Now zoom into Tanzania specifically. What percentage of Tanzanians have bank accounts? What job types are most associated with having one?

  • Create a new code cell and explore Tanzania's financial inclusion by pasting this code:
# Cell 8: Tanzania deep dive
tz = df[df["country"] == "Tanzania"]
print(f"Tanzania records: {len(tz)}")
print(f"Tanzania bank account rate: {tz['bank_account'].value_counts(normalize=True)['Yes']*100:.1f}%")
print("\nBank access by job type in Tanzania:")
print(tz.groupby("job_type")["bank_account"].value_counts(normalize=True).unstack().fillna(0))

What does this code do?

  • df[df["country"] == "Tanzania"] filters the entire dataset down to only Tanzanian records and stores them in a new variable tz.
  • The job_type breakdown shows what proportion of people in each job category have a bank account. .fillna(0) replaces any empty cells with zero so the table prints cleanly.
  • Run the cell with Shift+Enter.

You should see the number of Tanzanian records, their overall bank account rate, and a table breaking down bank access by job type. Look at which job types have the lowest bank account rates. These are the groups your later business recommendation will target.

Getting an empty table or NaN values?

Check that you typed "Tanzania" with a capital T. The country names in the dataset are capitalized. Run df["country"].unique() to see the exact values.

Help me fix my Tanzania filter that's returning empty results

  • Add a new Markdown cell below Cell 8 by clicking + Markdown at the top of the notebook.
  • Write 2-3 sentences documenting your findings. Which country has the highest bank access? What surprised you about Tanzania's numbers? Which job types are most underserved?
  • Run the Markdown cell with Shift+Enter to render it.

Document as you go

Adding Markdown cells with your observations is a habit that separates polished notebooks from throwaway scripts. When you show this notebook to an interviewer, they want to see your thinking, not just your code.

✔️ Awesome, I've got everything!

Great. Double check you've saved your notebook with Cmd+S (macOS) or Ctrl+S (Windows).

ⓧ I'd like to double check the full code

Your notebook should contain these cells in order. Scroll through your notebook and confirm each one is present.

  • Cell 1: Import libraries (pandas, numpy, matplotlib, seaborn, scikit-learn).
  • Cell 2: Load the data with pd.read_csv() and print the shape.
  • Cell 3: Inspect the data with df.info(), missing values check, and target distribution.
  • Cell 4: Bar chart of bank_account distribution.
  • Cell 5: Grouped bar chart of bank access by country.
  • Cell 6: Grouped bar chart of bank access by location type.
  • Cell 7: Grouped bar chart of bank access by education level.
  • Cell 8: Tanzania deep dive with bank account rate and job type breakdown.

Save your notebook with Cmd+S (macOS) or Ctrl+S (Windows).

Before you move on, take a moment to think. Based on the charts you just created, what is one finding about Tanzania's financial inclusion that surprised you?

You should now have at least 4 visualizations in your notebook (target distribution, country comparison, location type, and education level) plus a printed table showing Tanzania's job type breakdown. You can point to specific patterns like the rural-urban gap or the education staircase.

You've uncovered the patterns in your data. You know the class imbalance, the country-level differences, and the demographic factors linked to bank account access. Next up, you'll clean this data and convert it into a format that a machine learning model can actually work with.

Clean the Data and Prepare It for Modeling

Your visualizations uncovered powerful patterns in financial inclusion across East Africa. You can now see how Tanzania's bank account access varies by location, education, and job type.

Machine learning algorithms work with numbers, not text. Columns like "Rural" or "Female" need to become numeric values before any model can learn from them. In this step, you'll clean, encode, and split the data so it's ready for training.

In this step, get ready to:
  • Drop columns that don't contribute to predictions and encode the target variable.
  • Convert all categorical text columns into numeric format using one-hot encoding.
  • Split the data into training and test sets for fair model evaluation.
Drop unnecessary columns and encode the target

Two columns in the dataset don't help predict bank account access. uniqueid is just a row identifier, and year indicates when the survey was conducted. Neither describes the individual.

The target column bank_account also needs to change from text ("Yes"/"No") to numbers (1/0) so your model can process it.

  • Add a new cell to your notebook (Cell 9) and paste the following code:
# Cell 9: Drop unnecessary columns and encode target
df_model = df.drop(columns=["uniqueid", "year"])
df_model["bank_account"] = df_model["bank_account"].map({"Yes": 1, "No": 0})

What does this code do?

  • df.drop(columns=["uniqueid", "year"]) creates a new DataFrame without the two non-predictive columns, keeping the original df untouched.
  • .map({"Yes": 1, "No": 0}) swaps every "Yes" to 1 and every "No" to 0 in the bank_account column. This is what the model will try to predict.
  • Run the cell. The cell executes and the execution counter [9] appears next to it, confirming the transformation succeeded.
Encode categorical features as numbers

Your dataset still has eight columns filled with text categories like "Rural", "Female", and "Farming and Fishing". A technique called one-hot encoding converts each category into its own column of 1s and 0s.

  • Add a new cell (Cell 10) and paste the following code:
# Cell 10: One-hot encode categorical features
df_encoded = pd.get_dummies(df_model, drop_first=True)
print(f"Encoded dataset shape: {df_encoded.shape}")
print(f"Missing values: {df_encoded.isnull().sum().sum()}")

What does this code do?

  • pd.get_dummies(df_model, drop_first=True) converts every text column into numeric columns. For example, the country column (Kenya, Rwanda, Tanzania, Uganda) becomes three separate columns like country_Rwanda, country_Tanzania, and country_Uganda, each filled with 1 or 0.
  • drop_first=True removes one category per column to avoid redundancy. If all other country columns are 0, the model already knows the row is Kenya.
  • The two print statements confirm the new shape (many more columns now) and that there are zero missing values.
  • Run the cell. You should see the encoded dataset shape printed, with significantly more columns than before (the original 13 columns expand because each category became its own column).
  • Confirm the second line reads Missing values: 0. Every value in your dataset is now a number with nothing missing.

Seeing missing values or an error?

  • Make sure you ran Cell 9 before Cell 10. Cell 10 uses df_model, which Cell 9 creates.
  • If you see a NameError, restart your kernel and run all cells from Cell 1 through Cell 10 in order.
  • Help me debug my one-hot encoding cell

Why not use LabelEncoder?

pd.get_dummies() creates separate binary columns, which avoids a subtle trap. A LabelEncoder would assign numbers like 1, 2, 3 to categories, which would trick the model into thinking "Tertiary education" (3) is somehow "greater" than "Primary education" (1). One-hot encoding treats each category as equally distinct.

Split the data into training and test sets

To test a model fairly, you need data it has never seen during training. scikit-learn's train_test_split function randomly divides the data into a training set (75%) for learning and a test set (25%) for evaluation.

  • Add a new cell (Cell 11) and paste the following code:
# Cell 11: Split into features and target
X = df_encoded.drop(columns=["bank_account"])
y = df_encoded["bank_account"]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=42)
print(f"Training set: {X_train.shape}")
print(f"Test set: {X_test.shape}")

What does this code do?

  • X holds every column except bank_account. These are the features the model uses to make predictions.
  • y holds only the bank_account column. This is the answer the model is trying to learn.
  • test_size=0.25 reserves 25% of the data for testing. The model never sees these rows during training.
  • random_state=42 locks the random shuffle so you get the same split every time. This makes your results reproducible.

Before you run this cell, think about it: with roughly 33,600 rows and a 75/25 split, how many rows do you expect in the training set versus the test set?

  • Run the cell. You should see two lines printed: the training set shape (roughly 25,000 rows) and the test set shape (roughly 8,400 rows). Both should have the same number of columns.

Shapes look wrong or seeing an error?

  • If the number of columns is different between X_train and X_test, something went wrong in an earlier cell. Restart your kernel and run all cells from Cell 1 in order.
  • Make sure Cell 10 ran before Cell 11. Cell 11 depends on df_encoded, which Cell 10 creates.
  • Help me debug my train-test split

✔️ Awesome, I've got everything!

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

ⓧ I'd like to double check the full code

Scroll up through your notebook and confirm these three cells are present in order, matching the code below.

Cell 9: Drop unnecessary columns and encode target.

# Cell 9: Drop unnecessary columns and encode target
df_model = df.drop(columns=["uniqueid", "year"])
df_model["bank_account"] = df_model["bank_account"].map({"Yes": 1, "No": 0})

Cell 10: One-hot encode categorical features.

# Cell 10: One-hot encode categorical features
df_encoded = pd.get_dummies(df_model, drop_first=True)
print(f"Encoded dataset shape: {df_encoded.shape}")
print(f"Missing values: {df_encoded.isnull().sum().sum()}")

Cell 11: Split into features and target.

# Cell 11: Split into features and target
X = df_encoded.drop(columns=["bank_account"])
y = df_encoded["bank_account"]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=42)
print(f"Training set: {X_train.shape}")
print(f"Test set: {X_test.shape}")

Your data is cleaned, encoded, and split. Every value is a number, nothing is missing, and 25% of the data is held back for a fair test. Next up, you'll train your first machine learning model and see how well it predicts bank account access.

Train a Baseline Model and See It Fall Short

Your data is cleaned, encoded, and split into training and test sets. You have a fully numeric DataFrame ready for machine learning.

The first step in any modeling task is to establish a baseline. A baseline is the simplest reasonable model you can train. It gives you a concrete number to measure future improvements against.

In this step, get ready to:
  • Train a logistic regression model and evaluate its accuracy.
  • Visualize the model's mistakes with a confusion matrix.
  • Document where the model struggles and why.
Train the logistic regression model

Logistic regression is a linear model that predicts the probability of a binary outcome. It draws a straight boundary between the two classes. It is fast, interpretable, and a great starting point for classification tasks.

Before you run this cell, make a prediction. Your dataset has about 14% "Yes" (has a bank account) and 86% "No". Do you think a simple linear model will handle both classes equally well?

  • Train and evaluate a logistic regression model by adding this code in a new cell (Cell 12) in your notebook:
# Cell 12: Train logistic regression
lr = LogisticRegression(max_iter=1000, random_state=42)
lr.fit(X_train, y_train)
lr_preds = lr.predict(X_test)
lr_accuracy = accuracy_score(y_test, lr_preds)
print(f"Logistic Regression Accuracy: {lr_accuracy:.4f}")
print("\nClassification Report:")
print(classification_report(y_test, lr_preds))

What does this code do?

  • LogisticRegression(max_iter=1000, random_state=42) creates the model. max_iter=1000 gives the algorithm enough iterations to converge, and random_state=42 locks in a reproducible result.
  • .fit(X_train, y_train) trains the model on your training data. The model learns the relationship between the features and the target.
  • .predict(X_test) generates predictions on the unseen test data. The model has never seen these rows before.
  • accuracy_score measures the percentage of predictions that were correct overall.
  • classification_report breaks down precision, recall, and F1-score for each class (0 = No bank account, 1 = Has bank account).
  • Run the cell by pressing Shift+Enter.

The accuracy number prints first. It likely lands somewhere around 85-89%. That looks decent at first glance.

Now look at the classification report below it. Focus on the recall column for class 1 (people who have a bank account). It is significantly lower than the recall for class 0. Your model is missing many people who actually have accounts.

Why is the recall for class 1 so low?

Only about 14% of the dataset has bank_account = 1. The model learned that guessing "No" most of the time gets a high overall accuracy score. This is a classic symptom of class imbalance.

The model plays it safe. It gets the easy majority class right and sacrifices accuracy on the harder minority class. For a financial inclusion project, that is a problem. Identifying who DOES have access is just as important as identifying who does not.

Seeing a ConvergenceWarning?

  • If you see a convergence warning even with max_iter=1000, try increasing it to 2000. The model needs more iterations to find its optimal solution on larger datasets.
  • If you see a NameError for X_train or y_train, go back and re-run Cell 11 (the train/test split cell) first.

Still stuck? Help me debug my logistic regression training cell.

Visualize the confusion matrix

The classification report showed the gap in numbers. A confusion matrix shows it visually so you can see exactly how many predictions the model got right and wrong for each class.

  • Display the confusion matrix by adding this code in a new cell (Cell 13):
# Cell 13: Confusion matrix for logistic regression
fig, ax = plt.subplots(figsize=(6, 5))
ConfusionMatrixDisplay.from_predictions(y_test, lr_preds, ax=ax, cmap="Blues")
ax.set_title("Logistic Regression - Confusion Matrix")
plt.tight_layout()
plt.show()

How do I read a confusion matrix?

The matrix is a 2x2 grid. Rows represent the actual values (what the person really has). Columns represent what the model predicted.

  • Top-left: people with no bank account who the model correctly predicted as "No". This number is large.
  • Bottom-right: people with a bank account who the model correctly predicted as "Yes". This number is smaller.
  • Bottom-left: people who actually HAVE a bank account, but the model predicted "No". These are the missed cases.
  • Run the cell by pressing Shift+Enter.

You should see a blue heatmap grid. The top-left cell (true "No", predicted "No") has the largest number. The bottom-left cell (true "Yes", predicted "No") confirms the model's weakness. Many people who actually have bank accounts were incorrectly classified as not having one.

Not seeing the confusion matrix?

  • If you see a NameError for lr_preds, go back and re-run Cell 12 first. Cell 13 depends on the predictions from Cell 12.
  • Make sure you imported ConfusionMatrixDisplay in Cell 1. Re-run Cell 1 if needed.

Still stuck? Help me fix my confusion matrix cell.

Document your findings

Good data science is not just about running code. It is about recording what you learned so you (and anyone reading your notebook) can follow your reasoning.

  • Add a new Markdown cell below Cell 13. In VS Code, click the + Markdown button at the top of the notebook.
  • Write a heading like ## Baseline Model Observations.
  • Answer these three questions in the cell:
  • What is the overall accuracy of the logistic regression model?
  • Which class does the model struggle with more, and what does that mean for the people it is misclassifying?
  • Why might a simple linear boundary not capture the full picture of who has a bank account?

Why document in markdown cells?

A notebook full of code with no explanation is hard to revisit. Markdown cells turn your notebook into a readable report. When you share this project on a resume or with a hiring manager, the markdown cells tell the story of your analysis.

  • Run the markdown cell by pressing Shift+Enter to render the formatted text.

You should see your observations rendered as formatted text below Cell 13's confusion matrix.

✔️ Awesome, I've got everything!

Great. Make sure you have saved your notebook before moving on.

ⓧ I'd like to double check the full code

Scroll through your notebook and confirm you have the following cells in order. All previous cells (Cells 1-11) should still be present from earlier steps.

Cell 12 should contain your logistic regression training, predictions, accuracy score, and classification report:

# Cell 12: Train logistic regression
lr = LogisticRegression(max_iter=1000, random_state=42)
lr.fit(X_train, y_train)
lr_preds = lr.predict(X_test)
lr_accuracy = accuracy_score(y_test, lr_preds)
print(f"Logistic Regression Accuracy: {lr_accuracy:.4f}")
print("\nClassification Report:")
print(classification_report(y_test, lr_preds))

Cell 13 should contain your confusion matrix visualization:

# Cell 13: Confusion matrix for logistic regression
fig, ax = plt.subplots(figsize=(6, 5))
ConfusionMatrixDisplay.from_predictions(y_test, lr_preds, ax=ax, cmap="Blues")
ax.set_title("Logistic Regression - Confusion Matrix")
plt.tight_layout()
plt.show()

You should also have a markdown cell below Cell 13 with your baseline model observations.

Your baseline model revealed a clear gap. It can spot who does NOT have a bank account, but it struggles to identify who does. In the next step, you will train a Random Forest model that captures more complex patterns and extracts the business insights that make your analysis actionable.

Train a Better Model and Extract Business Insights

Your logistic regression model is trained and its limitations are clear. It achieves decent overall accuracy but struggles to identify the people who actually have bank accounts.

A Random Forest combines many decision trees, each trained on a random slice of the data, to capture patterns a single model misses. It also reveals which features drive its predictions. That is the foundation for a concrete business recommendation.

In this step, get ready to:
  • Train a Random Forest and compare its performance against your logistic regression.
  • Extract feature importances and visualize the top predictors of bank account access.
  • Write a concrete business recommendation for improving financial inclusion in Tanzania.
Train a Random Forest classifier

Your logistic regression treated each feature independently with a straight-line decision boundary. A Random Forest builds 100 separate decision trees, each seeing a random subset of the data and features, then combines their votes for a more robust prediction.

Before you run this model, make a prediction of your own: do you think the Random Forest will improve on the logistic regression's recall for the "Yes" class (people with bank accounts)? Why or why not?

  • Create a new cell in your notebook and train a Random Forest model by adding this code:
# Cell 14: Train random forest
rf = RandomForestClassifier(n_estimators=100, random_state=42)
rf.fit(X_train, y_train)
rf_preds = rf.predict(X_test)
rf_accuracy = accuracy_score(y_test, rf_preds)
print(f"Random Forest Accuracy: {rf_accuracy:.4f}")
print("\nClassification Report:")
print(classification_report(y_test, rf_preds))

What does this code do?

  • RandomForestClassifier(n_estimators=100, random_state=42) creates a forest of 100 decision trees. random_state=42 makes results reproducible so you get the same output every time.
  • .fit() trains every tree on your training data. .predict() generates predictions on the test set by letting each tree vote.
  • The accuracy score and classification report let you compare this model directly against the logistic regression from Step 4.
  • Run the cell.
  • You should see an accuracy score and a full classification report. Compare the recall for class 1 (people with bank accounts) against the logistic regression's report above.

Seeing a NameError?

If you see an error about RandomForestClassifier or X_train not being defined, scroll up and re-run all the cells from Cell 1 onward. Jupyter notebooks lose variables if the kernel restarts.

Still stuck? Help me debug why my Random Forest cell throws a NameError in Jupyter.

Now visualize the predictions so you can compare the two confusion matrices side by side.

  • Create a new cell and generate the Random Forest's confusion matrix by adding this code:
# Cell 15: Confusion matrix for random forest
fig, ax = plt.subplots(figsize=(6, 5))
ConfusionMatrixDisplay.from_predictions(y_test, rf_preds, ax=ax, cmap="Greens")
ax.set_title("Random Forest - Confusion Matrix")
plt.tight_layout()
plt.show()

What does this code do?

This creates a confusion matrix using a green colormap (Greens) so you can visually distinguish it from the logistic regression's blue matrix above. The matrix shows how many predictions the model got right (diagonal) and wrong (off-diagonal) for each class.

  • Run the cell.
  • You should see a green confusion matrix. Scroll up to compare it against the blue logistic regression matrix from Step 4. Look at whether the Random Forest correctly identified more people who have bank accounts (the bottom-right cell).

You now have two models and two confusion matrices. Time to put the numbers side by side.

  • Create a new cell and print a direct comparison of both models by adding this code:
# Cell 16: Model comparison
print(f"Logistic Regression Accuracy: {lr_accuracy:.4f}")
print(f"Random Forest Accuracy:       {rf_accuracy:.4f}")
print(f"Improvement: {(rf_accuracy - lr_accuracy)*100:.2f} percentage points")

What does this code do?

This prints both accuracy scores on consecutive lines so you can compare at a glance. The third line calculates the exact improvement in percentage points, making it easy to quantify how much the Random Forest gained over logistic regression.

  • Run the cell.
  • You should see the Random Forest accuracy is higher than the logistic regression, with the exact improvement printed on the third line.

Why did the Random Forest win?

Logistic regression draws a single straight-line boundary between "has bank account" and "does not." That works when the relationship between features and outcome is simple.

Real-world data is messier. A farmer in rural Tanzania with a cellphone faces a completely different financial picture than a government employee in Nairobi. Random Forest captures these layered interactions because each tree can split the data differently, and the ensemble combines those perspectives.

Extract feature importances and visualize top predictors

Knowing that the Random Forest is more accurate is useful. Knowing why it predicts the way it does is what makes your project stand out. Feature importances tell you which columns in the data had the most influence on the model's decisions.

  • Create a new cell and extract the top 10 most important features by adding this code:
# Cell 17: Feature importance
importances = pd.Series(rf.feature_importances_, index=X.columns)
top_features = importances.nlargest(10)

fig, ax = plt.subplots(figsize=(8, 6))
top_features.sort_values().plot(kind="barh", ax=ax, color="#3498db")
ax.set_title("Top 10 Most Important Features for Predicting Bank Access")
ax.set_xlabel("Feature Importance")
plt.tight_layout()
plt.show()

What does this code do?

  • rf.feature_importances_ returns a score for every feature showing how much it contributed to the model's predictions. Higher scores mean greater influence.
  • Wrapping the scores in a pd.Series with X.columns as the index labels each score with the matching feature name, making the chart readable.
  • .nlargest(10) pulls the top 10 features. The horizontal bar chart (barh) makes long feature names easy to read.
  • Run the cell.
  • You should see a horizontal bar chart with blue bars showing the top 10 features. Look at which features appear at the top. Do you see factors like age, country, job type, or cellphone access?

Chart looks empty or features are missing?

Make sure you used X.columns (capital X, the features DataFrame from Cell 11), not X_train.columns. Both work, but X matches the original feature set that the importances were computed against.

Need more help? Help me fix my feature importance chart in Jupyter.

✔️ Awesome, I've got everything!

Great. Double check you have saved your notebook before moving on.

ⓧ I'd like to double check the full code

Your notebook has grown past the point where showing the full file inline is practical. Scroll through your notebook and confirm these cells are present, in order:

  • Cells 1-13 from previous steps (imports, data loading, exploration, cleaning, logistic regression).
  • Cell 14: Train random forest. Creates rf, fits, predicts, prints accuracy and classification report.
  • Cell 15: Confusion matrix for random forest with Greens colormap.
  • Cell 16: Model comparison printing both accuracies and the improvement.
  • Cell 17: Feature importance. Creates importances and top_features, then plots a horizontal bar chart.

If any cell is missing or out of order, scroll up to the relevant chunk in this step and copy the code again.

Write your business recommendation

Charts and accuracy scores are tools. The real deliverable is telling a stakeholder what to do about it. Your feature importance chart just revealed which factors matter most for predicting bank account access. Now translate that into a concrete recommendation for Tanzania.

  • Create a new Markdown cell below your feature importance chart. In VS Code, click the + Markdown button at the top of your notebook (or change a code cell's type to Markdown).
  • Title the cell "Business Recommendation" using a Markdown heading.
  • Answer these three questions in your cell:
  • Who in Tanzania is most underserved? Look at your Tanzania deep-dive from Step 2 and the feature importances. Which demographic groups have the lowest bank account access?
  • What intervention would you recommend? Be specific. Name the type of outreach, the target group, and the channel. For example: "Partner with mobile money providers to target rural, self-employed individuals aged 25-45 with no formal education, as these groups show the lowest bank account access but highest cellphone ownership."
  • What evidence supports your recommendation? Reference specific features from your chart and specific percentages from your Tanzania analysis.

This is your resume differentiator

Anyone can train a model and report an accuracy number. What hiring managers look for is the "so what": can you turn a model's output into a recommendation that a non-technical stakeholder can act on? Write this cell as if you are presenting to a bank executive.

  • Run the Markdown cell to render it.
  • Read through your recommendation. It should name a specific group, a specific action, and specific evidence from your analysis.

Before you check the box, confirm three things: your Random Forest accuracy is higher than your logistic regression, you have a feature importance bar chart, and your Markdown cell contains a concrete recommendation backed by evidence.

Secret mission

Engineer Features and Tune Your Model

Your Random Forest already beats logistic regression. But real data scientists push further by engineering custom features and tuning hyperparameters. Can you improve your model's accuracy even more?

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project runs entirely locally, so there are no ongoing costs.

Resources you used:

  • financial-inclusion project folder containing your Jupyter notebook and dataset CSV.
  • Python packages installed via pip: pandas, scikit-learn, matplotlib, and seaborn.

Keep everything running

No action needed. Choose this if you plan to keep experimenting with the notebook or want to revisit the dataset later.

  • Your financial-inclusion folder stays on your Desktop with all your work intact.
  • The Python packages remain installed and available for future projects.
  • Nothing here costs money. Everything runs locally on your machine.

Pause - I'll come back to this later

There are no running processes to stop. Your notebook and dataset are just files on disk, so they are already paused by default.

  • If you have the Jupyter notebook open in VS Code, close the tab to free up memory.
  • Your financial-inclusion folder and all installed packages will be waiting when you come back.

Delete - I don't want to use this again

Remove all project files and uninstall the Python packages to start fresh.

  • Delete the project folder by running this command in your terminal:

macOS

rm -rf ~/Desktop/financial-inclusion

Windows

rmdir /s /q %USERPROFILE%\Desktop\financial-inclusion
  • Uninstall the Python packages you installed for this project by running:
pip uninstall pandas scikit-learn matplotlib seaborn -y

Keep pandas and matplotlib

If you plan to do more data science projects, you will likely need pandas and matplotlib again. Consider skipping the uninstall command and keeping them around.

Nice Work!

Nice Work!

Nice work! You just built an end-to-end machine learning pipeline that predicts financial inclusion across East Africa and turns raw survey data into a concrete business recommendation.

You've learned how to:

  • Explore real survey data covering ~33,600 individuals across four countries and create visualizations revealing demographic patterns behind financial inclusion in Tanzania.
  • Clean, encode, and split messy survey data into a modeling-ready format using pandas and scikit-learn, then train and compare a Logistic Regression against a Random Forest to see firsthand why model selection matters.
  • Extract feature importances from your best model and translate them into a data-driven business recommendation for improving financial inclusion in Tanzania.
  • Secret Mission: Engineer custom features and tune hyperparameters to push your model's accuracy even further.

Ready to quiz yourself?