What Is NetCDF?
NetCDF — Network Common Data Form — is a set of software libraries and self-describing, machine-independent data formats designed for array-oriented scientific data. Originally developed by Unidata at the University Corporation for Atmospheric Research (UCAR) in 1989, NetCDF has become the dominant format for storing and sharing climate model output, weather reanalysis products, satellite observations, and oceanographic data.
A NetCDF file is self-describing: it contains not just the data arrays, but also metadata describing what those arrays represent — variable names, units, spatial and temporal coordinates, and provenance information. Opening a NetCDF file in any NetCDF-aware tool reveals the complete structure without needing any external schema or documentation file.
The NetCDF Data Model
Every NetCDF file is built from three fundamental concepts:
Dimensions — named axes with a size:
time = UNLIMITED; // special: can grow as data is appended
lat = 721; // 0.25° grid → 180°/0.25° + 1 = 721 rows
lon = 1440; // 0.25° grid → 360°/0.25° = 1440 columns
lev = 37; // 37 pressure levels
Variables — multi-dimensional arrays using zero or more dimensions:
float temperature(time, lev, lat, lon);
temperature:long_name = "Air Temperature";
temperature:units = "K";
temperature:_FillValue = -9999.f;
temperature:missing_value = -9999.f;
temperature:scale_factor = 0.001f;
temperature:add_offset = 250.f;
double time(time);
time:units = "hours since 1900-01-01 00:00:00.0";
time:calendar = "gregorian";
time:long_name = "Time";
Global attributes — metadata about the file itself:
:Conventions = "CF-1.9";
:history = "2024-03-15T00:00:00 ECMWF ERA5 reanalysis";
:institution = "European Centre for Medium-Range Weather Forecasts";
:source = "ERA5 hourly data on pressure levels";
:title = "ERA5 Temperature 2023";
NetCDF Versions
| Version | Notes |
|---|---|
| Classic (netCDF-3) | Original format; limited to 4 GB; still widely used |
| 64-bit Offset | Removes 4 GB limit; otherwise identical to Classic |
| netCDF-4 | Built on HDF5; supports groups, compression, chunking, multiple unlimited dimensions |
| netCDF-4 Classic Model | HDF5 container, but restricted to classic data model features |
NetCDF-4 is the recommended version for new files. It enables ZLIB compression (typically 2–8× size reduction for gridded climate data), chunking (optimizing for either time-series or spatial slice access), and parallel I/O via HDF5+MPI.
CF Conventions: The Metadata Standard
NetCDF files alone don't specify what "latitude" means or how to interpret time values. The CF Conventions (Climate and Forecast Metadata Conventions) fill this gap — a community-maintained standard defining:
- Standard names for physical quantities (
air_temperature,sea_surface_temperature,precipitation_flux) - Coordinate variable requirements (how lat/lon/time/lev must be declared)
- Cell methods for statistical aggregations (
time: mean,time: maximum) - Grid mapping attributes for non-geographic projections (Lambert conformal, polar stereographic)
- Ancillary variables (quality flags, uncertainties)
A NetCDF file claiming Conventions = "CF-1.9" is machine-interpretable by any CF-aware tool. The ECMWF ERA5 reanalysis, CMIP6 climate model outputs, and NOAA GFS weather model all use CF conventions.
Reading NetCDF with Python
xarray is the modern, high-level NetCDF interface:
import xarray as xr
import numpy as np
# Open a NetCDF file (lazy loading — data not read until needed)
ds = xr.open_dataset("era5_temperature_2023.nc")
print(ds) # overview of all variables
print(ds.dims) # {'time': 8760, 'level': 37, 'lat': 721, 'lon': 1440}
print(ds['temperature'].attrs) # metadata: units, long_name, etc.
# Select data: temperature at 850 hPa pressure level on Jan 15, 2023
t850 = ds['temperature'].sel(
level=850,
time="2023-01-15T12:00",
method="nearest"
)
print(f"Global mean at 850 hPa: {float(t850.mean()):.2f} K")
print(f"Max over Europe: {float(t850.sel(lat=slice(71,35), lon=slice(-10,40)).max()):.2f} K")
# Time mean over the full year
annual_mean = ds['temperature'].sel(level=850).mean(dim='time')
# Write to a new NetCDF file
annual_mean.to_netcdf("era5_t850_annual_mean_2023.nc")
# Open multiple files as a single dataset (concatenating along 'time')
ds_all = xr.open_mfdataset("era5_*.nc", combine="by_coords")
The lower-level netCDF4 library:
from netCDF4 import Dataset
import numpy as np
with Dataset("era5_temperature_2023.nc", "r") as nc:
print(nc.variables.keys()) # all variable names
time_var = nc.variables["time"]
lat = nc.variables["lat"][:] # masked numpy array
lon = nc.variables["lon"][:]
temp = nc.variables["temperature"] # lazy — not loaded yet
# Load one time step to save memory
t0 = temp[0, :, :, :] # shape (37, 721, 1440)
print(f"Shape: {t0.shape}")
print(f"Fill value: {temp._FillValue}")
# Decode time units to datetime objects
import cftime
times = cftime.num2date(time_var[:], time_var.units, time_var.calendar)
print(f"First timestamp: {times[0]}")
Command-Line Tools
ncdump — inspect NetCDF structure:
ncdump -h era5.nc # header only (metadata)
ncdump -v temperature era5.nc # dump temperature variable as ASCII
CDO (Climate Data Operators) — ECMWF's command-line toolkit:
# Compute annual mean
cdo yearmean era5_temperature_2023.nc annual_mean.nc
# Extract one variable from a multi-variable file
cdo select,name=temperature multi_var.nc temperature_only.nc
# Regrid from 0.25° to 1° using bilinear interpolation
cdo remapbil,r360x181 era5_0p25.nc era5_1deg.nc
# Convert from GRIB2 to NetCDF
cdo -f nc copy ecmwf_forecast.grib2 forecast.nc
# Time series at a specific location (Paris: lat=48.9, lon=2.3)
cdo remapnn,lon=2.3/lat=48.9 era5.nc paris_timeseries.nc
NCO (NetCDF Operators):
ncrcat file1.nc file2.nc merged.nc # concatenate along record dimension
ncks -v temperature file.nc out.nc # extract one variable
ncatted -a units,temperature,o,c,"degC" file.nc # edit attribute
Major Datasets Using NetCDF
- ERA5 (ECMWF): 80+ years of global hourly reanalysis at 0.25° (~5 km) resolution; 0.5 PB total; free via Copernicus Climate Data Store
- CMIP6: output from ~50 global climate models under the 6th Coupled Model Intercomparison Project; used in IPCC AR6
- NOAA GFS: Global Forecast System operational NWP model output; available as GRIB2 and NetCDF
- ARGO floats: ~4,000 profiling floats providing ocean temperature/salinity to 2,000 m depth; distributed as individual profile NetCDF files
- NASA MODIS/VIIRS: land surface temperature, vegetation indices, cloud properties distributed as NetCDF (Level 3 products)
- HYCOM: HYbrid Coordinate Ocean Model — global ocean state (3D temperature, salinity, velocity) at 1/12° resolution
NetCDF vs GRIB2
| Feature | NetCDF-4 | GRIB2 |
|---|---|---|
| Readability | Human-readable structure (ncdump) | Binary, needs eccodes/cfgrib |
| Compression | ZLIB via HDF5 | JPEG2000 or PNG in data section |
| Dominant use | Climate models, reanalysis archives | NWP operational forecasts |
| Python library | xarray, netCDF4 | cfgrib, eccodes-python |
| Self-describing | Yes | Partially (edition 2 improved) |
| Unlimited dimensions | Yes (netCDF-4) | No concept |
Related conversions
Frequent conversions across the catalogue: