CO2 Emissions Predictor — Project Story

Inspiration

Climate change conversations are usually full of global numbers — gigatons here, percentage increases there — but it's hard to connect that to any single country's trajectory. We wanted to answer a more grounded question: can a country's own economic and demographic fingerprint (GDP, energy use, urbanization, land use) predict how much CO₂ it emits per person? The World Bank's Climate Change Data dataset, spanning ~96 countries from 1990–2011, gave us exactly the kind of cross-country, multi-variable data needed to explore that question with machine learning — and turning a static analysis into something people could actually play with felt like the natural next step.

What it does

The project is a full pipeline, wrapped in a single self-contained Jupyter notebook, that:

  • Cleans and prepares the raw World Bank climate dataset
  • Explores relationships between CO₂ emissions per capita and features like cereal yield, energy use per capita, urban population share, and protected land area
  • Trains a Random Forest Regressor, using recursive feature elimination (RFECV) to automatically pick the most predictive features, and RandomizedSearchCV for hyperparameter tuning
  • Evaluates the model with cross-validated \( R^2 \), MSE, and RMSE
  • Serves the trained model through an interactive Gradio app with three tabs:
    • Predict — move sliders (or auto-fill them from any real country/year) to get a live CO₂-per-capita prediction
    • Explore data — visualize a country's emissions trend over two decades
    • Model performance — see feature importances and evaluation metrics without digging through code

The model reaches a test-set coefficient of determination of roughly:

$$ R^2 \approx 0.96 $$

which means the five to eight selected socioeconomic features explain the overwhelming majority of the variance in per-capita CO₂ emissions across countries and years.

How we built it

We started from an existing three-stage analysis (data preparation → visualization → predictive modeling) and consolidated it into one linear notebook so the whole story — from raw .xls file to working prediction UI — reads top to bottom without needing to jump between files.

  • Data layer: pandas / numpy for cleaning, an outlier pass to remove the UAE (ARE), whose extreme values distorted every bivariate relationship
  • Modeling: scikit-learn's RandomForestRegressor, RFECV for feature selection, and RandomizedSearchCV over n_estimators, max_depth, max_features, min_samples_split, and min_samples_leaf
  • Reproducibility: a fixed random_state and explicit numpy.random.seed() calls before every stochastic step, so the "best" model doesn't shift between reruns
  • Interface: gradio.Blocks for a tabbed UI — sliders are generated dynamically from whichever features RFECV selects, and a "load real data" button lets you seed the sliders from an actual country/year instead of guessing plausible values
import gradio as gr
from sklearn.ensemble import RandomForestRegressor

# sliders are built dynamically from the RFECV-selected features
with gr.Blocks() as demo:
    with gr.Tab("Predict"):
        sliders = [gr.Slider(*feature_ranges[f], label=f) for f in chosen_features]
        gr.Button("Predict").click(predict_co2, sliders, output)

Challenges we ran into

  • Small-data generalization: with only ~1,700 rows split 30/70 in favor of the test set, avoiding overfitting meant leaning hard on cross-validation at every stage (feature selection, tuning, and evaluation) rather than trusting a single train/test split.
  • Outlier sensitivity: a handful of extreme values (looking at you, UAE) skewed feature scales enough to distort splits early in development — we had to explicitly detect and exclude them rather than let the model quietly overfit around them.
  • Search-space vs. runtime trade-off: the original hyperparameter grid was wide; making it interactive meant trimming RandomizedSearchCV's search space enough to keep the notebook runnable in a few minutes without materially hurting the tuned model's accuracy.
  • Dynamic UI from a non-deterministic feature list: since RFECV can select a different feature subset if the underlying data or library versions shift slightly, we built the Gradio sliders programmatically from whatever chosen_features came out of feature selection, instead of hardcoding them.

Accomplishments that we're proud of

  • A model that's not just accurate on paper (\( R^2 \approx 0.96 \) on held-out data) but genuinely explorable — anyone can load a real country, see its record, tweak a variable, and watch the prediction respond
  • One notebook, zero setup friction: clone, run all cells, and the whole pipeline — from raw World Bank data to a live demo — just works

What we learned

  • Cross-validated feature selection and hyperparameter tuning genuinely matter more than model choice on small, high-dimensional socioeconomic datasets
  • A good interactive demo does more to build intuition about a model than another chart ever could — watching a prediction move in response to a single slider makes feature importance tangible
  • Designing a UI around whatever the pipeline decides (rather than hardcoded assumptions) makes the whole notebook far more robust to re-runs and data updates

What's next

  • Extend the dataset beyond 2011 with more recent World Bank releases
  • Add confidence/prediction intervals instead of a single point estimate
  • Let users compare two countries side-by-side, or simulate "what if this country adopted country X's energy profile"

Built With

  • artificial
  • change
  • climate
  • co2
  • colab
  • data
  • emissions
  • environmental
  • forest
  • google
  • jupyter
  • learning
  • machine
  • matplotlib
  • notebook
  • numpy
  • pandas
  • python
  • random
  • science
  • scikit-learn
  • seaborn
  • sustainability
  • visualization
Share this project:

Updates