How to Get Weather Forecast Data in Python and Check Its Accuracy

On this page

Problem statement

Getting a weather forecast into Python is a single request: the Open-Meteo forecast API returns hourly temperature, rain, wind and dozens of other variables for the next 16 days at any coordinates, with no key. The harder question is how much to trust it. A forecast for tomorrow and one for next week are not equally good, and the only way to know how good is to compare past forecasts with what actually happened. Open-Meteo keeps an archive of previous forecast runs that makes this possible.

This page requests the current forecast for Washington, D.C. and looks at what the response contains. It then uses the previous-runs archive to fetch the temperature forecasts issued one, three, five and seven days ahead for every hour of 2024, and scores each lead time against the observations at Reagan National Airport.

Quick answer

Request hourly and daily forecast variables in local time:

r = requests.get("https://api.open-meteo.com/v1/forecast", params={
    "latitude": LAT, "longitude": LON, "timezone": TZ, "forecast_days": 7,
    "hourly": "temperature_2m,precipitation_probability,precipitation,wind_speed_10m",
    "daily": "temperature_2m_max,temperature_2m_min,precipitation_sum"}, timeout=60)
r.raise_for_status(); js = r.json()
F = pd.DataFrame(js["hourly"]).assign(time=lambda d: pd.to_datetime(d.time)).set_index("time")
print(f"{len(F)} hourly steps and {len(js['daily']['time'])} days; variables {list(F.columns)}; units {js['hourly_units']['temperature_2m']}, {js['hourly_units']['wind_speed_10m']}")
168 hourly steps and 7 days; variables ['temperature_2m', 'precipitation_probability', 'precipitation', 'wind_speed_10m']; units °C, km/h

The forecast has 168 hourly steps and 7 daily values, with temperature in °C and wind in km/h. The values themselves change with every model run, which is why they are not shown.

Line chart of hourly temperature at Washington National from 8 to 21 July 2024, observed in black with forecasts issued one day and seven days ahead, the seven-day forecast missing several peaks and troughs. Line chart of hourly temperature at Washington National from 8 to 21 July 2024, observed in black with forecasts issued one day and seven days ahead, the seven-day forecast missing several peaks and troughs.
Archived forecasts against observations.

Past forecasts, scores by lead time, where it went wrong

1. Previous forecasts for a past period

The previous-runs API returns, for each hour, the value forecast one to seven days earlier, beside the latest:

P = previous_runs("2024-01-01", "2024-12-31")
print(f"{len(P):,} hours; columns {list(P.columns)}")
print(P.loc["2024-07-15 15:00"].round(1).to_dict())
8,784 hours; columns ['temperature_2m', 'temperature_2m_previous_day1', 'temperature_2m_previous_day3', 'temperature_2m_previous_day5', 'temperature_2m_previous_day7']
{'temperature_2m': 37.3, 'temperature_2m_previous_day1': 35.0, 'temperature_2m_previous_day3': 38.2, 'temperature_2m_previous_day5': 38.6, 'temperature_2m_previous_day7': 37.5}

The archive returns all 8,784 hours of 2024, each with the latest value and the forecasts issued one, three, five and seven days earlier. At 15:00 on 15 July, the latest value was 37.3 °C, and the forecasts from 1 to 7 days earlier said 35.0, 38.2, 38.6 and 37.5 °C.

2. Score each lead time against the station

Join the forecasts to the station's hourly observations and compute the mean absolute error and the bias:

obs = ms.hourly("72405", datetime(2024, 1, 1, 5), datetime(2025, 1, 1, 4)).fetch().temp
obs.index = obs.index.tz_localize("UTC").tz_convert(TZ).tz_localize(None)
J = P.join(obs.rename("observed"), how="inner").dropna()
score = pd.DataFrame({lead: {"MAE °C": (J[col] - J.observed).abs().mean(), "bias °C": (J[col] - J.observed).mean()}
                      for lead, col in LEADS.items()}).T
print(f"hours compared: {len(J):,}")
print(score.round(2).to_string())
hours compared: 8,200
        MAE °C  bias °C
1 day     1.84    -1.02
3 days    1.80    -0.69
5 days    2.28    -0.75
7 days    2.81    -0.90

Over 8,200 hours with an observation, the one-day forecast was off by 1.84 °C on average and the seven-day forecast by 2.81 °C. Oddly, the three-day forecast (1.80 °C) did slightly better than the one-day one. Every lead time runs 0.7 to 1.0 °C colder than the airport. A bias that does not grow with lead time points to the difference between a model grid cell and this one station, not to forecast error.

Mean absolute error of hourly temperature forecasts by lead time at Washington National in 2024. forecast error by lead time1 day ahead1.84 °C3 days1.80 °C5 days2.28 °C7 days2.81 °C01 °C2 °C3 °C8,200 hours; every lead 0.7-1.0 °C colder than the station spatialworkflow.io
Error by lead time.

3. Where the forecast misses most

Look at the error of the one-day forecast by month and by hour of day:

e = (J["temperature_2m_previous_day1"] - J.observed)
print("MAE by month:", e.abs().groupby(J.index.month).mean().round(2).to_dict())
by_hour = e.abs().groupby(J.index.hour).mean()
print(f"MAE by hour: lowest {by_hour.min():.2f} °C at {by_hour.idxmin()}:00, highest {by_hour.max():.2f} °C at {by_hour.idxmax()}:00")
MAE by month: {1: 1.93, 2: 1.94, 3: 2.02, 4: 1.89, 5: 1.55, 6: 1.61, 7: 1.63, 8: 1.58, 9: 1.47, 10: 2.02, 11: 2.49, 12: 2.06}
MAE by hour: lowest 1.63 °C at 17:00, highest 2.03 °C at 6:00

The one-day forecast was best in September (1.47 °C) and worst in November (2.49 °C). By hour, it was best at 17:00 (1.63 °C) and worst at 06:00 (2.03 °C), around dawn.

How errors grow with lead time

The data used on this page

The Open-Meteo forecast API and previous runs API, which archives the forecasts of national weather models from 2024 on, free for non-commercial use; and hourly observations from Meteostat station 72405, Washington National Airport (CC BY 4.0).

import hashlib
import json
import time
from datetime import datetime
from pathlib import Path

import meteostat as ms
import pandas as pd
import requests

Path("data").mkdir(exist_ok=True)
LAT, LON, TZ = 38.8472, -77.0345, "America/New_York"
LEADS = {"1 day": "temperature_2m_previous_day1", "3 days": "temperature_2m_previous_day3",
         "5 days": "temperature_2m_previous_day5", "7 days": "temperature_2m_previous_day7"}

def previous_runs(start, end):
    params = {"latitude": LAT, "longitude": LON, "timezone": TZ, "start_date": start, "end_date": end,
              "hourly": ",".join(["temperature_2m"] + list(LEADS.values()))}
    f = Path("data/prev_" + hashlib.md5(json.dumps(params, sort_keys=True).encode()).hexdigest() + ".json")
    if not f.exists():
        for attempt in range(6):
            r = requests.get("https://previous-runs-api.open-meteo.com/v1/forecast", params=params, timeout=600)
            if r.status_code == 429:
                time.sleep(60 * (attempt + 1)); continue
            r.raise_for_status(); f.write_text(r.text); break
    return pd.DataFrame(json.loads(f.read_text())["hourly"]).assign(time=lambda d: pd.to_datetime(d.time)).set_index("time")
print("ready")
ready

The previous-runs helper is ready.

Example 1: how errors grow with lead time

The day-ahead forecast is the baseline. Express the error at each lead as a multiple of it:

base = score.loc["1 day", "MAE °C"]
print({lead: f"{v / base:.2f}x" for lead, v in score["MAE °C"].items()})
big = (J["temperature_2m_previous_day7"] - J.observed).abs()
print(f"hours where the 7-day forecast was off by more than 5 °C: {(big > 5).mean():.1%}; for the 1-day forecast: {((J['temperature_2m_previous_day1'] - J.observed).abs() > 5).mean():.1%}")
{'1 day': '1.00x', '3 days': '0.98x', '5 days': '1.24x', '7 days': '1.53x'}
hours where the 7-day forecast was off by more than 5 °C: 16.0%; for the 1-day forecast: 2.4%

Relative to the day-ahead forecast, the error grows to 1.24 times at five days and 1.53 times at seven. Large misses grow faster: the seven-day forecast was more than 5 °C off in 16.0% of hours, the one-day forecast in 2.4%.

Line chart of forecast error by hour of day: the one-day forecast between 1.6 and 2.0 °C, the seven-day forecast rising to about 3.4 °C in the afternoon. Line chart of forecast error by hour of day: the one-day forecast between 1.6 and 2.0 °C, the seven-day forecast rising to about 3.4 °C in the afternoon.
Error by hour of day.

Forecast APIs serve weather models

How to score archived forecasts against observations. scoring forecastsprevious runsAPIprevious_day1..7station hoursMeteostat, UTCalign timezoneslocaljoin8,200 hoursMAE and biasper lead timeLive forecasts change; archives do not spatialworkflow.io
Scoring forecasts.

A weather forecast API serves the output of numerical weather prediction models: the American GFS, ECMWF's IFS, Germany's ICON, Canada's GEM and others, run several times a day from the latest observations. Open-Meteo combines them into a "best match" for each location and returns the result as JSON. The forecast changes every time the models run, so the values above will differ tomorrow, which is why code that uses forecasts should store each response with the time it was fetched.

Forecast skill falls with lead time because small errors in the starting state grow as the model runs forward, and weather is chaotic. Scoring that decline needs the forecasts as they were issued, not as they are now, which is what an archive of previous runs provides. Temperature is the easiest variable to forecast and to score. Precipitation and wind need other measures, because a forecast of 2 mm on a day with 0 mm is a different kind of error from being 2 °C too warm. Errors also vary with season and time of day, and the observing station's siting matters as much as the model.

Reproducibility, models and ensembles

Checks when using weather forecast data. before relying on a forecastsave the responseforecasts change every runscore past forecastserror grows with leadbias vs errorcell vs stationseason and hourNov and dawn worstpin a model if neededmodels=Open-Meteo forecast and previous runs APIs spatialworkflow.io
Before relying on a forecast.
  • Reproducibility: live forecast values change with every model run; save the response if results must be repeatable.
  • Models: models=gfs_seamless, ecmwf_ifs025 or icon_seamless pin the forecast to one model.
  • Ensembles: the ensemble API returns all members, for probability forecasts rather than single values.
  • Station time stamps: observations are in UTC; align them with the forecast's time zone before joining.

FAQ

How do I get a weather forecast in Python?

Call the Open-Meteo forecast API with latitude, longitude, the variables and forecast_days; it returns JSON that pandas reads directly.

How accurate are weather forecasts?

For hourly temperature at Washington National in 2024, the one- and three-day forecasts were off by about 1.8 °C on average, the seven-day forecast by 2.8 °C; measure it for your location with archived forecasts.

How do I get past weather forecasts?

Open-Meteo's previous-runs API returns the values forecast one to seven days before each hour, from 2024 on.

Is the Open-Meteo forecast API free?

Yes for non-commercial use without a key; commercial use needs a subscription.