Fixing ERA5 Temperatures in Kelvin and Precipitation in Metres
On this page
Problem statement
ERA5 data straight from the archive looks wrong at first sight: summer temperatures of 300, rainfall totals of 0.003, and daily sums that are many times too large. Nothing is wrong. ERA5 uses SI units, kelvin for temperature and metres of water for precipitation, and its precipitation is an accumulation over a period, not a rate. Converting temperature is one subtraction. Converting precipitation needs care, because summing hourly values is right in one ERA5 product and badly wrong in another.
This page reads temperature and precipitation for Washington, D.C. from the ERA5 Zarr store on Google Cloud. It converts temperature and dew point from kelvin, turns precipitation from metres to millimetres, and checks the daily total against the Open-Meteo archive's ERA5 series and the station at Reagan National Airport.
Quick answer
Subtract 273.15 from temperatures and multiply precipitation by 1,000:
t = ds["2m_temperature"].sel(time=HOURS, latitude=38.75, longitude=283.0, method="nearest").load()
tp = ds["total_precipitation"].sel(time=HOURS, latitude=38.75, longitude=283.0, method="nearest").load()
print(f"units in the store: temperature {ds['2m_temperature'].attrs.get('units')}, precipitation {ds['total_precipitation'].attrs.get('units')}")
print(f"raw: temperature {float(t.max()):.2f}, precipitation {float(tp.sum()):.5f}; converted: {float(t.max()) - 273.15:.1f} °C, {float(tp.sum()) * 1000:.1f} mm over the {len(HOURS)} hours")
units in the store: temperature K, precipitation m
raw: temperature 311.46, precipitation 0.00000; converted: 38.3 °C, 0.0 mm over the 4 hours
The store labels temperature in K and precipitation in m. The afternoon peak of 311.46 K is 38.3 °C. Precipitation over those hours was zero, which is easy to misread as missing when the raw values are printed in metres.
Dew point and humidity, accumulations, the daily-mean trap
1. Dew point and humidity
Dew point is also in kelvin. Relative humidity follows from temperature and dew point with the Magnus formula:
td = ds["2m_dewpoint_temperature"].sel(time=HOURS, latitude=38.75, longitude=283.0, method="nearest").load() - 273.15
tc = t - 273.15
rh = 100 * np.exp(17.625 * td / (243.04 + td)) / np.exp(17.625 * tc / (243.04 + tc))
print(pd.DataFrame({"temp °C": tc.to_series(), "dew point °C": td.to_series(), "RH %": rh.to_series()}).astype(float).round(1).to_string())
temp °C dew point °C RH %
time
2024-07-16 12:00:00 27.3 22.5 74.9
2024-07-16 15:00:00 33.9 22.4 51.2
2024-07-16 18:00:00 37.2 20.3 37.6
2024-07-16 21:00:00 38.3 19.2 32.9
Through the afternoon of 16 July the temperature rose from 27.3 to 38.3 °C, while the dew point fell from 22.5 to 19.2 °C. Relative humidity dropped from 74.9% to 32.9%.
2. What "accumulated" means here
In this hourly store, each total_precipitation value is the amount that fell in the hour ending at its time step, so a day's total is the sum of 24 hours. Take 9 January 2024, the station's wettest day of the year, and compare with the Open-Meteo archive's ERA5 daily total and the station:
day = slice("2024-01-09T06:00", "2024-01-10T05:00") # hours ending 06:00-05:00 UTC: local 9 January (EST)
tp_day = float(ds["total_precipitation"].sel(latitude=38.75, longitude=283.0, method="nearest").sel(time=day).sum()) * 1000
om = om_daily("2024-01-09", "precipitation_sum")
sta = G.loc["2024-01-09", "PRCP"]
print(f"9 January 2024 precipitation: ERA5 store, 24 hourly values summed {tp_day:.1f} mm; Open-Meteo ERA5 daily {om:.1f} mm; station {sta:.1f} mm")
9 January 2024 precipitation: ERA5 store, 24 hourly values summed 50.3 mm; Open-Meteo ERA5 daily 49.7 mm; station 56.9 mm
Summing the 24 hourly values of 9 January gives 50.3 mm, matching Open-Meteo's ERA5 daily total of 49.7 mm, while the station measured 56.9 mm. The units and the summing are right. What remains is the difference between a model cell and a gauge.
3. The daily-mean trap
In the daily ERA5 products on the Climate Data Store, precipitation is a daily mean of hourly accumulations, or an accumulation from the forecast start. Show what multiplying an hourly mean by the wrong count does:
hourly_mm = ds["total_precipitation"].sel(latitude=38.75, longitude=283.0, method="nearest").sel(time=day).load() * 1000
mean_rate = float(hourly_mm.mean())
print(f"mean hourly amount {mean_rate:.2f} mm; x 24 = {mean_rate * 24:.1f} mm (the day's total); summing daily means of a month without x 24 would understate it 24-fold")
mean hourly amount 2.10 mm; x 24 = 50.3 mm (the day's total); summing daily means of a month without x 24 would understate it 24-fold
The day averaged 2.10 mm an hour. Multiplied by 24 that is the day's 50.3 mm, but a product that stores this mean, if summed as though it were a total, would understate the rain 24-fold.
273 or 273.15
The data used on this page
ERA5 hourly single-level data (Copernicus Climate Change Service) from Google's public ARCO-ERA5 Zarr store; the Open-Meteo archive ERA5 daily series; and the GHCN-Daily record for Reagan National Airport (USW00013743).
import hashlib
import json
from pathlib import Path
import numpy as np
import pandas as pd
import requests
import xarray as xr
Path("data").mkdir(exist_ok=True)
ds = xr.open_zarr("gs://gcp-public-data-arco-era5/ar/full_37-1h-0p25deg-chunk-1.zarr-v3", chunks=None, storage_options={"token": "anon"})
HOURS = pd.date_range("2024-07-16T12:00", "2024-07-16T23:00", freq="3h")
def om_daily(day, var):
params = {"latitude": 38.8472, "longitude": -77.0345, "start_date": day, "end_date": day, "daily": var, "models": "era5", "timezone": "America/New_York"}
f = Path("data/om_" + hashlib.md5(json.dumps(params, sort_keys=True).encode()).hexdigest() + ".json")
if not f.exists():
r = requests.get("https://archive-api.open-meteo.com/v1/archive", params=params, timeout=300); r.raise_for_status(); f.write_text(r.text)
return json.loads(f.read_text())["daily"][var][0]
CSV = Path("data/USW00013743.csv")
if not CSV.exists():
r = requests.get("https://www.ncei.noaa.gov/data/global-historical-climatology-network-daily/access/USW00013743.csv", timeout=600); r.raise_for_status(); CSV.write_bytes(r.content)
G = pd.read_csv(CSV, usecols=["DATE", "PRCP"], parse_dates=["DATE"]).set_index("DATE") / 10
print(f"{len(HOURS)} time steps selected")
4 time steps selected
Four 3-hourly steps on 16 July 2024 are selected.
Example 1: 273 or 273.15
The difference between subtracting 273 and 273.15 is small but systematic:
mean_k = float(t.mean())
print(f"mean of the selected hours: {mean_k - 273.15:.2f} °C with 273.15, {mean_k - 273:.2f} °C with 273; error {0.15:.2f} °C in every value")
mean of the selected hours: 34.18 °C with 273.15, 34.33 °C with 273; error 0.15 °C in every value
Subtracting 273 instead of 273.15 makes every value 0.15 °C too warm, 34.33 instead of 34.18 °C here: small, but systematic, and enough to bias a trend comparison.
SI units and accumulated variables
ERA5 follows the conventions of the ECMWF model that produces it: SI units throughout, and variables that are either instantaneous or accumulated. Temperature, dew point, wind and pressure are instantaneous, the state at the time step, in K, m/s and Pa. Precipitation, radiation and evaporation are accumulated, a total over a period, in metres of water for precipitation and J/m² for radiation. In the hourly ERA5 data, including Google's ARCO store, a precipitation value is the total for the hour ending at that time, so 24 consecutive values sum to a day. Daily statistics products and ERA5-Land use other conventions, such as daily means or accumulation from 00 UTC, and those need different handling.
Conversion is mechanical once the convention is known. Subtract 273.15 for °C, multiply metres by 1,000 for millimetres, and sum hourly accumulations within the local day, which in Washington starts at 04:00 or 05:00 UTC. ERA5 precipitation is a model product, so a day's total at a point can differ widely from a gauge even when the units are right. Check the units against a reference before trusting the magnitude.
Negative precipitation, radiation and ERA5-Land
- Negative precipitation: tiny negative values can appear from data packing; clip at zero.
- Radiation: divide accumulated J/m² by 3,600 for an hourly mean in W/m².
- ERA5-Land: its accumulations run from 00 UTC through the day; take differences between steps to get hourly amounts.
- GRIB from the CDS: the same units apply; cfgrib keeps the
unitsattribute, so check it.
Internal links
- How to get ERA5 reanalysis data in Python — the data source
- Fixing GHCN temperatures in tenths of a degree — the station-data equivalent
- Fixing weather data in the wrong time zone — summing within the local day
FAQ
How do I convert ERA5 temperature to Celsius?
Subtract 273.15 from the kelvin values.
What units is ERA5 precipitation in?
Metres of water accumulated over the period; multiply by 1,000 for millimetres.
How do I get daily precipitation from hourly ERA5?
Sum the 24 hourly values within the day you want, in local time if the day should be local.
Why is my ERA5 rainfall far too small or too large?
Usually metres not converted to millimetres, or daily means and accumulations confused between ERA5 products.