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, andRandomizedSearchCVfor 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/numpyfor cleaning, an outlier pass to remove the UAE (ARE), whose extreme values distorted every bivariate relationship - Modeling:
scikit-learn'sRandomForestRegressor,RFECVfor feature selection, andRandomizedSearchCVovern_estimators,max_depth,max_features,min_samples_split, andmin_samples_leaf - Reproducibility: a fixed
random_stateand explicitnumpy.random.seed()calls before every stochastic step, so the "best" model doesn't shift between reruns - Interface:
gradio.Blocksfor a tabbed UI — sliders are generated dynamically from whichever featuresRFECVselects, 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
RFECVcan select a different feature subset if the underlying data or library versions shift slightly, we built the Gradio sliders programmatically from whateverchosen_featurescame 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
- jupyter
- learning
- machine
- matplotlib
- notebook
- numpy
- pandas
- python
- random
- science
- scikit-learn
- seaborn
- sustainability
- visualization


Log in or sign up for Devpost to join the conversation.