Skip to content

Methods

How the estimates are made, and checked

Each topic starts with a plain summary; open “The details” for the full technical account and the code file behind each step (the repository is available on request from csuhrudh@gmail.com). Every number quoted here comes from the site's data files.

Research prototype: not for health or regulatory decisions.

Overview

The pipeline in brief

  1. 01

    Line everything up

    Put every measurement onto one hourly grid of 10-km over the Bay (times in UTC, the standard clock for science data).

  2. 02

    Build the answers

    Make a for each pollutant: what ground-level NO₂ and O₃ were, cell by cell and hour by hour, wherever we can know it.

  3. 03

    Train

    Teach an to predict the next hour's NO₂ and O₃ from the previous 24 hours of inputs, plus four simpler models on the same data.

  4. 04

    Test three ways

    Against its own target, against , and at a cell with no measurements of its own, always beside a simple baseline.

  5. 05

    Test again, later

    Run the same next-hour models on April–October 2026, months after training and testing ended, and check them against EPA monitors again.

Space and time

Study area and grid

The Bay is split into 348 squares about 10 km across, and every measurement is lined up hour by hour. The model learns from 2018–2024, is tuned on 2025, and is tested on January–March 2026, which it never sees while learning.

The details

The grid has 348 cells, each 0.1° (about 10 km) on a side, covering 36.75–39.57°N and 76.76–75.69°W: the Bay from Hampton Roads to the Susquehanna. Everything is hourly in UTC (Coordinated Universal Time, 4–5 hours ahead of Eastern time). Cell IDs are R{row}_C{column}, counted from the south-west corner.

Data splits are by calendar date: train 2018-01-01 to 2024-12-31, validate 2025, test 2026-01-01 to 2026-03-31. Nothing from the test period is used for training or for choosing settings.

Ground truth

What the model learns to predict

To learn, the model needs a “right answer” for every hour: its . Where an EPA monitor exists, that answer is the monitor's reading. Elsewhere it is built from (for NO₂) or the satellite (for ozone), converted to ground-level values.

The details

NO₂ (µg/m³, surface). Pandora spectrometers (in 5 grid cells) measure the total NO₂ column overhead. We convert that to a surface concentration by spreading it over the , using an boundary-layer-height climatology (its typical depth) by month, hour of day and location instead of a fixed 1 km. Where an EPA surface NO₂ monitor lies within 0.30° of a cell centre (14 monitors, 11 cells), its real reading replaces the converted value.

O₃ (µg/m³, surface). Built from NASA TEMPO's tropospheric ozone profile product, converted to a surface value, with real EPA surface O₃ readings replacing it within 0.30° of a monitor (33 monitors, 25 cells). Earlier versions used Pandora's total ozone column in Dobson units (a measure of all the ozone overhead), which is dominated by the high-altitude ozone layer and can't be compared with surface monitors.

The target for each hour is the next hour's value: a 24-hour window ending at hour t predicts hour t+1.

Features

Inputs

Each hour, the model looks at 33 pieces of information for every cell: the latest pollution readings, the reading at the nearest measured cell, haze, the weather's typical mixing height, the time of day and year, and what is on the ground (roads, people, water, forest).

The details

The map's LSTM uses 33 inputs per hour:

  • Current NO₂ and O₃ at the cell (the target series itself, where measured).
  • Neighbouring NO₂: the same hour's NO₂ at the nearest other cell with ground truth, and the distance to it. This gives unmonitored cells a live signal.
  • Aerosol: AERONET optical depth (how hazy the air is), Ångström exponent (a hint at particle size) and water vapour (6 sun photometers), and VIIRS satellite aerosol optical depth.
  • Weather and calendar: ERA5 boundary-layer-height climatology, hour of day, day of year, day of week and a weekend flag (as sine/cosine pairs where cyclic).
  • Place: location encodings, elevation, road density, land-cover fractions and population density.

A missing input gets that feature's training-set median. Inputs and targets are rescaled with a RobustScaler (which puts every input on a similar scale without letting rare extreme hours dominate), fitted on the training years only. TEMPO NO₂, TEMPO total-column O₃ and TROPOMI NO₂ were tested and dropped: they lowered test skill.

All 33 input names

AERONET_AOD, AERONET_Angstrom, AERONET_Precip_Water, ERA5_BLH_clim, PANDORA_NO2, PANDORA_O3, SPATIAL_LAG_DIST_KM, SPATIAL_LAG_NO2, VIIRS_AOD550, dayofyear_cos, dayofyear_sin, dow_cos, dow_sin, elevation_m, frac_agriculture, frac_developed, frac_forest, frac_other, frac_water, frac_wetland, hour_cos, hour_sin, is_weekend, loc_lat_cos, loc_lat_norm, loc_lat_sin, loc_lon_cos, loc_lon_norm, loc_lon_sin, population_density_mean, road_km_motorway_trunk_primary, road_km_secondary, road_km_total

Models

The models

The main model is an , a neural network that reads the past 24 hours as a sequence. Four learn from the same data for comparison, and two no-learning guesses, and , set the bar everything has to clear.

The details

LSTM (run o3_surface_v1): two stacked LSTM layers (224 then 112 units), each followed by layer normalisation and 14% dropout (standard techniques that keep training stable and stop the network memorising), a shared 128-unit dense layer, and separate one-number outputs for NO₂ and O₃. Input is the last 24 hours of all inputs. Trained with Adam (learning rate 4×10⁻⁴, gradient clipping) on a Huber loss (an error measure that is less thrown off by a few extreme hours).

Baselines: linear regression, random forest, gradient boosting and XGBoost, trained on the same features, targets and splits, using only the last hour of each 24-hour window. Linear, random forest and XGBoost are on the map; all four are on the validation page.

Persistence (“next hour = this hour”) and climatology (a monitor's average for that month and hour) are not trained models, but they are the bars every model has to clear.

Code: lstm_model.py, baselines.py, run_lstm_pipeline.py, o3_target_fix/run_o3_surface_pipeline.py.

Evaluation

How it is scored

Every headline score is an for next-hour estimates, checked against on months no model trained on and shown beside persistence. A separate script recomputes every number from the saved predictions.

The details

Fit to target R² against the model's own target on the January–March 2026 test hours. It shows the model learned the target; it says nothing about whether the target is right.

EPA check Each EPA monitor is matched to the nearest cell centre within 0.20°; monitors sharing a cell are averaged per hour; readings in parts per billion or million are converted to µg/m³ at 25 °C and sea-level pressure. Each prediction is compared with the EPA reading for the hour it predicts (t+1), and persistence is scored on exactly the same cell-hours. R² is computed per cell, then averaged. An independent script recomputes every number from the saved prediction files.

Unmonitored check The same EPA check at a cell that has a monitor but no ground truth of its own in the target: the closest thing to a test away from instruments.

Calibration test. To see whether a statistical correction could rescue the original model, its output is rescaled per site (EPA reading = slope × prediction + intercept, fit on 2018–2025) and scored on the same January–March 2026 hours. It removes the bias but adds almost no skill, which is why the later models train on real EPA readings instead. Code: aqs_integration/calibrate_and_validate.py.

EPA check Second check, on new data. The same scoring on April 1 to October 5, 2026, beside persistence and climatology on the same hours. Certified EPA data lag by months, so most of these readings are AirNow's preliminary data for the same monitors (in late March they match the certified values exactly for O₃ and at r = 0.997 for NO₂). Sites with under a month of readings are left out of the averages and listed.

Code: o3_target_fix/aqs_validation_all_models.py, o3_target_fix/verify_aqs_validation.py, o3_target_fix/nowcast_check_apr_oct.py.

April–October

How the April–October estimates are made

For April–October 2026, the same models ran hour by hour on fresh monitor readings, exactly as in testing, to see whether the skill holds up on months that came after everything else.

The details

For every cell and every hour from April 1 to October 5, 2026, the models read the previous 24 hours of inputs and estimate the next hour, exactly as in training and testing. The inputs are built by the same code that built the training data, with newer readings added:

  • EPA monitor readings (certified where available, otherwise AirNow preliminary) feed the NO₂ and O₃ inputs at cells with a monitor nearby, through the same blending step as in training;
  • the neighbouring-cell NO₂ input comes from the nearest such cell's reading for the same hour;
  • calendar features and the boundary-layer climatology come from the timestamp;
  • AERONET and VIIRS, which have no data in the cache after March, get their training median, as in training whenever they were missing.

The model never sees the reading for the hour it estimates. The map shows each day's mean of these hourly estimates. See how they score.

Code: o3_target_fix/nowcast_check_apr_oct.py (--web).

Display

How the map is drawn

The map colours each cell by its daily average and blends smoothly between cells for display. Downloads hold the exact cell values.

The details

The next-hour map shows each cell's daily mean on one fixed colour scale for every model and day (NO₂ 0–40, O₃ 0–120 µg/m³, viridis). For display it is smoothed between cell centres with inverse-distance weighting (nearby cells count more than distant ones); the downloadable values are the cell values. The AQI view converts EPA breakpoints to µg/m³ at 25 °C; the AQI uses 1-hour (NO₂) and 8-hour (O₃) averages, so colours on daily means are indicative only. The backtest map shows each cell's error against the training target.

Limits

Known limitations

  • No skill shown away from instruments. Every scored cell is on developed land near a monitor, and the one unmonitored check showed no skill yet.
  • The margin over persistence is modest. At monitors the model sees the current reading, so persistence is strong. In January–March the LSTM was just below it for NO₂ and above it for O₃; on April–October it is above it for both.
  • No long-range forecast. The estimates are next-hour only. An earlier version rolled the model forward for months with no new readings; past the first day it fell below a seasonal-average baseline, so it is no longer published.
  • Targets are partly modelled. Away from EPA monitors, the NO₂ target relies on a column-to-surface conversion and the O₃ target on a satellite product.
  • Not an official product. For real readings use AirNow.

Words

Glossary

Every term the site underlines, in one place.
NO₂ (nitrogen dioxide)
A gas that comes mostly from vehicle exhaust, power plants and ships. It irritates the lungs, helps make ozone, and some of it ends up in the Bay as nitrate.
O₃ (ground-level ozone)
Not released directly: it forms when sunlight acts on NO₂ and other pollutants, so it peaks on hot, sunny afternoons. (This is the ozone we breathe, not the protective layer high up.)
µg/m³
Micrograms per cubic metre: millionths of a gram of pollutant in each cubic metre of air. Both pollutants are shown in this unit.
R² (skill score)
How well the estimates follow the real ups and downs. 1 is perfect; 0 means no better than always guessing that site's average; below 0 is worse than that guess.
Persistence
The simplest possible forecast: assume the next hour equals this hour. One hour ahead it is hard to beat, so it is the bar every model here has to clear.
Climatology
Another simple baseline: the usual value at that place for that month and hour of day, averaged over 2018–2025.
Next-hour estimate
Each estimate looks at the previous 24 hours of data and predicts the following hour, never further ahead. It never sees the reading it is predicting.
Grid cell
One square of the map, 0.1° (about 10 km) on a side. The Bay is covered by 348 of them, each with its own hourly estimate.
EPA monitor (AQS)
Official ground-level air stations, run by state agencies under EPA rules, that sample the air people breathe. Their quality-checked readings are archived in EPA's Air Quality System (AQS).
AirNow
EPA's real-time feed of the same monitors' readings, published before they are quality-checked. Used here for April–October 2026.
Pandora
A ground instrument that looks up at the sun and measures how much NO₂ and ozone are in the whole column of air overhead. Five of them feed this model.
AERONET
NASA's network of sun photometers, which measure haze (tiny particles) in the air. Six are used as model inputs.
TEMPO
A NASA satellite that watches North American air pollution every daylight hour. Here only its lower-atmosphere ozone is used, to help build the ozone target.
VIIRS
A satellite instrument that measures haze from space; one of the model's inputs.
ERA5
A global weather record from Europe's ECMWF. Used here for the typical height of the boundary layer at each place, month and hour.
Boundary layer
The bottom layer of air, from a few hundred metres to about 2 km thick, where pollution mixes. It is shallow at night and deep on sunny afternoons, which dilutes what we breathe.
LSTM
Long short-term memory network: a type of neural network built to learn patterns in sequences, here the last 24 hours of readings at each grid cell.
Simpler models
Linear regression, random forest, gradient boosting and XGBoost: standard machine-learning methods trained on the same data, to check whether the neural network is worth its complexity.
Training target
The 'right answer' the model learns to reproduce. Here it is real monitor readings where they exist, and converted Pandora or satellite measurements elsewhere.
Held-out period
Hours kept aside and never used in training, so a score on them shows how the model does on data it has not seen.
Backtest
Running the model over a past period (January–March 2026 here) and comparing its estimates with what it was trained to predict.
Calibration
Rescaling a model's output (multiplying and adding a constant, per site) so its average level matches the monitors.
RMSE
Root-mean-square error: the typical size of a miss, in µg/m³. Smaller is better.
Bias
The average over- or under-estimate, in µg/m³. Zero means no systematic lean either way.