Read NetCDF Data with Python. Access a slightly confusing, yet... | by Konrad Hafen | Towards Data Science

Network common data form (NetCDF) is commonly used to store multidimensional geographic data. Some examples of these data are temperature, precipitation, and wind speed. Variables stored in NetCDF are often measured multiple times per day over large (continental) areas. With multiple measurements per day, data values accumulate quickly and become unwieldy to work with. When each value is also assigned to a geographic location, data management is further complicated. NetCDF provides a solution for these challenges. This article will get you started with reading data from NetCDF files using Python.
NetCDF files can be read with a few different Python modules. The most popular are netCDF4 and gdal. For this article we’ll focus strictly on netCDF4 as it is my personal preference.
For information on how to read and plot NetCDF data in Python with xarray and rioxarray check out this article.
Installation is simple. I generally recommend using the anaconda Python distribution to eliminate the confusion that can come with dependencies and versioning. To install with anaconda (conda) simply type conda install netCDF4. Alternatively, you can install with pip.
To be sure your netCDF4 module is properly installed start an interactive session in the terminal (type python and press ‘Enter’). Then import netCDF4 as nc.
Loading a dataset is simple, just pass a NetCDF file path to netCDF4.Dataset(). For this article, I’m using a file containing climate data from Daymet.
import netCDF4 as ncfn = '/path/to/file.nc4'ds = nc.Dataset(fn)
A NetCDF file has three basic parts: metadata, dimensions and variables. Variables contain both metadata and data. netCDF4 allows us to access the metadata and data associated with a NetCDF file.
Printing the dataset, ds, gives us information about the variables contained in the file and their dimensions.
print(ds)
And the output . . .
<class 'netCDF4._netCDF4.Dataset'>root group (NETCDF4_CLASSIC data model, file format HDF5):start_year: 1980source: Daymet Software Version 3.0Version_software: Daymet Software Version 3.0Version_data: Daymet Data Version 3.0Conventions: CF-1.6citation: Please see http://daymet.ornl.gov/ for current Daymet data citation informationreferences: Please see http://daymet.ornl.gov/ for current information on Daymet referencesdimensions(sizes): time(1), nv(2), y(8075), x(7814)variables(dimensions): float32 time_bnds(time,nv), int16 lambert_conformal_conic(), float32 lat(y,x), float32 lon(y,x), float32 prcp(time,y,x), float32 time(time), float32 x(x), float32 y(y)groups:
Above you can see information for the file format, data source, data version, citation, dimensions, and variables. The variables we’re interested in are lat, lon, time, and prcp (precipitation). With these variables we can find the precipitation at a given location for a given time. This file only contains one time step (time dimension is 1).
Metadata can also be accessed as a Python dictionary, which (in my opinion) is more useful.
print(ds.__dict__)OrderedDict([('start_year', 1980), ('source', 'Daymet Software Version 3.0'), ('Version_software', 'Daymet Software Version 3.0'), ('Version_data', 'Daymet Data Version 3.0'), ('Conventions', 'CF-1.6'), ('citation', 'Please see http://daymet.ornl.gov/ for current Daymet data citation information'), ('references', 'Please see http://daymet.ornl.gov/ for current information on Daymet references')])
Then any metadata item can be accessed with its key. For example:
print(ds.__dict__['start_year']1980
Access to dimensions is similar to file metadata. Each dimension is stored as a dimension class which contains pertinent information. Metadata for all dimensions can be access by looping through all available dimensions, like so.
for dim in ds.dimensions.values():    print(dim)<class 'netCDF4._netCDF4.Dimension'> (unlimited): name = 'time', size = 1<class 'netCDF4._netCDF4.Dimension'>: name = 'nv', size = 2<class 'netCDF4._netCDF4.Dimension'>: name = 'y', size = 8075<class 'netCDF4._netCDF4.Dimension'>: name = 'x', size = 7814
Individual dimensions are accessed like so: ds.dimensions['x'].
Access variable metadata in the same manner as dimensions. The code below shows how this is done. I’ve forgone the output because it is quite lengthy.
for var in ds.variables.values():    print(var)
The procedure to access information for a specific variable is demonstrated below for prcp (precipitation).
print(ds['prcp'])
And the output . . .
<class 'netCDF4._netCDF4.Variable'>float32 prcp(time, y, x)_FillValue: -9999.0coordinates: lat longrid_mapping: lambert_conformal_conicmissing_value: -9999.0cell_methods: area: mean time: sum within days time: sum over daysunits: mmlong_name: annual total precipitationunlimited dimensions: timecurrent shape = (1, 8075, 7814)
The actual precipitation data values are accessed by array indexing, and a numpy array is returned. All variable data is returned as follows:
prcp = ds['prcp'][:]
Or a subset can be returned. The following code returns a 2D subset.
prcp = ds['prcp'][0, 4000:4005, 4000:4005]
Here’s the 2D subset result.
[[341.0 347.0 336.0 329.0 353.0][336.0 339.0 341.0 332.0 349.0][337.0 340.0 334.0 336.0 348.0][342.0 344.0 332.0 338.0 350.0][350.0 351.0 342.0 346.0 348.0]]
NetCDF files are commonly used for geographic time-series data. Initially, they can be a bit intimidating to work with because of the large amounts of data contained, and the different format from the csv and raster files that are most commonly used. NetCDF is a great way to document geographic data because of the built in documentation and metadata. This makes it easy for end users to understand exactly what the data represent with little ambiguity. NetCDF data are accessed as numpy arrays, which present many possibilities for analysis and incorporation to existing tools and workflows.