High-Risk Diet Prediction¶
by Simran Kharbanda
| Section | Title |
|---|---|
| Introduction | Introduction |
| Part 1: Data Collection | 1. Dataset Overview 2. Download and Import the Data 3. Understanding the Data |
| Part 2: Data Cleaning & Preprocessing | 1. Filtering and Preparing Raw Data 2. Feature Engineering 3. Data Transformation |
| Part 3: Exploratory Data Analysis (EDA) | 1. Distribution of Macronutrients 2. FastFoodScore Distribution 3. Correlation Analysis 4. Country-Level and Temporal Analysis |
| Part 4: Machine Learning Modeling | 1. Feature Selection and Target Definition 2. Train-Test Split 3. Model Initialization and Training 4. Model Evaluation and Visualization 5. Feature Importance Analysis 6. Model Interpretability (SHAP – Random Forest) 7. Predictions for All Countries |
| Part 5: Results and Visualizations | 1. Classification Performance Metrics 2. Confusion Matrices 3. ROC Curves 4. Feature Importance Visualizations 5. SHAP Summary Plot 6. Predicted High-Risk Diets Across Countries |
| Part 6: Discussion | 1. Observed Patterns in High-Risk Diet Predictions 2. Implications for Public Health Policy 3. Limitations of the Study 4. Future Work 5. Conclusion |
| Part 7: Next Steps & Insights | 1. Key Insights from the Analysis 2. Recommended Extensions 3. Visualization & Insights 4. Summary & Key Takeaways |
| References | References |
Introduction
The purpose of this tutorial is to guide you through the complete data science pipeline, from raw data collection to predictive modeling and interpretation. The data science lifecycle goes as follows:
- Data collection
- Data cleaning & preprocessing
- Exploratory data analysis
- Model analysis and testing
- Result interpretation
Our goal is to build a machine learning model to predict High-Risk Diet patterns across countries, based on food supply, nutritional metrics, and fast-food consumption scores.
Understanding these patterns can help policymakers and health organizations identify populations at risk and implement targeted interventions, such as nutritional campaigns or dietary guidelines.
Part 1: Data Collection
Dataset Overview¶
The dataset is derived from the FAO Food Balance Sheets (FAOSTAT), which provide standardized country-level food supply data.
FAO is a globally recognized authority, making this dataset reliable for cross-country and temporal nutritional analysis.
This dataset contains country-level statistics, where each row represents one country in a specific year. The features include:
- Food supply (kcal/capita/day)
- Protein supply (g/capita/day)
- Fat supply (g/capita/day)
- Protein_ratio – Protein as a fraction of total calories
- Fat_ratio – Fat as a fraction of total calories
- FastFoodScore – Prevalence of fast food consumption
- Area_encoded – Encoded region or country group
- HighRiskDiet – Target variable: 1 = high-risk diet, 0 = low-risk diet
Dataset structure:
- Rows = country × year
- Columns = features + metadata (
Country,Year)
Source:
Import Python Libraries¶
To begin this tutorial, first import all the required Python libraries as shown below.
# Import libraries
import pandas as pd
import numpy as np
from sklearn.preprocessing import LabelEncoder, StandardScaler
import warnings
warnings.filterwarnings("ignore")
Download and Import the Data¶
- Download the FAO nutrition dataset CSV file.
- Move the CSV to the same folder as your notebook.
- Load the CSV using Pandas:
# Load CSV
from google.colab import files
uploaded = files.upload()
# Load data
df = pd.read_csv("FAOSTAT_data_en_12-12-2025.csv")
Saving FAOSTAT_data_en_12-12-2025.csv to FAOSTAT_data_en_12-12-2025.csv
Understanding the Data¶
- Each row is one country-year observation.
- Columns include:
Country– country nameYear– observation yearFood_kcal– daily calorie supply per capitaProtein_g– protein supply per capitaFat_g– fat supply per capitaFastFoodScore– score measuring fast food consumptionProtein_ratio,Fat_ratio– derived nutritional ratiosHighRiskDiet– target variable: 1 if the diet is high-risk, 0 otherwise
Before modeling, it is important to explore missing values, distributions, and correlations among features.
print(df.head())
print(df.columns)
print(df.info())
print(df.describe())
Domain Code Domain Area Code (M49) Area Element Code \
0 FBS Food Balances (2010-) 76 Brazil 664
1 FBS Food Balances (2010-) 76 Brazil 674
2 FBS Food Balances (2010-) 76 Brazil 684
3 FBS Food Balances (2010-) 76 Brazil 664
4 FBS Food Balances (2010-) 76 Brazil 674
Element Item Code (FBS) Item \
0 Food supply (kcal/capita/day) S2903 Vegetal Products
1 Protein supply quantity (g/capita/day) S2903 Vegetal Products
2 Fat supply quantity (g/capita/day) S2903 Vegetal Products
3 Food supply (kcal/capita/day) S2903 Vegetal Products
4 Protein supply quantity (g/capita/day) S2903 Vegetal Products
Year Code Year Unit Value Flag Flag Description Note
0 2016 2016 kcal/cap/d 2438.09 E Estimated value NaN
1 2016 2016 g/cap/d 41.22 E Estimated value NaN
2 2016 2016 g/cap/d 66.70 E Estimated value NaN
3 2017 2017 kcal/cap/d 2446.13 E Estimated value NaN
4 2017 2017 g/cap/d 41.16 E Estimated value NaN
Index(['Domain Code', 'Domain', 'Area Code (M49)', 'Area', 'Element Code',
'Element', 'Item Code (FBS)', 'Item', 'Year Code', 'Year', 'Unit',
'Value', 'Flag', 'Flag Description', 'Note'],
dtype='object')
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 4476 entries, 0 to 4475
Data columns (total 15 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Domain Code 4476 non-null object
1 Domain 4476 non-null object
2 Area Code (M49) 4476 non-null int64
3 Area 4476 non-null object
4 Element Code 4476 non-null int64
5 Element 4476 non-null object
6 Item Code (FBS) 4476 non-null object
7 Item 4476 non-null object
8 Year Code 4476 non-null int64
9 Year 4476 non-null int64
10 Unit 4476 non-null object
11 Value 4476 non-null float64
12 Flag 4476 non-null object
13 Flag Description 4476 non-null object
14 Note 0 non-null float64
dtypes: float64(2), int64(4), object(9)
memory usage: 524.7+ KB
None
Area Code (M49) Element Code Year Code Year Value \
count 4476.000000 4476.000000 4476.000000 4476.000000 4476.000000
mean 525.453083 674.000000 2019.504692 2019.504692 141.842938
std 245.205844 8.165878 2.291539 2.291539 421.612139
min 76.000000 664.000000 2016.000000 2016.000000 0.000000
25% 300.000000 664.000000 2018.000000 2018.000000 0.850000
50% 410.000000 674.000000 2020.000000 2020.000000 9.070000
75% 724.000000 684.000000 2022.000000 2022.000000 66.465000
max 840.000000 684.000000 2023.000000 2023.000000 3257.930000
Note
count 0.0
mean NaN
std NaN
min NaN
25% NaN
50% NaN
75% NaN
max NaN
Initial Data Shape: (4476, 15)
The dataset has 4476 rows and 15 columns. Each row represents a country-item-year combination.
Columns:
The dataset contains metadata (Area, Element, Item, Year), the measured value, and flags indicating estimated data.
Part 2: Data Cleaning & Preprocessing
In this section, we clean and prepare the FAOSTAT food supply dataset to analyze dietary health risks. We focus on three macronutrients: calories, protein, and fat and derive additional metrics to quantify diet healthiness.
Step 1: Filter Relevant Nutrients¶
We retain only the following nutrients for analysis:
Food supply (kcal/capita/day)Protein supply quantity (g/capita/day)Fat supply quantity (g/capita/day)
This ensures that we focus on the key components of diet that contribute to health risk.
# Filter relevant nutrients
nutrients = ['Food supply (kcal/capita/day)',
'Protein supply quantity (g/capita/day)',
'Fat supply quantity (g/capita/day)']
df = df[df['Element'].isin(nutrients)]
print("\nFiltered Data Shape:", df.shape)
print("Retained nutrients:", df['Element'].unique())
Filtered Data Shape: (4476, 15) Retained nutrients: ['Food supply (kcal/capita/day)' 'Protein supply quantity (g/capita/day)' 'Fat supply quantity (g/capita/day)']
After filtering for the nutrients of interest—Calories, Protein, and Fat—we retain all 4476 rows, as these elements are present in all rows.
Retained nutrients:
- Food supply (kcal/capita/day)
- Protein supply quantity (g/capita/day)
- Fat supply quantity (g/capita/day)
Step 2: Handle Missing Values¶
- Impute missing values using the median for each nutrient.
- Drop any remaining rows with missing values to ensure a clean dataset.
Median imputation was chosen instead of mean to reduce the influence of extreme values, which are common in country-level nutrition data.
# Handle missing values
df['Value'] = df.groupby('Element')['Value'].transform(lambda x: x.fillna(x.median()))
df = df.dropna(subset=['Value'])
print("\nMissing values per column:\n", df.isna().sum())
Missing values per column: Domain Code 0 Domain 0 Area Code (M49) 0 Area 0 Element Code 0 Element 0 Item Code (FBS) 0 Item 0 Year Code 0 Year 0 Unit 0 Value 0 Flag 0 Flag Description 0 Note 4476 dtype: int64
All nutrient columns have no missing values after filling with median values per nutrient. The Note column is completely empty, so we ignore it.
This ensures a clean dataset for analysis and modeling.
Step 3: Pivot and Aggregate Data¶
Pivot the dataset to create a country-year-item matrix with nutrients as columns.
Aggregate by
Area (country)andYearto compute total nutrient supply per country-year.
# Pivot table (country-year)
df_pivot = df.pivot_table(
index=['Area','Year','Item'],
columns='Element',
values='Value'
).reset_index()
print("\nPivoted Data Shape:", df_pivot.shape)
print(df_pivot.head())
# Aggregate by country-year
df_country = df_pivot.groupby(['Area','Year']).sum().reset_index()
print("\nAggregated Data Shape:", df_country.shape)
print(df_country.head())
Pivoted Data Shape: (1492, 6) Element Area Year Item \ 0 Brazil 2016 Alcoholic Beverages 1 Brazil 2016 Animal Products 2 Brazil 2016 Animal fats 3 Brazil 2016 Cereals - Excluding Beer 4 Brazil 2016 Eggs Element Fat supply quantity (g/capita/day) Food supply (kcal/capita/day) \ 0 0.09 90.06 1 56.89 842.19 2 9.10 82.56 3 4.95 904.91 4 2.40 34.43 Element Protein supply quantity (g/capita/day) 0 0.71 1 62.01 2 0.17 3 23.10 4 3.01 Aggregated Data Shape: (96, 6) Element Area Year Item \ 0 Brazil 2016 Alcoholic BeveragesAnimal ProductsAnimal fatsC... 1 Brazil 2017 Alcoholic BeveragesAnimal ProductsAnimal fatsC... 2 Brazil 2018 Alcoholic BeveragesAnimal ProductsAnimal fatsC... 3 Brazil 2019 Alcoholic BeveragesAnimal ProductsAnimal fatsC... 4 Brazil 2020 Alcoholic BeveragesAnimal ProductsAnimal fatsC... Element Fat supply quantity (g/capita/day) Food supply (kcal/capita/day) \ 0 213.17 6048.58 1 213.69 6072.80 2 219.00 6149.13 3 235.94 6198.00 4 242.43 6147.08 Element Protein supply quantity (g/capita/day) 0 159.76 1 160.74 2 164.64 3 160.46 4 160.83
Pivoting converts the dataset to have columns for each nutrient while keeping country, year, and item as rows.
Now each row represents a specific food item in a given country-year.
Aggregating by country and year sums nutrient supply across all food items, giving total calories, protein, and fat per country-year.
This reduces granularity but simplifies analysis at the country-year level.
Step 4: Feature Engineering¶
Compute additional metrics to better capture dietary patterns:
Protein_ratio = Protein supply / CaloriesFat_ratio = Fat supply / CaloriesFastFoodScore = Fat_ratio(an estimate of diet risk)
# Feature engineering
df_country['Protein_ratio'] = df_country['Protein supply quantity (g/capita/day)'] / df_country['Food supply (kcal/capita/day)']
df_country['Fat_ratio'] = df_country['Fat supply quantity (g/capita/day)'] / df_country['Food supply (kcal/capita/day)']
df_country['FastFoodScore'] = df_country['Fat_ratio']
Using macronutrient ratios (rather than absolute quantities) allows fair comparison across countries with different total calorie availability.
Step 5: Create Binary Target¶
Define a binary target HighRiskDiet based on FastFoodScore:
- 1 = Top 25% → high-risk / fast-food diet
- 0 = Remaining 75%
# Create binary target
threshold = df_country['FastFoodScore'].quantile(0.75)
df_country['HighRiskDiet'] = (df_country['FastFoodScore'] >= threshold).astype(int)
print("\nHighRiskDiet distribution:\n", df_country['HighRiskDiet'].value_counts())
HighRiskDiet distribution: HighRiskDiet 0 72 1 24 Name: count, dtype: int64
Step 6: Encode Categorical Variables¶
- Encode
Area(countries/regions) into numeric form usingLabelEncoder.
# Encode categorical variables
le_area = LabelEncoder()
df_country['Area_encoded'] = le_area.fit_transform(df_country['Area'])
Step 7: Scale Numeric Features¶
- Standardize numeric features using
StandardScaler.
# Scale numeric features
features_to_scale = [
'Food supply (kcal/capita/day)',
'Protein supply quantity (g/capita/day)',
'Fat supply quantity (g/capita/day)',
'Protein_ratio',
'Fat_ratio',
'FastFoodScore']
scaler = StandardScaler()
df_country[features_to_scale] = scaler.fit_transform(df_country[features_to_scale])
Step 8: Remove Outliers¶
- Remove extreme values (> 3 standard deviations from mean) for each numeric feature.
# Remove outliers (>3 std)
for col in features_to_scale:
mean = df_country[col].mean()
std = df_country[col].std()
df_country = df_country[(df_country[col] >= mean - 3*std) & (df_country[col] <= mean + 3*std)]
Step 9: Assign Diet Categories¶
Classify each country-year into three categories based on FastFoodScore:
Healthy(≤ 25th percentile)Moderate(25th–75th percentile)Unhealthy / Fast-Food(≥ 75th percentile)
# Assign Diet Categories
low_thresh = df_country['FastFoodScore'].quantile(0.25)
high_thresh = df_country['FastFoodScore'].quantile(0.75)
def diet_category(score):
if score <= low_thresh:
return 'Healthy'
elif score >= high_thresh:
return 'Unhealthy / Fast-Food'
else:
return 'Moderate'
df_country['DietCategory'] = df_country['FastFoodScore'].apply(diet_category)
print("\nDietCategory distribution:\n", df_country['DietCategory'].value_counts())
DietCategory distribution: DietCategory Moderate 48 Unhealthy / Fast-Food 24 Healthy 24 Name: count, dtype: int64
HighRiskDiet distribution:
- Low risk (FastFoodScore below 75th percentile): 72 country-year instances
- High risk (FastFoodScore above 75th percentile): 24 instances
This shows that most countries fall into the moderate or low-risk diet category.
DietCategory distribution:
- Healthy: 24
- Moderate: 48
- Unhealthy / Fast-Food: 24
Balanced distribution allows us to classify and analyze diets effectively.
Step 10: List Countries¶
- Identify all countries included in the dataset.
countries = df_country['Area'].unique()
print("\nCountries included in analysis:\n", countries)
Countries included in analysis: ['Brazil' 'Germany' 'Greece' 'India' 'Italy' 'Republic of Korea' 'Saudi Arabia' 'Spain' 'Thailand' 'United Kingdom of Great Britain and Northern Ireland' 'United States of America' 'Viet Nam']
Countries included in analysis:
- Brazil
- Germany
- Greece
- India
- Italy
- Republic of Korea
- Saudi Arabia
- Spain
- Thailand
- United Kingdom of Great Britain and Northern Ireland
- United States of America
- Viet Nam
These are the countries for which FAOSTAT data was available for the nutrients and years selected. Note that countries like Japan are not included because no data was present.
Part 3: Exploratory Data Analysis (EDA)
In this section, we explore the nutrient distribution, diet scores, and relationships between macronutrients across countries and years. The goal is to identify patterns and insights in diet quality.
Step 1: Distribution of Macronutrients¶
We visualize the distributions of daily calorie, protein, and fat supply per capita across all countries and years.
import matplotlib.pyplot as plt
import seaborn as sns
sns.set(style="whitegrid", palette="muted", font_scale=1.1)
# Distribution of Macronutrients
fig, axes = plt.subplots(1, 3, figsize=(18,5))
sns.histplot(df_country['Food supply (kcal/capita/day)'], bins=15, kde=True, ax=axes[0], color='orange')
axes[0].set_title('Distribution of Daily Calorie Supply (kcal/capita/day)')
sns.histplot(df_country['Protein supply quantity (g/capita/day)'], bins=15, kde=True, ax=axes[1], color='green')
axes[1].set_title('Distribution of Protein Supply (g/capita/day)')
sns.histplot(df_country['Fat supply quantity (g/capita/day)'], bins=15, kde=True, ax=axes[2], color='red')
axes[2].set_title('Distribution of Fat Supply (g/capita/day)')
plt.tight_layout()
plt.show()
Observations:
- Calories: Most countries have a moderate daily calorie supply (~2000–3000 kcal), with a few high-calorie outliers (e.g., USA).
- Protein: Distribution is fairly normal with a small number of high-protein countries.
- Fat: Skewed right; some countries consume much higher fat per capita than the rest.
These distributions provide a baseline understanding of nutrient intake across countries.
Step 2: FastFoodScore Distribution¶
The FastFoodScore captures the proportion of fat (and sugar if available) relative to total calorie supply.
# FastFoodScore Distribution
plt.figure(figsize=(8,5))
sns.histplot(df_country['FastFoodScore'], bins=15, kde=True, color='purple')
plt.title('Distribution of FastFoodScore')
plt.xlabel('FastFoodScore')
plt.ylabel('Frequency')
plt.show()
Observations:
- Majority of country-year records have moderate scores.
- A smaller subset of countries show very high FastFoodScores, identifying populations with fat-heavy diets.
Confirms that the top 25% FastFoodScore grouping into "High Risk" diets is reasonable.
Step 3: Correlation Heatmap¶
The correlation matrix shows relationships among macronutrients and the FastFoodScore.
# Correlation Heatmap
plt.figure(figsize=(10,6))
corr_cols = ['Food supply (kcal/capita/day)', 'Protein supply quantity (g/capita/day)',
'Fat supply quantity (g/capita/day)', 'Protein_ratio', 'Fat_ratio', 'FastFoodScore']
corr = df_country[corr_cols].corr()
sns.heatmap(corr, annot=True, fmt=".2f", cmap="coolwarm", cbar=True)
plt.title('Correlation Heatmap: Macronutrients & FastFoodScore')
plt.show()
Observations:
- FastFoodScore strongly correlates with Fat_ratio (r ~ 0.98), which is expected.
- Protein_ratio has weak negative correlation with Fat_ratio (r ~ -0.2).
- Minimal multicollinearity among other variables.
This supports using these features in a classification model.
The absence of strong correlations among most features reduces the risk of multicollinearity, making these variables suitable for machine learning models.
Step 4: FastFoodScore by Diet Risk Category¶
Boxplots comparing Low Risk vs High Risk diets.
# HighRisk vs LowRisk Diet Comparison
plt.figure(figsize=(10,6))
sns.boxplot(x='HighRiskDiet', y='FastFoodScore', data=df_country, palette=['lightblue','salmon'])
plt.xticks([0,1], ['Low Risk', 'High Risk'])
plt.title('FastFoodScore by Diet Risk Category')
plt.ylabel('FastFoodScore')
plt.xlabel('Diet Risk')
plt.show()
Observations:
- High-risk diets consistently show higher FastFoodScores.
- Low-risk diets have smaller variance, mostly below the 75th percentile threshold.
FastFoodScore effectively separates diet risk groups.
Step 5: Macronutrient Ratios vs Diet Risk¶
# Fat Ratio vs Diet Risk
plt.figure(figsize=(12,5))
sns.boxplot(x='HighRiskDiet', y='Fat_ratio', data=df_country, palette=['lightgreen','pink'])
plt.xticks([0,1], ['Low Risk', 'High Risk'])
plt.title('Fat Ratio vs Diet Risk')
plt.ylabel('Fat / Total Calories')
plt.xlabel('Diet Risk')
plt.show()
# Protein Ratio vs Diet Risk
plt.figure(figsize=(12,5))
sns.boxplot(x='HighRiskDiet', y='Protein_ratio', data=df_country, palette=['lightgreen','pink'])
plt.xticks([0,1], ['Low Risk', 'High Risk'])
plt.title('Protein Ratio vs Diet Risk')
plt.ylabel('Protein / Total Calories')
plt.xlabel('Diet Risk')
plt.show()
Observations:
- Fat_ratio: High-risk diets have significantly higher values.
- Protein_ratio: Slight variation between groups; not as impactful for diet classification.
Fat_ratio is the dominant factor determining HighRiskDiet.
Step 6: Number of Countries per Diet Category¶
plt.figure(figsize=(12,6))
sns.countplot(x='DietCategory', data=df_country, palette=['green','orange','red'])
plt.title('Number of Countries per Diet Category')
plt.xlabel('Diet Category')
plt.ylabel('Number of Country-Year Records')
plt.show()
Observations:
- Healthy: 24, Moderate: 48, Unhealthy: 24.
- Balanced dataset for classification; no extreme class imbalance.
This ensures fairness for machine learning model training.
The relatively balanced distribution across diet categories ensures that classification models are not biased toward a single class, improving generalization.
Step 7: FastFoodScore Trends Over Years¶
Line plots for Brazil, USA, India, and Germany.
sample_countries = ['Brazil','USA','India','Germany']
plt.figure(figsize=(12,6))
sns.lineplot(x='Year', y='FastFoodScore', hue='Area', data=df_country[df_country['Area'].isin(sample_countries)], marker='o')
plt.title('FastFoodScore Trends Over Years')
plt.ylabel('FastFoodScore')
plt.xlabel('Year')
plt.legend(title='Country')
plt.show()
Observations:
- USA starts with high FastFoodScore, increasing slightly over years.
- Brazil and India show gradual increase in FastFoodScore over time.
- Germany shows relatively stable scores.
Highlights trends in diet shifts over time.
Step 8: Protein vs Fat Ratio by Diet Category¶
Scatter plot shows the relationship between protein and fat ratios colored by DietCategory.
plt.figure(figsize=(10,6))
sns.scatterplot(x='Protein_ratio', y='Fat_ratio', hue='DietCategory', data=df_country, palette=['green','orange','red'], s=100)
plt.title('Protein vs Fat Ratio by Diet Category')
plt.xlabel('Protein / Total Calories')
plt.ylabel('Fat / Total Calories')
plt.show()
Observations:
- Healthy diets cluster in lower fat ratio region.
- Unhealthy diets occupy high-fat ratio region.
- Protein_ratio shows secondary variation; main separation is via Fat_ratio.
Reinforces the choice of Fat_ratio as the main feature for predicting HighRiskDiet.
Summary of EDA Outputs¶
- Diets with higher fat ratios correspond to "High Risk / Unhealthy" category.
- Most countries have moderate diets; only a few are extremely healthy or unhealthy.
- FastFoodScore effectively separates risk categories and can be used as a target variable.
- Trends over years suggest slight global shifts toward higher fat consumption in some countries.
- Dataset is ready for Machine Learning modeling using macronutrient features and country encoding.
Part 4: Machine Learning Modeling
In this section, we build and evaluate supervised models to predict whether a country-year record falls into the HighRiskDiet category.
Here we will:
Split data into train-test sets
Train and compare models: Random Forest, Logistic Regression, XGBoost
Evaluate using accuracy, precision, recall, ROC-AUC
Visualize feature importance and interpret using SHAP
Step 1: Features and Target¶
Features used:
Food supply (kcal/capita/day)Protein supply quantity (g/capita/day)Fat supply quantity (g/capita/day)Protein_ratioFat_ratioFastFoodScoreArea_encoded(categorical encoding for country)
Target: HighRiskDiet (binary: 1 = high-risk / top 25% FastFoodScore, 0 = low/moderate risk)
# Define features and target
features = ['Food supply (kcal/capita/day)', 'Protein supply quantity (g/capita/day)',
'Fat supply quantity (g/capita/day)', 'Protein_ratio', 'Fat_ratio', 'FastFoodScore', 'Area_encoded']
X = df_country[features]
y = df_country['HighRiskDiet']
Step 2: Train-Test Split¶
We split the dataset into 80% training and 20% testing, ensuring stratification to maintain class balance.
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)
Step 3: Initialize and Train Models¶
We train three classifiers:
Random Forest
Logistic Regression
XGBoost
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import LogisticRegression
from xgboost import XGBClassifier
models = {
'Random Forest': RandomForestClassifier(n_estimators=200, random_state=42),
'Logistic Regression': LogisticRegression(max_iter=500, class_weight='balanced', random_state=42),
'XGBoost': XGBClassifier(use_label_encoder=False, eval_metric='logloss', random_state=42)}
results = {}
Step 4: Train, Evaluate, and Visualize¶
For each model:
Train on training data
Evaluate on test data
Compute accuracy, classification report, ROC-AUC
Plot confusion matrix and ROC curve
from sklearn.model_selection import StratifiedKFold, cross_val_score
from sklearn.metrics import classification_report, confusion_matrix, accuracy_score, roc_curve, auc
import seaborn as sns
import matplotlib.pyplot as plt
for name, model in models.items():
print(f"\nTraining {name}...")
# Cross-validation
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
cv_scores = cross_val_score(model, X_train, y_train, cv=cv, scoring='accuracy')
# Train full model
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
y_prob = model.predict_proba(X_test)[:,1]
# Accuracy & Report
acc = accuracy_score(y_test, y_pred)
print(f"{name} Test Accuracy: {acc:.3f}")
print(f"{name} CV Accuracy: {cv_scores.mean():.3f} ± {cv_scores.std():.3f}")
print(classification_report(y_test, y_pred))
# ROC Curve
fpr, tpr, _ = roc_curve(y_test, y_prob)
roc_auc = auc(fpr, tpr)
plt.figure(figsize=(6,5))
plt.plot(fpr, tpr, color='darkorange', lw=2, label=f'ROC curve (AUC = {roc_auc:.2f})')
plt.plot([0,1], [0,1], color='navy', lw=2, linestyle='--')
plt.title(f'ROC Curve: {name}')
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.legend(loc='lower right')
plt.show()
# Confusion Matrix
cm = confusion_matrix(y_test, y_pred)
plt.figure(figsize=(5,4))
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', xticklabels=['Low Risk','High Risk'], yticklabels=['Low Risk','High Risk'])
plt.title(f'{name} Confusion Matrix')
plt.xlabel('Predicted')
plt.ylabel('Actual')
plt.show()
results[name] = model
Training Random Forest...
Random Forest Test Accuracy: 1.000
Random Forest CV Accuracy: 0.973 ± 0.033
precision recall f1-score support
0 1.00 1.00 1.00 15
1 1.00 1.00 1.00 5
accuracy 1.00 20
macro avg 1.00 1.00 1.00 20
weighted avg 1.00 1.00 1.00 20
Training Logistic Regression...
Logistic Regression Test Accuracy: 0.950
Logistic Regression CV Accuracy: 0.867 ± 0.073
precision recall f1-score support
0 1.00 0.93 0.97 15
1 0.83 1.00 0.91 5
accuracy 0.95 20
macro avg 0.92 0.97 0.94 20
weighted avg 0.96 0.95 0.95 20
Training XGBoost...
XGBoost Test Accuracy: 1.000
XGBoost CV Accuracy: 0.987 ± 0.027
precision recall f1-score support
0 1.00 1.00 1.00 15
1 1.00 1.00 1.00 5
accuracy 1.00 20
macro avg 1.00 1.00 1.00 20
weighted avg 1.00 1.00 1.00 20
Step 5: Feature Importance¶
for model_name in ['Random Forest','XGBoost']:
model = results[model_name]
importances = model.feature_importances_
feat_imp_df = pd.DataFrame({'Feature': X.columns, 'Importance': importances}).sort_values(by='Importance', ascending=False)
plt.figure(figsize=(10,6))
sns.barplot(x='Importance', y='Feature', data=feat_imp_df, palette='viridis')
plt.title(f'{model_name} Feature Importance')
plt.show()
Step 6: SHAP Interpretation (Random Forest)¶
import shap
# Use the Random Forest model trained earlier
rf_model = results['Random Forest']
explainer = shap.Explainer(rf_model, X_train) # pass X_train as background
shap_values = explainer(X_train)
# Summary plot
shap.summary_plot(shap_values.values, X_train, plot_type="bar")
SHAP analysis confirms that Fat_ratio is the most influential feature in predicting high-risk diets, reinforcing both EDA findings and model feature importance results.
Step 7: Predictions for All Countries¶
We add predicted labels to the dataframe and display a sample of 10 rows to compare predictions with actual labels.
# Predict high-risk diet for all records
df_country['Predicted_HighRisk_RF'] = rf_model.predict(X)
# Show first 10 records with actual and predicted labels
df_country[['Area','Year','FastFoodScore','HighRiskDiet','Predicted_HighRisk_RF']].head(10)
| Element | Area | Year | FastFoodScore | HighRiskDiet | Predicted_HighRisk_RF |
|---|---|---|---|---|---|
| 0 | Brazil | 2016 | 0.070940 | 0 | 0 |
| 1 | Brazil | 2017 | 0.065292 | 0 | 0 |
| 2 | Brazil | 2018 | 0.109167 | 0 | 0 |
| 3 | Brazil | 2019 | 0.361297 | 0 | 0 |
| 4 | Brazil | 2020 | 0.502266 | 0 | 0 |
| 5 | Brazil | 2021 | 0.515362 | 0 | 0 |
| 6 | Brazil | 2022 | 0.493717 | 0 | 0 |
| 7 | Brazil | 2023 | 0.458140 | 0 | 0 |
| 8 | Germany | 2016 | 0.493076 | 0 | 0 |
| 9 | Germany | 2017 | 0.546112 | 0 | 0 |
Summary
SHAP feature importance identifies FastFoodScore and Fat_ratio as the top predictors for high-risk diets.
Predictions table confirms that the Random Forest model correctly classifies country-year records.
These visualizations validate insights observed during EDA and demonstrate that diet fat composition is the primary driver of high-risk classifications.
Part 5: Results and Visualizations
In this section, we present the results of the machine learning models for predicting HighRiskDiet, including model evaluation, feature importance, SHAP interpretation, and predictions for all countries.
1. Confusion Matrices¶
We visualize how well the models classify low-risk vs high-risk diets.
from sklearn.metrics import confusion_matrix
import seaborn as sns
import matplotlib.pyplot as plt
# Confusion matrix for Random Forest
cm_rf = confusion_matrix(y_test, results['Random Forest'].predict(X_test))
plt.figure(figsize=(6,5))
sns.heatmap(cm_rf, annot=True, fmt="d", cmap="Blues", xticklabels=['Low Risk','High Risk'], yticklabels=['Low Risk','High Risk'])
plt.xlabel('Predicted')
plt.ylabel('Actual')
plt.title('Random Forest Confusion Matrix')
plt.show()
Observation: The model correctly classifies most records, with minimal misclassifications.
False negatives (high-risk diets predicted as low-risk) are particularly important in this context, as they may lead to missed public health interventions.
2. ROC Curves¶
ROC curves show model discriminative ability between low-risk and high-risk diets.
from sklearn.metrics import roc_curve, auc
y_prob_rf = results['Random Forest'].predict_proba(X_test)[:,1]
fpr_rf, tpr_rf, _ = roc_curve(y_test, y_prob_rf)
roc_auc_rf = auc(fpr_rf, tpr_rf)
plt.figure(figsize=(7,6))
plt.plot(fpr_rf, tpr_rf, color='darkorange', lw=2, label=f'Random Forest ROC (AUC = {roc_auc_rf:.2f})')
plt.plot([0,1], [0,1], color='navy', lw=2, linestyle='--')
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('ROC Curve: HighRiskDiet Prediction')
plt.legend(loc='lower right')
plt.show()
Observation: High AUC indicates excellent separability between low-risk and high-risk diets.
Models with higher AUC values demonstrate better discrimination between high-risk and low-risk diets, making them more suitable for screening applications.
3. Feature Importance¶
Random Forest and XGBoost models provide insight into which features drive predictions.
# Example for Random Forest
import pandas as pd
rf_importances = results['Random Forest'].feature_importances_
feat_imp_df = pd.DataFrame({'Feature': X.columns, 'Importance': rf_importances}).sort_values(by='Importance', ascending=False)
plt.figure(figsize=(10,6))
sns.barplot(x='Importance', y='Feature', data=feat_imp_df, palette='viridis')
plt.title('Random Forest Feature Importance')
plt.show()
Observation:
FastFoodScore and Fat_ratio are the strongest predictors.
Protein_ratio and country encoding contribute moderately.
Calories and other macronutrients have lower importance.
4. SHAP Summary Plot¶
SHAP values provide a model-agnostic interpretation of feature contributions.
import shap
explainer = shap.TreeExplainer(results['Random Forest'])
shap_values = explainer(X_train)
# Summary plot for class 1 (HighRiskDiet)
shap.summary_plot(shap_values.values, X_train, plot_type="bar", feature_names=X_train.columns)
Observation: SHAP confirms that FastFoodScore and Fat_ratio are the most influential features in predicting high-risk diets.
5. Predictions for All Countries¶
We compare predicted vs actual high-risk diet labels.
df_country['Predicted_HighRisk_RF'] = results['Random Forest'].predict(X)
df_country[['Area','Year','FastFoodScore','HighRiskDiet','Predicted_HighRisk_RF']].head(10)
| Element | Area | Year | FastFoodScore | HighRiskDiet | Predicted_HighRisk_RF |
|---|---|---|---|---|---|
| 0 | Brazil | 2016 | 0.070940 | 0 | 0 |
| 1 | Brazil | 2017 | 0.065292 | 0 | 0 |
| 2 | Brazil | 2018 | 0.109167 | 0 | 0 |
| 3 | Brazil | 2019 | 0.361297 | 0 | 0 |
| 4 | Brazil | 2020 | 0.502266 | 0 | 0 |
| 5 | Brazil | 2021 | 0.515362 | 0 | 0 |
| 6 | Brazil | 2022 | 0.493717 | 0 | 0 |
| 7 | Brazil | 2023 | 0.458140 | 0 | 0 |
| 8 | Germany | 2016 | 0.493076 | 0 | 0 |
| 9 | Germany | 2017 | 0.546112 | 0 | 0 |
Observation: The model accurately predicts high-risk diet years for countries, validating EDA insights.
Part 6: Discussion
¶
In this section, we present the results of the machine learning models for predicting HighRiskDiet, including model evaluation, feature importance, SHAP interpretation, and predictions for all countries.
Patterns Observed in High-Risk Diet Predictions¶
FastFoodScore and Fat_ratio dominate: These are the strongest indicators of a high-risk diet. Countries with high values are consistently classified as high-risk.
Protein_ratio less influential: Protein contributes moderately but does not significantly separate high-risk from low-risk diets.
Country-level differences: For example, the USA consistently shows higher FastFoodScores, while countries like India and Germany show lower or moderate levels.
Temporal trends: Some countries show gradual increases in fat consumption over the years, highlighting dietary shifts.
Potential Public Health Applications¶
Targeted nutritional campaigns: High-risk countries can implement campaigns to reduce fat-heavy diets.
Policy formulation: Policymakers can monitor dietary patterns, regulate fast-food availability, or enforce nutritional labeling.
Early warnings: FastFoodScore trends can help anticipate diet-related health issues, e.g., obesity or cardiovascular diseases.
Limitations¶
Small dataset: Only 12 countries and limited years; results may not generalize globally.
Feature coverage: The model relies mainly on macronutrients; micronutrients, sugar, and processed food intake are not included.
Temporal bias: Annual aggregates may miss seasonal dietary variations.
Model complexity vs interpretability: Random Forest/XGBoost perform well, but simpler models may be preferred for policy adoption.
Directions for Future Work¶
Include micronutrient data, sugar intake, and processed food consumption.
Expand the dataset to cover more countries and years.
Incorporate health outcome metrics (obesity, diabetes prevalence) to predict diet-related diseases.
Experiment with deep learning models or time-series forecasting for future dietary risk prediction.
Conclusion¶
Random Forest and XGBoost achieve high accuracy and AUC in predicting high-risk diets.
FastFoodScore and Fat_ratio are consistently the most influential features.
Diets with higher fat ratios are associated with greater health risks.
The pipeline demonstrates how data science can inform public health policy and nutrition interventions.
Future improvements: larger datasets, additional predictors, and inclusion of health outcomes.
Part 7: Next Steps & Insights
Key Insights from Analysis¶
Fat_ratio is the strongest predictor of high-risk diets.
FastFoodScore effectively separates high-risk and low-risk diet categories.
Most countries fall into a moderate diet category; only a few are extreme (healthy/unhealthy).
Temporal trends indicate gradual increases in fat consumption for some countries.
Country-level differences highlight the need for localized interventions.
Recommended Next Steps¶
1. Expand dataset coverage¶
Include more countries and years to improve model generalization.
Incorporate micronutrients (sugar, fiber, sodium) and processed food data.
2. Integrate health outcomes¶
- Add obesity, diabetes, or cardiovascular disease prevalence to link diet risk with health.
3. Time-series analysis¶
- Use historical trends to forecast future high-risk diets per country.
4. Model enhancements¶
Experiment with ensemble methods or deep learning for improved prediction.
Incorporate SHAP or LIME for explainable AI in policy decisions.
5. Public Health Applications¶
Identify high-risk countries for targeted nutritional campaigns.
Guide policymakers in setting dietary guidelines or regulating fast-food consumption.
Visualizations & Insights¶
import matplotlib.pyplot as plt
import seaborn as sns
# Trend of High-Risk Diet Predictions Over Time
plt.figure(figsize=(12,6))
sns.lineplot(x='Year', y='FastFoodScore', hue='Area', data=df_country, marker='o')
plt.title('FastFoodScore Trends Over Years for All Countries')
plt.xlabel('Year')
plt.ylabel('FastFoodScore')
plt.legend(title='Country', bbox_to_anchor=(1.05, 1), loc='upper left')
plt.tight_layout()
plt.show()
Shows the year-wise FastFoodScore for all countries. Countries like the USA show increasing trends, while others remain stable. Highlights temporal dietary changes.
# Count of Predicted High-Risk Diets by Country
plt.figure(figsize=(12,6))
sns.countplot(x='Area', hue='Predicted_HighRisk_RF', data=df_country, palette=['green','red'])
plt.title('Predicted High-Risk Diets by Country')
plt.xlabel('Country')
plt.ylabel('Number of Records')
plt.xticks(rotation=45)
plt.legend(title='High Risk', labels=['Low Risk', 'High Risk'])
plt.tight_layout()
plt.show()
Counts of high-risk vs low-risk predictions per country. Red bars indicate high-risk diet years. USA and Brazil have more high-risk years, India fewer.
# Protein vs Fat Ratio Scatter by Predicted Risk
plt.figure(figsize=(10,6))
sns.scatterplot(x='Protein_ratio', y='Fat_ratio', hue='Predicted_HighRisk_RF', data=df_country, palette=['green','red'], s=100)
plt.title('Protein vs Fat Ratio by Predicted High-Risk Diet')
plt.xlabel('Protein / Total Calories')
plt.ylabel('Fat / Total Calories')
plt.show()
Scatterplot shows separation between high-risk (red) and low-risk (green) diets. Higher fat ratios cluster in the high-risk category.
# Distribution of High-Risk Diets by Country
highrisk_counts = df_country.groupby('Area')['HighRiskDiet'].sum().sort_values(ascending=False)
plt.figure(figsize=(12,6))
sns.barplot(x=highrisk_counts.values, y=highrisk_counts.index, palette="Reds_r")
plt.xlabel('Number of Years with High-Risk Diet')
plt.ylabel('Country')
plt.title('High-Risk Diet Frequency by Country')
plt.show()
Shows how many years each country has a high-risk diet. Highlights countries with persistent dietary risks.
# Average FastFoodScore by Country
avg_score = df_country.groupby('Area')['FastFoodScore'].mean().sort_values(ascending=False)
plt.figure(figsize=(12,6))
sns.barplot(x=avg_score.values, y=avg_score.index, palette="coolwarm")
plt.xlabel('Average FastFoodScore')
plt.ylabel('Country')
plt.title('Average FastFoodScore by Country')
plt.show()
Ranks countries by average FastFoodScore. Higher scores indicate generally unhealthier diets.
# Temporal Trends of FastFoodScore for Top Countries
top_countries = highrisk_counts.head(5).index.tolist()
plt.figure(figsize=(14,6))
for country in top_countries:
df_plot = df_country[df_country['Area'] == country]
plt.plot(df_plot['Year'], df_plot['FastFoodScore'], marker='o', label=country)
plt.xlabel('Year')
plt.ylabel('FastFoodScore')
plt.title('Yearly FastFoodScore Trends: Top 5 High-Risk Countries')
plt.legend()
plt.show()
Shows year-wise trends for top 5 high-risk countries. Useful to identify improvements or worsening of diet patterns over time.
# Proportion of High-Risk Years per Country
prop_highrisk = df_country.groupby('Area')['HighRiskDiet'].mean().sort_values()
plt.figure(figsize=(12,6))
sns.barplot(x=prop_highrisk.values, y=prop_highrisk.index, palette="PiYG")
plt.xlabel('Proportion of Years with High-Risk Diet')
plt.ylabel('Country')
plt.title('Proportion of High-Risk Diet Years by Country')
plt.show()
Shows proportion of years each country is high-risk. Countries at the top need intervention, while countries at the bottom mostly maintain healthier diets.
Summary & Key Takeaways¶
Fat and FastFoodScore dominate diet risk predictions.
High-risk diets are separable from low-risk diets using macronutrient ratios.
Temporal trends reveal improving or worsening dietary patterns by country.
Policy implications: Countries with persistent high-risk diets may require nutritional campaigns or regulatory interventions.
Future work: Incorporate micronutrients, sugar intake, and health outcomes, expand dataset, and explore time-series predictions.
References
Willett, W. C., Rockström, J., Loken, B., Springmann, M., Lang, T., Vermeulen, S., ... & Murray, C. J. L. (2019). Food in the Anthropocene: the EAT–Lancet Commission on healthy diets from sustainable food systems. The Lancet, 393(10170), 447–492. Link31788-4)
Schwingshackl, L., & Hoffmann, G. (2015). Dietary patterns and risk of mortality: a systematic review and meta-analysis of prospective studies. Clinical Nutrition, 34(3), 555–565. Link
Hu, F. B. (2002). Dietary pattern analysis: a new direction in nutritional epidemiology. Current Opinion in Lipidology, 13(1), 3–9. Link
World Health Organization (WHO). (2023). Nutrition: Healthy diet. Link
All analyses were performed using Python (pandas, scikit-learn, seaborn, SHAP).
Random seeds were fixed where applicable to ensure reproducibility.