Interactive Visualuzation using hvPlot
¶
Overview¶
ERA-5 Dataset is available from NCAR RDA in netcdf format. A subset of this dataset is processed into Zarr format and available from NCAR RDA endpoints. To learn how you can create Zarr files from NCAR RDA netcdf files, please see this notebook.
By the end of this notebook, you should be able to:
Understand the importance for interactive plots and the challenges associated with them
Use
hvPlot
to generate basic interactive plots withXarray
Imports¶
import holoviews as hv
import xarray as xr
from holoviews import opts
hv.extension("bokeh")
Data¶
As we mentioned above a subset of NCAR RDA data is available in Zarr format.
rda_url = "https://data.rda.ucar.edu/"
annual_means = rda_url + "pythia_era5_24/annual_means/"
xrds = xr.open_dataset(annual_means + "temp_2m_annual_1940_2023.zarr", engine="zarr")
xrds = xrds.isel(time=slice(0, 5))
xrds.load()
---------------------------------------------------------------------------
FileNotFoundError Traceback (most recent call last)
File ~/micromamba/envs/ERA5_interactive/lib/python3.13/site-packages/fsspec/mapping.py:155, in FSMap.__getitem__(self, key, default)
154 try:
--> 155 result = self.fs.cat(k)
156 except self.missing_exceptions as exc:
File ~/micromamba/envs/ERA5_interactive/lib/python3.13/site-packages/fsspec/asyn.py:118, in sync_wrapper.<locals>.wrapper(*args, **kwargs)
117 self = obj or args[0]
--> 118 return sync(self.loop, func, *args, **kwargs)
File ~/micromamba/envs/ERA5_interactive/lib/python3.13/site-packages/fsspec/asyn.py:103, in sync(loop, func, timeout, *args, **kwargs)
102 elif isinstance(return_result, BaseException):
--> 103 raise return_result
104 else:
File ~/micromamba/envs/ERA5_interactive/lib/python3.13/site-packages/fsspec/asyn.py:56, in _runner(event, coro, result, timeout)
55 try:
---> 56 result[0] = await coro
57 except Exception as ex:
File ~/micromamba/envs/ERA5_interactive/lib/python3.13/site-packages/fsspec/asyn.py:464, in AsyncFileSystem._cat(self, path, recursive, on_error, batch_size, **kwargs)
463 if ex:
--> 464 raise ex
465 if (
466 len(paths) > 1
467 or isinstance(path, list)
468 or paths[0] != self._strip_protocol(path)
469 ):
File ~/micromamba/envs/ERA5_interactive/lib/python3.13/site-packages/fsspec/asyn.py:244, in _run_coros_in_chunks.<locals>._run_coro(coro, i)
243 try:
--> 244 return await asyncio.wait_for(coro, timeout=timeout), i
245 except Exception as e:
File ~/micromamba/envs/ERA5_interactive/lib/python3.13/asyncio/tasks.py:507, in wait_for(fut, timeout)
506 async with timeouts.timeout(timeout):
--> 507 return await fut
File ~/micromamba/envs/ERA5_interactive/lib/python3.13/site-packages/fsspec/implementations/http.py:246, in HTTPFileSystem._cat_file(self, url, start, end, **kwargs)
245 out = await r.read()
--> 246 self._raise_not_found_for_status(r, url)
247 return out
File ~/micromamba/envs/ERA5_interactive/lib/python3.13/site-packages/fsspec/implementations/http.py:228, in HTTPFileSystem._raise_not_found_for_status(self, response, url)
227 if response.status == 404:
--> 228 raise FileNotFoundError(url)
229 response.raise_for_status()
FileNotFoundError: https://data.rda.ucar.edu/pythia_era5_24/annual_means/temp_2m_annual_1940_2023.zarr/.zmetadata
The above exception was the direct cause of the following exception:
KeyError Traceback (most recent call last)
File ~/micromamba/envs/ERA5_interactive/lib/python3.13/site-packages/zarr/storage.py:1446, in FSStore.__getitem__(self, key)
1445 try:
-> 1446 return self.map[key]
1447 except self.exceptions as e:
File ~/micromamba/envs/ERA5_interactive/lib/python3.13/site-packages/fsspec/mapping.py:159, in FSMap.__getitem__(self, key, default)
158 return default
--> 159 raise KeyError(key) from exc
160 return result
KeyError: '.zmetadata'
The above exception was the direct cause of the following exception:
KeyError Traceback (most recent call last)
File ~/micromamba/envs/ERA5_interactive/lib/python3.13/site-packages/xarray/backends/zarr.py:1862, in _get_open_params(store, mode, synchronizer, group, consolidated, consolidate_on_close, chunk_store, storage_options, zarr_version, use_zarr_fill_value_as_mask, zarr_format)
1861 try:
-> 1862 zarr_root_group = zarr.open_consolidated(store, **open_kwargs)
1863 except (ValueError, KeyError):
1864 # ValueError in zarr-python 3.x, KeyError in 2.x.
File ~/micromamba/envs/ERA5_interactive/lib/python3.13/site-packages/zarr/convenience.py:1362, in open_consolidated(store, metadata_key, mode, **kwargs)
1361 # setup metadata store
-> 1362 meta_store = ConsolidatedStoreClass(store, metadata_key=metadata_key)
1364 # pass through
File ~/micromamba/envs/ERA5_interactive/lib/python3.13/site-packages/zarr/storage.py:3045, in ConsolidatedMetadataStore.__init__(self, store, metadata_key)
3044 # retrieve consolidated metadata
-> 3045 meta = json_loads(self.store[metadata_key])
3047 # check format of consolidated metadata
File ~/micromamba/envs/ERA5_interactive/lib/python3.13/site-packages/zarr/storage.py:1448, in FSStore.__getitem__(self, key)
1447 except self.exceptions as e:
-> 1448 raise KeyError(key) from e
KeyError: '.zmetadata'
During handling of the above exception, another exception occurred:
GroupNotFoundError Traceback (most recent call last)
File ~/micromamba/envs/ERA5_interactive/lib/python3.13/site-packages/xarray/backends/zarr.py:1866, in _get_open_params(store, mode, synchronizer, group, consolidated, consolidate_on_close, chunk_store, storage_options, zarr_version, use_zarr_fill_value_as_mask, zarr_format)
1865 try:
-> 1866 zarr_root_group = zarr.open_group(store, **open_kwargs)
1867 emit_user_level_warning(
1868 "Failed to open Zarr store with consolidated metadata, "
1869 "but successfully read with non-consolidated metadata. "
(...) 1879 RuntimeWarning,
1880 )
File ~/micromamba/envs/ERA5_interactive/lib/python3.13/site-packages/zarr/hierarchy.py:1578, in open_group(store, mode, cache_attrs, synchronizer, path, chunk_store, storage_options, zarr_version, meta_array)
1577 raise ContainsArrayError(path)
-> 1578 raise GroupNotFoundError(path)
1580 elif mode == "w":
GroupNotFoundError: group not found at path ''
The above exception was the direct cause of the following exception:
FileNotFoundError Traceback (most recent call last)
Cell In[2], line 3
1 rda_url = "https://data.rda.ucar.edu/"
2 annual_means = rda_url + "pythia_era5_24/annual_means/"
----> 3 xrds = xr.open_dataset(annual_means + "temp_2m_annual_1940_2023.zarr", engine="zarr")
4 xrds = xrds.isel(time=slice(0, 5))
5 xrds.load()
File ~/micromamba/envs/ERA5_interactive/lib/python3.13/site-packages/xarray/backends/api.py:760, in open_dataset(filename_or_obj, engine, chunks, cache, decode_cf, mask_and_scale, decode_times, decode_timedelta, use_cftime, concat_characters, decode_coords, drop_variables, create_default_indexes, inline_array, chunked_array_type, from_array_kwargs, backend_kwargs, **kwargs)
748 decoders = _resolve_decoders_kwargs(
749 decode_cf,
750 open_backend_dataset_parameters=backend.open_dataset_parameters,
(...) 756 decode_coords=decode_coords,
757 )
759 overwrite_encoded_chunks = kwargs.pop("overwrite_encoded_chunks", None)
--> 760 backend_ds = backend.open_dataset(
761 filename_or_obj,
762 drop_variables=drop_variables,
763 **decoders,
764 **kwargs,
765 )
766 ds = _dataset_from_backend_dataset(
767 backend_ds,
768 filename_or_obj,
(...) 779 **kwargs,
780 )
781 return ds
File ~/micromamba/envs/ERA5_interactive/lib/python3.13/site-packages/xarray/backends/zarr.py:1654, in ZarrBackendEntrypoint.open_dataset(self, filename_or_obj, mask_and_scale, decode_times, concat_characters, decode_coords, drop_variables, use_cftime, decode_timedelta, group, mode, synchronizer, consolidated, chunk_store, storage_options, zarr_version, zarr_format, store, engine, use_zarr_fill_value_as_mask, cache_members)
1652 filename_or_obj = _normalize_path(filename_or_obj)
1653 if not store:
-> 1654 store = ZarrStore.open_group(
1655 filename_or_obj,
1656 group=group,
1657 mode=mode,
1658 synchronizer=synchronizer,
1659 consolidated=consolidated,
1660 consolidate_on_close=False,
1661 chunk_store=chunk_store,
1662 storage_options=storage_options,
1663 zarr_version=zarr_version,
1664 use_zarr_fill_value_as_mask=None,
1665 zarr_format=zarr_format,
1666 cache_members=cache_members,
1667 )
1669 store_entrypoint = StoreBackendEntrypoint()
1670 with close_on_error(store):
File ~/micromamba/envs/ERA5_interactive/lib/python3.13/site-packages/xarray/backends/zarr.py:714, in ZarrStore.open_group(cls, store, mode, synchronizer, group, consolidated, consolidate_on_close, chunk_store, storage_options, append_dim, write_region, safe_chunks, align_chunks, zarr_version, zarr_format, use_zarr_fill_value_as_mask, write_empty, cache_members)
688 @classmethod
689 def open_group(
690 cls,
(...) 707 cache_members: bool = True,
708 ):
709 (
710 zarr_group,
711 consolidate_on_close,
712 close_store_on_close,
713 use_zarr_fill_value_as_mask,
--> 714 ) = _get_open_params(
715 store=store,
716 mode=mode,
717 synchronizer=synchronizer,
718 group=group,
719 consolidated=consolidated,
720 consolidate_on_close=consolidate_on_close,
721 chunk_store=chunk_store,
722 storage_options=storage_options,
723 zarr_version=zarr_version,
724 use_zarr_fill_value_as_mask=use_zarr_fill_value_as_mask,
725 zarr_format=zarr_format,
726 )
728 return cls(
729 zarr_group,
730 mode,
(...) 739 cache_members=cache_members,
740 )
File ~/micromamba/envs/ERA5_interactive/lib/python3.13/site-packages/xarray/backends/zarr.py:1882, in _get_open_params(store, mode, synchronizer, group, consolidated, consolidate_on_close, chunk_store, storage_options, zarr_version, use_zarr_fill_value_as_mask, zarr_format)
1867 emit_user_level_warning(
1868 "Failed to open Zarr store with consolidated metadata, "
1869 "but successfully read with non-consolidated metadata. "
(...) 1879 RuntimeWarning,
1880 )
1881 except missing_exc as err:
-> 1882 raise FileNotFoundError(
1883 f"No such file or directory: '{store}'"
1884 ) from err
1886 # but the user should still receive a DataTree whose root is the group they asked for
1887 if group and group != "/":
FileNotFoundError: No such file or directory: 'https://data.rda.ucar.edu/pythia_era5_24/annual_means/temp_2m_annual_1940_2023.zarr'
Considerations for Interactive Plots¶
Add some markdown text on some of the following ideas:
What are some reasons we want to make data visualuzation interactive?
Baisc Interactivity using hvPlot
¶
The hvPlot
package is a familiar and high level API for data exploration and visualuzation.
One of the most powerfull features of hvPlot
is that it provides an alternative plotting API that directly attaches to existing Python objects through the .hvplot()
attribute. For the case of Xarray
, importing hvplot.xarray
adds a brand new set of plotting routines accessible either through xr.DataArray.hvplot()
or xr.Dataset.hvplot()
import hvplot.xarray
Before using hvPlot
, let’s take a look at the default Xarray
plotting methods.
xrds["VAR_2T"].plot()
We can replace the .plot()
function call with .hvplot()
. By default, hvPlot
uses the Bokeh
backend, which has naitive interactive tools, such as :
Panning
Box Select
Scroll Zoom
Saving
Resetting
xrds["VAR_2T"].hvplot()
If we wanted to plot ...
xrds["VAR_2T"].isel(time=0).plot()
Switching
xrds["VAR_2T"].isel(time=0).hvplot()
Time Widget¶
Climate data typically comes with multiple timesteps. We can create a basic widget that allows us to seek through time by setting the groupby='time'
parameter in our .hvplot()
call.
xrds["VAR_2T"].hvplot(groupby="time", widget_location="bottom")
You may notice that our colorbar is dynamically changing as we change our time steps. We can fix the colorbar by setting a clim
value, which is a tuple of the minimum and maximum desired colorbar range.
One suggestion is to use the minimum and maximum of the data variable you are visualuzing across time.
clim = (xrds["VAR_2T"].values.min(), xrds["VAR_2T"].values.max())
xrds["VAR_2T"].hvplot(clim=clim, groupby="time", widget_location="bottom")
You may have noticed that there is a slight lag when switching time steps. This is due to hvPlot
plotting the full resolution of our dataset. We can instead rasterize the output by setting rasterize=True
, which will significantly improve the perfromance of our interactive plot.
xrds["VAR_2T"].hvplot(
rasterize=True, clim=clim, groupby="time", widget_location="bottom"
)
Animation Widget¶
Another usefull interactive feature is animations. Instead of manually scrolling through time, we can set up a widget that lets us animate our data across time. This can be achieved by adding a Scrubber widget to our plot by setting widget_type="scrubber"
xrds["VAR_2T"].hvplot(
rasterize=True,
groupby="time",
widget_type="scrubber",
widget_location="bottom",
)