AI in Climate Change & Environmental Science
I spent about three months trying to build a temperature anomaly prediction model using NOAA's publicly available Global Surface Summary of Day (GSOD) dataset. I want to be upfront about how that went — the genuine wins, the embarrassing mistakes I only caught weeks later, and the moments where I seriously questioned whether ML was even the right approach for a problem this hard.
1. The Day I Lost to a CSV File
I knew climate data was going to be messy. I didn't know it was going to swallow an entire Tuesday.
The NOAA GSOD dataset covers thousands of weather stations globally, going back to the 1940s in some cases. On paper, that's extraordinary — decades of temperature, dew point, wind speed, and precipitation readings all in one place. In practice, it meant I was dealing with stations that went offline for years at a stretch, sentinel values like 9999.9 standing in for missing readings (which I did not notice at first), units that silently switch between Fahrenheit and Celsius depending on the station's country of origin, and duplicate rows that appear whenever a station's metadata was corrected mid-year. There's also a fun quirk where the column names change slightly between files from different decades.
I started at 9am planning to "quickly clean and load" before noon. By 4pm I had deleted and recreated my working DataFrame four times. At one point I accidentally dropped the station ID column and only noticed an hour later when my groupby operations started producing obviously wrong aggregates — mean anomalies that spanned entire continents because everything was being lumped together without a station key.
Here's the pipeline I eventually settled on. The comments are left exactly as I wrote them during the session:
NOAA GSOD Climate Data — Load & Clean (Python)
import pandas as pd
import numpy as np
import glob
# NOAA gives you one CSV per station per year. Glob them all.
# Column names change slightly between decades — fun discovery at hour three.
files = glob.glob("noaa_gsod/**/*.csv", recursive=True)
chunks = []
for f in files:
try:
df = pd.read_csv(f, low_memory=False)
chunks.append(df)
except Exception as e:
# A handful of files are genuinely corrupt. Skip and log.
print(f"Skipping {f}: {e}")
raw = pd.concat(chunks, ignore_index=True)
print(f"Loaded {len(raw):,} rows before cleaning")
# 9999.9 is NOAA's sentinel for missing temperature.
# This cost me an hour — my first model trained fine but then
# predicted approximately 9000 F for certain winter months. Classic.
SENTINEL = 9999.9
for col in ["TEMP", "DEWP", "WDSP", "PRCP"]:
raw[col] = raw[col].replace(SENTINEL, np.nan)
# Convert Fahrenheit to Celsius for imperial-system stations.
# A TEMP_FLAG column exists in newer files but NOT in pre-1975 data,
# so I had to infer it from country codes instead. Not ideal.
imperial = ["US", "BS", "BZ", "KY", "LR", "MM", "PW"]
mask = raw["COUNTRY_ABBR"].isin(imperial)
raw.loc[mask, "TEMP"] = (raw.loc[mask, "TEMP"] - 32) * 5 / 9
# Date is stored as YYYYMMDD integer. Of course it is.
raw["DATE"] = pd.to_datetime(
raw["YEARMODA"].astype(str), format="%Y%m%d", errors="coerce"
)
# Drop rows with no date, no temperature, or no station ID.
# Losing about 4.2% of rows here — acceptable.
cleaned = raw.dropna(subset=["DATE", "TEMP", "STATION"]).copy()
# Aggregate to monthly mean per station.
# This becomes the target variable for anomaly detection.
cleaned["YEAR_MONTH"] = cleaned["DATE"].dt.to_period("M")
monthly = (
cleaned
.groupby(["STATION", "YEAR_MONTH"])["TEMP"]
.mean()
.reset_index()
.rename(columns={"TEMP": "MONTHLY_MEAN_C"})
)
# WMO standard baseline is 1981-2010. Compute long-run mean per station.
baseline = (
monthly[monthly["YEAR_MONTH"].dt.year.between(1981, 2010)]
.groupby("STATION")["MONTHLY_MEAN_C"]
.mean()
.rename("BASELINE_C")
)
monthly = monthly.join(baseline, on="STATION")
monthly["ANOMALY_C"] = monthly["MONTHLY_MEAN_C"] - monthly["BASELINE_C"]
print(f"Final dataset: {len(monthly):,} station-month records")
print(monthly[["STATION", "YEAR_MONTH", "ANOMALY_C"]].head(8))
When this finally ran cleanly, I had 2.1 million station-month anomaly records spanning 1950 to 2023. I made coffee, opened a new notebook, and proceeded to make a much worse mistake.
2. The Data Leakage I Didn't Catch for Two Weeks
My model looked outstanding. An LSTM predicting monthly temperature anomalies at held-out stations, with a test MAE of 0.18°C. I was genuinely excited — that's competitive with numbers I'd seen in published papers. I wrote it up in a draft post and nearly hit publish.
Then I re-read my own feature engineering code at midnight and felt my stomach drop.
I had computed a rolling 12-month mean anomaly as a predictor feature — completely reasonable in isolation. But I'd computed it across the entire dataset before splitting into train and test folds. That meant my "lagged" features for, say, January 2010, quietly included data from February, March, and April 2010. The model had not learned to forecast anomalies. It had learned to interpolate them from future months it was never supposed to see. The MAE of 0.18°C was not a result. It was an artefact.
Here's what the numbers looked like when I reran it correctly — with the train/test split applied strictly before any feature construction:
Terminal Output — Before vs. After Fixing the Leakage
# --- BEFORE fix (rolling features computed on full dataset) ---
Epoch 40/40 | Train Loss: 0.0041 | Val Loss: 0.0039
Test MAE: 0.18 C <-- looked great. was completely fake.
# --- AFTER fix (features computed strictly within each time split) ---
Epoch 40/40 | Train Loss: 0.0312 | Val Loss: 0.0487
Test MAE: 0.74 C <-- honest. also sobering.
# For reference: WMO operational monthly anomaly forecasts typically
# achieve 0.4-0.9 C MAE at regional scale.
# So 0.74 C is... mid-pack. Real, but not a breakthrough.
Two weeks of excitement, gone in about fifteen minutes. The corrected 0.74°C MAE put me roughly in the middle of operational climatological forecast systems — not a disaster, but not the result I'd imagined either. I fixed the pipeline properly by writing a TimeSeriesSplit-aware feature builder that only looks backward from the current fold's cutoff date. I also added a unit test that asserts no feature column for timestamp t contains any value derived from timestamps t+1 or later. If I'd had that test from the start, I'd have caught this on day one.
3. Why Climate Is Genuinely Harder Than Standard ML Benchmarks
After that experience I started reading more carefully about what the serious research groups — DeepMind, ECMWF, the GraphCast and Pangu-Weather teams — are actually solving, and why it's hard in ways that don't show up in typical benchmark tables.
Standard ML benchmarks reward being right on average across an i.i.d. test set. Climate is almost the opposite of that structure. The events that matter most — the ones that drive policy decisions, cause mass displacement, reshape coastlines — are the rarest ones. A model that nails the mean summer temperature in the American Midwest while completely whiffing on a once-in-fifty-year heat dome is not a useful climate model. And the training signal for those extremes is, by definition, thin. You might have two or three historical examples across an entire century of records. Standard cross-validation doesn't even apply meaningfully in that regime.
Physics-informed approaches exist precisely because of this. When you constrain a neural network's loss function with known physical laws — conservation of energy, the Navier-Stokes equations for atmospheric fluid flow — you're baking in structure the data alone cannot provide. The model can't predict a heatwave that violates thermodynamics, even if gradient descent would otherwise happily go there given a weird local minimum.
Physics-Informed Loss Constraint — What It Actually Looks Like
import torch
def physics_informed_loss(predictions, targets, inputs):
# Standard regression loss on observed values.
data_loss = torch.mean((predictions - targets) ** 2)
# Spatial gradient of predictions w.r.t. inputs.
# inputs MUST have requires_grad=True or you get a silent wrong result.
# I forgot this the first time and got a cryptic RuntimeError. Fun.
pred_grad = torch.autograd.grad(
outputs=predictions.sum(),
inputs=inputs,
create_graph=True # must be True so the physics loss can backprop
)[0]
# Enforce approximate conservation of mass:
# divergence of the horizontal wind field should be near zero
# for large-scale, slow-evolving synoptic flow.
divergence = pred_grad[:, 0] + pred_grad[:, 1]
physics_loss = torch.mean(divergence ** 2)
# lambda=0.1 is a hyperparameter. Too high: model ignores the data.
# Too low: model ignores the physics. No clean way to set this —
# it's an open research problem and I tuned it by hand on a validation set.
return data_loss + 0.1 * physics_loss
I tested a simplified version of this on my NOAA anomaly dataset — not a full PINN, just using the spatial smoothness constraint (nearby stations shouldn't have wildly diverging anomalies). It reduced my worst per-station errors noticeably, even though the aggregate MAE barely budged. That gap between aggregate and tail performance is something worth sitting with. It's where real-world usefulness lives.
4. Where the Field Is Actually Headed
The most impressive recent work — GraphCast, Pangu-Weather, FourCastNet — treats the atmosphere as a graph or 3D spatial structure and runs at 0.25-degree global resolution. GraphCast produces a 10-day global forecast in under a minute versus hours of supercomputer time for traditional Numerical Weather Prediction. These are real, operationally significant improvements, not just benchmark wins.
But I noticed something reading their validation papers: the benchmarks cluster heavily around medium-range (3–10 day) forecasting, where training data is dense and the physics is relatively tractable. Monthly and seasonal anomaly prediction — the time horizon that actually drives climate policy — is still wide open. Skill scores drop fast past two weeks. My model's 0.74°C MAE on monthly anomalies isn't embarrassing in that context. It's just honest about the difficulty.
Satellite-based carbon monitoring is further along. Computer vision models trained on multi-spectral imagery are now good enough to detect methane plumes from individual pipeline leaks, segment forest canopy loss at hectare resolution, and estimate industrial emissions from the thermal signature of cooling towers — all without waiting for voluntary national reporting. U-Net and its derivatives have become the standard workhorse for geospatial segmentation here, and the availability of high-quality free satellite data (Sentinel-2, Landsat 8/9) has largely solved the data access bottleneck for researchers.
Materials discovery for carbon capture is further from my hands-on experience, but the structure is similar: Graph Neural Networks predicting the thermodynamic stability and CO₂ absorption capacity of hypothetical metal-organic frameworks — porous crystalline structures designed to scrub carbon dioxide from the air — without requiring lab synthesis for each candidate. The simulation-to-reality gap is real, but it's narrowing.
| Approach | Key Architecture | Actual Strength | Honest Limitation |
|---|---|---|---|
| Medium-Range Forecasting | Graph Neural Networks / Transformers | 3–10 day global forecasts at near-operational quality | Skill collapses sharply past ~14 days; rare extremes still underfit |
| Grid & Energy Optimization | Spatiotemporal LSTMs / GNNs | Hour-ahead wind/solar yield forecasts with real dispatch savings | Highly sensitive to local topography; requires per-site retraining |
| Satellite Carbon Monitoring | U-Net / CNN segmentation | Detects methane leaks and deforestation without self-reporting | Persistent cloud cover creates systematic blind spots in the tropics |
5. What I Actually Think After All of This
ML is genuinely useful for climate science. But the hype tends to run ahead of the reality in ways that matter. The results that make press releases are the ones hitting impressive numbers on well-posed, data-rich benchmarks. The actual hard problems — predicting regional precipitation anomalies six months out, detecting abrupt tipping-point transitions before they occur, attributing individual extreme events to anthropogenic forcing — are still brutally difficult. No architecture has cracked them.
That said, I don't think that's a reason for pessimism. The tooling has improved dramatically. The data is increasingly open. And the community has gotten much more honest about failure modes — data leakage, distribution shift, extrapolating into physical regimes never seen in training data. My NOAA project taught me more about this problem than reading a dozen survey papers did, mostly by being wrong in specific, instructive ways. The data leakage bug alone completely changed how I think about time-series feature construction.
If you're thinking about getting into this area, I'd genuinely recommend starting with GSOD or the GHCN monthly dataset. They're free, they're messy enough to be educational, and the apparently simple question — "will next month's temperature be above or below the 1981–2010 baseline for this station?" — will immediately surface just how much is packed inside it. More than I expected, anyway.
Pratyush
Pratyush is an AI researcher learning machine learning, computer vision, and deep learning architectures. He focuses on practical, hands-on ML implementation and building accessible educational resources.
Continue Through the Maze
Ethical AI Frameworks
Algorithmic bias, transparency, and safe AI integration.
AI ApplicationsAI in Healthcare
Cancer detection, drug discovery, and AlphaFold breakthroughs.
Reinforcement LearningQ-Learning & Policy Loops
Agent action cycles, feedback loops, and policy networks.
Machine LearningGraph Neural Networks (GNNs)
Message-passing and graph convolution for irregular structures.