A country-level economic stress model built on 12,762 country-year observations across 215 countries, 1960–2025. Predicts a country's economic stress score from raw macro indicators (GDP growth, inflation, unemployment, food security, population) — not from the sub-scores that define the target.
final_economic_stress_score (0–100, higher = worse) is a derived
metric: it's an exact weighted sum —
`0.25×inflation_score + 0.25×unemployment_score + 0.20×gdp_growth_score
- 0.15×income_vulnerability_score + 0.15×food_pressure_score
— verified via least-squares fit (residual std ≈ 0.003).economic_stress_scoreis an exact duplicate of it, andstress_category` (Low/Moderate/High/Severe) is just a direct bucketing of the same number. Feeding any of those six columns into a model "predicting" the target would be circular.
The real question: can raw macro indicators — GDP growth, inflation, unemployment, food security, population, agricultural land — forecast a country's stress level for years the model hasn't seen? That's what this project answers, using a genuine temporal holdout (train on ≤2020, test on 2021–2025) rather than a random split, since a random split would let the model see a country's 2023 data while "predicting" its 2019 — not how this would work in production.
| Model | Test R² (2021–2025 holdout) | Test MAE |
|---|---|---|
| Random Forest (best) | 0.637 | 7.9 points |
| Gradient Boosting | 0.581 | 9.5 points |
| Linear Regression | 0.573 | 9.0 points |
The strongest predictor by far is a country's own recent stress trend
(stress_3yr_avg, 59% of importance) — stress is autocorrelated: countries
under strain tend to stay under strain year to year. GDP growth and the
prior year's stress level are the next-strongest signals. See
reports/figures/11_feature_importance.png.
geo_stress_project/
├── data/
│ ├── raw/ # original CSV as uploaded
│ └── processed/ # cleaned, imputed, feature-ready CSV
├── notebooks/
│ └── eda.py # exploratory analysis -> reports/figures/
├── src/
│ ├── preprocessing.py # cleaning, imputation, lag/rolling features
│ ├── features.py # leakage-safe feature matrix
│ ├── train.py # trains & compares 3 models (temporal split)
│ └── evaluate.py # diagnostic plots for the chosen model
├── models/
│ ├── best_model.pkl
│ └── feature_columns.pkl
├── reports/
│ ├── figures/ # 11 PNG charts (EDA + model diagnostics)
│ ├── eda_summary.json
│ ├── model_comparison.json
│ └── feature_importance.csv
├── dashboard/
│ └── app.py # Streamlit app: world map / country lookup / predictor
├── tests/
│ └── test_pipeline.py # 10 tests incl. anti-leakage & causal-lag checks
├── main.py # runs the whole pipeline end-to-end
└── requirements.txt
pip install -r requirements.txt
# Run the full pipeline (preprocessing -> EDA -> training -> evaluation)
python main.py
# Launch the interactive dashboard
streamlit run dashboard/app.py
# Run tests
pytest tests/ -v- Leakage guard: the five sub-scores plus
economic_stress_scoreandstress_categoryare permanently excluded from features.tests/test_pipeline.pyenforces this with an explicit assertion. - Imputation strategy: raw indicators (inflation is 37% missing, unemployment 54% missing in the raw data) are imputed country-median → region-year-median → global-median, in that priority order, rather than dropped — dropping would have discarded most of the panel.
- Temporal train/test split, not random: train ≤2020, test 2021–2025. This is the honest way to evaluate a forecasting-style model; a random split would leak future information into training and overstate accuracy.
- Lag & rolling features:
stress_lag1,stress_3yr_avg,gdp_growth_volatility_5yrare built per-country using only past years (verified by an explicit causal-ordering test) — this is what makes the temporal split meaningful instead of just splitting an otherwise static table. - Log-scaling population, GDP per capita, and cereal production, which span multiple orders of magnitude across 215 countries.
Three tabs:
- World Map & Trends — choropleth map by year, regional averages, global stress trend since 1960, top-15 most-stressed countries table.
- Country Lookup — full time series and macro indicator trends for any single country.
- Stress Predictor — enter hypothetical macro conditions (region, income group, GDP growth, inflation, unemployment, food security) and get a predicted stress score + category, with the model's honest held-out accuracy shown alongside it.
- MAPE is inflated by countries with near-zero stress scores (division by a small number); MAE (7.9 points on a 0–100 scale) is the more reliable headline metric here.
- The dominant feature (
stress_3yr_avg) means the model is largely learning "stress persists" rather than discovering novel macro drivers — worth knowing before treating the feature-importance ranking as a policy insight rather than a forecasting aid. - Pre-1990 data is sparser and more heavily imputed than recent decades; trend charts for early years should be read with that in mind.
- This models a constructed composite index, not a validated external benchmark (e.g., IMF or World Bank fragility indices) — useful for understanding the data's internal structure, not as a substitute for those institutions' published assessments.
Shubham Panchal
Data Analytics | Data Science | AI and Machine Learning | Business Intelligence LinkedIn: https://linkedin.com/in/shubham-panchal-a100282a8