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:

  1. Data collection
  2. Data cleaning & preprocessing
  3. Exploratory data analysis
  4. Model analysis and testing
  5. 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:

  • FAO Food Balance Sheets

Import Python Libraries¶

To begin this tutorial, first import all the required Python libraries as shown below.

In [1]:
# Import libraries
import pandas as pd
import numpy as np
from sklearn.preprocessing import LabelEncoder, StandardScaler
In [2]:
import warnings
warnings.filterwarnings("ignore")

Download and Import the Data¶

  1. Download the FAO nutrition dataset CSV file.
  2. Move the CSV to the same folder as your notebook.
  3. Load the CSV using Pandas:
In [3]:
# Load CSV
from google.colab import files
uploaded = files.upload()
# Load data
df = pd.read_csv("FAOSTAT_data_en_12-12-2025.csv")
Upload widget is only available when the cell has been executed in the current browser session. Please rerun this cell to enable.
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 name
    • Year – observation year
    • Food_kcal – daily calorie supply per capita
    • Protein_g – protein supply per capita
    • Fat_g – fat supply per capita
    • FastFoodScore – score measuring fast food consumption
    • Protein_ratio, Fat_ratio – derived nutritional ratios
    • HighRiskDiet – 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.

In [4]:
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.

In [5]:
# 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.

In [6]:
# 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) and Year to compute total nutrient supply per country-year.

In [7]:
# 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 / Calories

  • Fat_ratio = Fat supply / Calories

  • FastFoodScore = Fat_ratio (an estimate of diet risk)

In [8]:
# 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%
In [9]:
# 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 using LabelEncoder.
In [10]:
# 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.
In [11]:
# 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.
In [12]:
# 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)

In [13]:
# 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.
In [14]:
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.

In [15]:
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()
No description has been provided for this image

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.

In [16]:
# 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()
No description has been provided for this image

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.

In [17]:
# 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()
No description has been provided for this image

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.

In [18]:
# 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()
No description has been provided for this image

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¶

In [19]:
# 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()
No description has been provided for this image
No description has been provided for this image

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¶

In [20]:
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()
No description has been provided for this image

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.

In [21]:
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()
No description has been provided for this image

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.

In [22]:
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()
No description has been provided for this image

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¶

  1. Diets with higher fat ratios correspond to "High Risk / Unhealthy" category.
  2. Most countries have moderate diets; only a few are extremely healthy or unhealthy.
  3. FastFoodScore effectively separates risk categories and can be used as a target variable.
  4. Trends over years suggest slight global shifts toward higher fat consumption in some countries.
  5. 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_ratio
  • Fat_ratio
  • FastFoodScore
  • Area_encoded (categorical encoding for country)

Target: HighRiskDiet (binary: 1 = high-risk / top 25% FastFoodScore, 0 = low/moderate risk)

In [23]:
# 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.

In [24]:
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

In [25]:
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

In [26]:
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

No description has been provided for this image
No description has been provided for this image
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

No description has been provided for this image
No description has been provided for this image
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

No description has been provided for this image
No description has been provided for this image

Step 5: Feature Importance¶

In [27]:
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()
No description has been provided for this image
No description has been provided for this image

Step 6: SHAP Interpretation (Random Forest)¶

In [28]:
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")
No description has been provided for this image

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.

In [29]:
# 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)
Out[29]:
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.

In [30]:
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()
No description has been provided for this image

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.

In [31]:
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()
No description has been provided for this image

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.

In [32]:
# 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()
No description has been provided for this image

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.

In [33]:
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)
No description has been provided for this image

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.

In [34]:
df_country['Predicted_HighRisk_RF'] = results['Random Forest'].predict(X)
df_country[['Area','Year','FastFoodScore','HighRiskDiet','Predicted_HighRisk_RF']].head(10)
Out[34]:
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¶

In [35]:
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()
No description has been provided for this image

Shows the year-wise FastFoodScore for all countries. Countries like the USA show increasing trends, while others remain stable. Highlights temporal dietary changes.

In [36]:
# 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()
No description has been provided for this image

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.

In [37]:
# 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()
No description has been provided for this image

Scatterplot shows separation between high-risk (red) and low-risk (green) diets. Higher fat ratios cluster in the high-risk category.

In [38]:
# 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()
No description has been provided for this image

Shows how many years each country has a high-risk diet. Highlights countries with persistent dietary risks.

In [39]:
# 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()
No description has been provided for this image

Ranks countries by average FastFoodScore. Higher scores indicate generally unhealthier diets.

In [40]:
# 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()
No description has been provided for this image

Shows year-wise trends for top 5 high-risk countries. Useful to identify improvements or worsening of diet patterns over time.

In [41]:
# 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()
No description has been provided for this image

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.


In [41]: