Kerchunk and Xarray-Datatree at Scale

Overview

In this tutorial we are going to use a large collection of pre-generated Kerchunk reference files and open them with xarray-datatree. This chapter is heavily inspired by this blog post.

About the Dataset

This collection of reference files were generated from the NASA NEX-GDDP-CMIP6 (Global Daily Downscaled Projections) dataset. A version of this dataset is hosted on s3 as a collection of NetCDF files.

Prerequisites

Concepts

Importance

Notes

Kerchunk Basics

Required

Core

Multiple Files and Kerchunk

Required

Core

Kerchunk and Dask

Required

Core

Multi-File Datasets with Kerchunk

Required

IO/Visualization

Xarray-Datatree Overview

Required

IO

  • Time to learn: 30 minutes

Motivation

In total the dataset is roughly 12TB in compressed blob storage, with a single NetCDF file per yearly timestep, per variable. Downloading this entire dataset for analysis on a local machine would difficult to say the least. The collection of Kerchunk reference files for this entire dataset is only 272 Mb, which is about 42,000 times smaller!

Imports

from datatree import DataTree
import xarray as xr
import pandas as pd
import dask
from distributed import Client
from fsspec.implementations.reference import ReferenceFileSystem
import hvplot.xarray

Read the Reference Catalog

The NASA NEX-GDDP-CMIP6 dataset is organized by GCM, Scenario and Ensemble Member. Each of these Scenario/GCM combinations is represented as a combined reference file, which was created by merging across variables and concatenating along time-steps. All of these references are organized into a simple .csv catalog in the schema:

GCM/Scenario

url

Organzing with Xarray-Datatree

Not all of the GCM/Scenario reference datasets have shared spatial coordinates and many of the have slight differences in their calendar and thus time dimension. Because of this, these cannot be combined into a single Xarray-Dataset. Fortunately Xarray-Datatree provides a higher level abstraction where related Xarray-Datasets are organized into a tree structure where each dataset corresponds to a leaf.

# Read the reference catalog into a Pandas DataFrame
cat_df = pd.read_csv(
    "s3://carbonplan-share/nasa-nex-reference/reference_catalog_nested.csv"
)
# Convert the DataFrame into a dictionary
catalog = cat_df.set_index("ID").T.to_dict("records")[0]

Load Reference Datasets into Xarray-DataTree

In the following cell we create a function load_ref_ds, which can be parallelized via Dask to load Kerchunk references into a dictionary of Xarray-Datasets.

def load_ref_ds(url: str):

    fs = ReferenceFileSystem(
        url,
        remote_protocol="s3",
        target_protocol="s3",
        target_options={"anon": True},
        lazy=True,
    )
    return xr.open_dataset(
        fs.get_mapper(),
        engine="zarr",
        backend_kwargs={"consolidated": False},
        chunks={"time": 300},
    )


tasks = {id: dask.delayed(load_ref_ds)(url) for id, url in catalog.items()}

Use Dask Distributed to load the Xarray-Datasets from Kerchunk reference files

Using Dask, we are loading 164 reference datasets into memory. Since they are are Xarray datasets the coordinates are loaded eagerly, but the underlying data is still lazy.

client = Client(n_workers=8)
client

Client

Client-cf5b0644-67af-11ee-8ac0-00224808823d

Connection method: Cluster object Cluster type: distributed.LocalCluster
Dashboard: http://127.0.0.1:8787/status

Cluster Info

catalog_computed = dask.compute(tasks)

Build an Xarray-Datatree from the dictionary of datasets

dt = DataTree.from_dict(catalog_computed[0])

Accessing the Datatree

A Datatree is a collection of related Xarray datasets. We can access individual datasets using UNIX syntax. In the cell below, we will access a single dataset from the datatree.

dt["ACCESS-CM2/ssp585"]

# or

dt["ACCESS-CM2"]["ssp585"]
<xarray.DatasetView>
Dimensions:  (time: 31411, lat: 600, lon: 1440)
Coordinates:
  * lat      (lat) float64 6.912e-310 6.912e-310 6.912e-310 ... 0.0 0.0 0.0
  * lon      (lon) float64 6.912e-310 6.912e-310 6.912e-310 ... 0.0 0.0 0.0
  * time     (time) datetime64[ns] 2015-01-01T12:00:00 ... 2100-12-31T12:00:00
Data variables:
    hurs     (time, lat, lon) float32 dask.array<chunksize=(300, 600, 1440), meta=np.ndarray>
    huss     (time, lat, lon) float32 dask.array<chunksize=(300, 600, 1440), meta=np.ndarray>
    pr       (time, lat, lon) float32 dask.array<chunksize=(300, 600, 1440), meta=np.ndarray>
    rlds     (time, lat, lon) float32 dask.array<chunksize=(300, 600, 1440), meta=np.ndarray>
    rsds     (time, lat, lon) float32 dask.array<chunksize=(300, 600, 1440), meta=np.ndarray>
    sfcWind  (time, lat, lon) float32 dask.array<chunksize=(300, 600, 1440), meta=np.ndarray>
    tas      (time, lat, lon) float32 dask.array<chunksize=(300, 600, 1440), meta=np.ndarray>
    tasmax   (time, lat, lon) float32 dask.array<chunksize=(300, 600, 1440), meta=np.ndarray>
    tasmin   (time, lat, lon) float32 dask.array<chunksize=(300, 600, 1440), meta=np.ndarray>
Attributes: (12/22)
    Conventions:           CF-1.7
    activity:              NEX-GDDP-CMIP6
    cmip6_institution_id:  CSIRO-ARCCSS
    cmip6_license:         CC-BY-SA 4.0
    cmip6_source_id:       ACCESS-CM2
    contact:               Dr. Rama Nemani: rama.nemani@nasa.gov, Dr. Bridget...
    ...                    ...
    scenario:              ssp585
    source:                BCSD
    title:                 ACCESS-CM2, r1i1p1f1, ssp585, global downscaled CM...
    tracking_id:           dcae752c-5de5-4170-8a98-e7043538d702
    variant_label:         r1i1p1f1
    version:               1.0

Convert a Datatree node to a Dataset

dt["ACCESS-CM2"]["ssp585"].to_dataset()
<xarray.Dataset>
Dimensions:  (time: 31411, lat: 600, lon: 1440)
Coordinates:
  * lat      (lat) float64 6.912e-310 6.912e-310 6.912e-310 ... 0.0 0.0 0.0
  * lon      (lon) float64 6.912e-310 6.912e-310 6.912e-310 ... 0.0 0.0 0.0
  * time     (time) datetime64[ns] 2015-01-01T12:00:00 ... 2100-12-31T12:00:00
Data variables:
    hurs     (time, lat, lon) float32 dask.array<chunksize=(300, 600, 1440), meta=np.ndarray>
    huss     (time, lat, lon) float32 dask.array<chunksize=(300, 600, 1440), meta=np.ndarray>
    pr       (time, lat, lon) float32 dask.array<chunksize=(300, 600, 1440), meta=np.ndarray>
    rlds     (time, lat, lon) float32 dask.array<chunksize=(300, 600, 1440), meta=np.ndarray>
    rsds     (time, lat, lon) float32 dask.array<chunksize=(300, 600, 1440), meta=np.ndarray>
    sfcWind  (time, lat, lon) float32 dask.array<chunksize=(300, 600, 1440), meta=np.ndarray>
    tas      (time, lat, lon) float32 dask.array<chunksize=(300, 600, 1440), meta=np.ndarray>
    tasmax   (time, lat, lon) float32 dask.array<chunksize=(300, 600, 1440), meta=np.ndarray>
    tasmin   (time, lat, lon) float32 dask.array<chunksize=(300, 600, 1440), meta=np.ndarray>
Attributes: (12/22)
    Conventions:           CF-1.7
    activity:              NEX-GDDP-CMIP6
    cmip6_institution_id:  CSIRO-ARCCSS
    cmip6_license:         CC-BY-SA 4.0
    cmip6_source_id:       ACCESS-CM2
    contact:               Dr. Rama Nemani: rama.nemani@nasa.gov, Dr. Bridget...
    ...                    ...
    scenario:              ssp585
    source:                BCSD
    title:                 ACCESS-CM2, r1i1p1f1, ssp585, global downscaled CM...
    tracking_id:           dcae752c-5de5-4170-8a98-e7043538d702
    variant_label:         r1i1p1f1
    version:               1.0

Operations across a Datatree

A Datatree contains a collection of datasets with related coordinates and variables. Using some in-built methods, we can analyze it as if it were a single dataset. Instead of looping through hundreds of Xarray datasets, we can apply operations across the Datatree. In the example below, we will lazily create a time-series.

ts = dt.mean(dim=["lat", "lon"])

Visualize a single dataset with HvPlot

display(dt["ACCESS-CM2/ssp585"].to_dataset().pr.hvplot("lon", "lat", rasterize=True))
/usr/share/miniconda3/envs/kerchunk-cookbook/lib/python3.10/site-packages/holoviews/core/util.py:2062: RuntimeWarning: overflow encountered in scalar divide
  return length/diff
/usr/share/miniconda3/envs/kerchunk-cookbook/lib/python3.10/site-packages/holoviews/core/sheetcoords.py:232: RuntimeWarning: invalid value encountered in scalar multiply
  float_col = (x-self.lbrt[0]) * xdensity
WARNING:param.dynamic_operation: Callable raised "ValueError('cannot convert float NaN to integer')".
Invoked as dynamic_operation(numpy.datetime64('2015-01-01T12:00:00.000000000'))
WARNING:param.dynamic_operation: Callable raised "ValueError('cannot convert float NaN to integer')".
Invoked as dynamic_operation(numpy.datetime64('2015-01-01T12:00:00.000000000'), height=300, scale=1.0, width=700, x_range=None, y_range=None)
WARNING:param.dynamic_operation: Callable raised "ValueError('cannot convert float NaN to integer')".
Invoked as dynamic_operation(numpy.datetime64('2015-01-01T12:00:00.000000000'), height=300, scale=1.0, width=700, x_range=None, y_range=None)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
File /usr/share/miniconda3/envs/kerchunk-cookbook/lib/python3.10/site-packages/IPython/core/formatters.py:974, in MimeBundleFormatter.__call__(self, obj, include, exclude)
    971     method = get_real_method(obj, self.print_method)
    973     if method is not None:
--> 974         return method(include=include, exclude=exclude)
    975     return None
    976 else:

File /usr/share/miniconda3/envs/kerchunk-cookbook/lib/python3.10/site-packages/holoviews/core/dimension.py:1287, in Dimensioned._repr_mimebundle_(self, include, exclude)
   1280 def _repr_mimebundle_(self, include=None, exclude=None):
   1281     """
   1282     Resolves the class hierarchy for the class rendering the
   1283     object using any display hooks registered on Store.display
   1284     hooks.  The output of all registered display_hooks is then
   1285     combined and returned.
   1286     """
-> 1287     return Store.render(self)

File /usr/share/miniconda3/envs/kerchunk-cookbook/lib/python3.10/site-packages/holoviews/core/options.py:1423, in Store.render(cls, obj)
   1421 data, metadata = {}, {}
   1422 for hook in hooks:
-> 1423     ret = hook(obj)
   1424     if ret is None:
   1425         continue

File /usr/share/miniconda3/envs/kerchunk-cookbook/lib/python3.10/site-packages/holoviews/ipython/display_hooks.py:280, in pprint_display(obj)
    278 if not ip.display_formatter.formatters['text/plain'].pprint:
    279     return None
--> 280 return display(obj, raw_output=True)

File /usr/share/miniconda3/envs/kerchunk-cookbook/lib/python3.10/site-packages/holoviews/ipython/display_hooks.py:254, in display(obj, raw_output, **kwargs)
    252 elif isinstance(obj, (HoloMap, DynamicMap)):
    253     with option_state(obj):
--> 254         output = map_display(obj)
    255 elif isinstance(obj, Plot):
    256     output = render(obj)

File /usr/share/miniconda3/envs/kerchunk-cookbook/lib/python3.10/site-packages/holoviews/ipython/display_hooks.py:142, in display_hook.<locals>.wrapped(element)
    140 try:
    141     max_frames = OutputSettings.options['max_frames']
--> 142     mimebundle = fn(element, max_frames=max_frames)
    143     if mimebundle is None:
    144         return {}, {}

File /usr/share/miniconda3/envs/kerchunk-cookbook/lib/python3.10/site-packages/holoviews/ipython/display_hooks.py:202, in map_display(vmap, max_frames)
    199     max_frame_warning(max_frames)
    200     return None
--> 202 return render(vmap)

File /usr/share/miniconda3/envs/kerchunk-cookbook/lib/python3.10/site-packages/holoviews/ipython/display_hooks.py:69, in render(obj, **kwargs)
     66 if renderer.fig == 'pdf':
     67     renderer = renderer.instance(fig='png')
---> 69 return renderer.components(obj, **kwargs)

File /usr/share/miniconda3/envs/kerchunk-cookbook/lib/python3.10/site-packages/holoviews/plotting/renderer.py:397, in Renderer.components(self, obj, fmt, comm, **kwargs)
    394 embed = (not (dynamic or streams or self.widget_mode == 'live') or config.embed)
    396 if embed or config.comms == 'default':
--> 397     return self._render_panel(plot, embed, comm)
    398 return self._render_ipywidget(plot)

File /usr/share/miniconda3/envs/kerchunk-cookbook/lib/python3.10/site-packages/holoviews/plotting/renderer.py:404, in Renderer._render_panel(self, plot, embed, comm)
    402 doc = Document()
    403 with config.set(embed=embed):
--> 404     model = plot.layout._render_model(doc, comm)
    405 if embed:
    406     return render_model(model, comm)

File /usr/share/miniconda3/envs/kerchunk-cookbook/lib/python3.10/site-packages/panel/viewable.py:737, in Viewable._render_model(self, doc, comm)
    735 if comm is None:
    736     comm = state._comm_manager.get_server_comm()
--> 737 model = self.get_root(doc, comm)
    739 if self._design and self._design.theme.bokeh_theme:
    740     doc.theme = self._design.theme.bokeh_theme

File /usr/share/miniconda3/envs/kerchunk-cookbook/lib/python3.10/site-packages/panel/layout/base.py:306, in Panel.get_root(self, doc, comm, preprocess)
    302 def get_root(
    303     self, doc: Optional[Document] = None, comm: Optional[Comm] = None,
    304     preprocess: bool = True
    305 ) -> Model:
--> 306     root = super().get_root(doc, comm, preprocess)
    307     # ALERT: Find a better way to handle this
    308     if hasattr(root, 'styles') and 'overflow-x' in root.styles:

File /usr/share/miniconda3/envs/kerchunk-cookbook/lib/python3.10/site-packages/panel/viewable.py:659, in Renderable.get_root(self, doc, comm, preprocess)
    657 wrapper = self._design._wrapper(self)
    658 if wrapper is self:
--> 659     root = self._get_model(doc, comm=comm)
    660     if preprocess:
    661         self._preprocess(root)

File /usr/share/miniconda3/envs/kerchunk-cookbook/lib/python3.10/site-packages/panel/layout/base.py:174, in Panel._get_model(self, doc, root, parent, comm)
    172 root = root or model
    173 self._models[root.ref['id']] = (model, parent)
--> 174 objects, _ = self._get_objects(model, [], doc, root, comm)
    175 props = self._get_properties(doc)
    176 props[self._property_mapping['objects']] = objects

File /usr/share/miniconda3/envs/kerchunk-cookbook/lib/python3.10/site-packages/panel/layout/base.py:156, in Panel._get_objects(self, model, old_objects, doc, root, comm)
    154 else:
    155     try:
--> 156         child = pane._get_model(doc, root, model, comm)
    157     except RerenderError as e:
    158         if e.layout is not None and e.layout is not self:

File /usr/share/miniconda3/envs/kerchunk-cookbook/lib/python3.10/site-packages/panel/pane/holoviews.py:411, in HoloViews._get_model(self, doc, root, parent, comm)
    409     plot = self.object
    410 else:
--> 411     plot = self._render(doc, comm, root)
    413 plot.pane = self
    414 backend = plot.renderer.backend

File /usr/share/miniconda3/envs/kerchunk-cookbook/lib/python3.10/site-packages/panel/pane/holoviews.py:506, in HoloViews._render(self, doc, comm, root)
    503     if comm:
    504         kwargs['comm'] = comm
--> 506 return renderer.get_plot(self.object, **kwargs)

File /usr/share/miniconda3/envs/kerchunk-cookbook/lib/python3.10/site-packages/holoviews/plotting/bokeh/renderer.py:70, in BokehRenderer.get_plot(self_or_cls, obj, doc, renderer, **kwargs)
     63 @bothmethod
     64 def get_plot(self_or_cls, obj, doc=None, renderer=None, **kwargs):
     65     """
     66     Given a HoloViews Viewable return a corresponding plot instance.
     67     Allows supplying a document attach the plot to, useful when
     68     combining the bokeh model with another plot.
     69     """
---> 70     plot = super().get_plot(obj, doc, renderer, **kwargs)
     71     if plot.document is None:
     72         plot.document = Document() if self_or_cls.notebook_context else curdoc()

File /usr/share/miniconda3/envs/kerchunk-cookbook/lib/python3.10/site-packages/holoviews/plotting/renderer.py:218, in Renderer.get_plot(self_or_cls, obj, doc, renderer, comm, **kwargs)
    215     raise SkipRendering(msg.format(dims=dims))
    217 # Initialize DynamicMaps with first data item
--> 218 initialize_dynamic(obj)
    220 if not renderer:
    221     renderer = self_or_cls

File /usr/share/miniconda3/envs/kerchunk-cookbook/lib/python3.10/site-packages/holoviews/plotting/util.py:256, in initialize_dynamic(obj)
    254     continue
    255 if not len(dmap):
--> 256     dmap[dmap._initial_key()]

File /usr/share/miniconda3/envs/kerchunk-cookbook/lib/python3.10/site-packages/holoviews/core/spaces.py:1212, in DynamicMap.__getitem__(self, key)
   1210 # Not a cross product and nothing cached so compute element.
   1211 if cache is not None: return cache
-> 1212 val = self._execute_callback(*tuple_key)
   1213 if data_slice:
   1214     val = self._dataslice(val, data_slice)

File /usr/share/miniconda3/envs/kerchunk-cookbook/lib/python3.10/site-packages/holoviews/core/spaces.py:979, in DynamicMap._execute_callback(self, *args)
    976     kwargs['_memoization_hash_'] = hash_items
    978 with dynamicmap_memoization(self.callback, self.streams):
--> 979     retval = self.callback(*args, **kwargs)
    980 return self._style(retval)

File /usr/share/miniconda3/envs/kerchunk-cookbook/lib/python3.10/site-packages/holoviews/core/spaces.py:579, in Callable.__call__(self, *args, **kwargs)
    576     args, kwargs = (), dict(pos_kwargs, **kwargs)
    578 try:
--> 579     ret = self.callable(*args, **kwargs)
    580 except KeyError:
    581     # KeyError is caught separately because it is used to signal
    582     # invalid keys on DynamicMap and should not warn
    583     raise

File /usr/share/miniconda3/envs/kerchunk-cookbook/lib/python3.10/site-packages/holoviews/util/__init__.py:1024, in Dynamic._dynamic_operation.<locals>.dynamic_operation(*key, **kwargs)
   1023 def dynamic_operation(*key, **kwargs):
-> 1024     key, obj = resolve(key, kwargs)
   1025     return apply(obj, *key, **kwargs)

File /usr/share/miniconda3/envs/kerchunk-cookbook/lib/python3.10/site-packages/holoviews/util/__init__.py:1013, in Dynamic._dynamic_operation.<locals>.resolve(key, kwargs)
   1011 elif isinstance(map_obj, DynamicMap) and map_obj._posarg_keys and not key:
   1012     key = tuple(kwargs[k] for k in map_obj._posarg_keys)
-> 1013 return key, map_obj[key]

File /usr/share/miniconda3/envs/kerchunk-cookbook/lib/python3.10/site-packages/holoviews/core/spaces.py:1212, in DynamicMap.__getitem__(self, key)
   1210 # Not a cross product and nothing cached so compute element.
   1211 if cache is not None: return cache
-> 1212 val = self._execute_callback(*tuple_key)
   1213 if data_slice:
   1214     val = self._dataslice(val, data_slice)

File /usr/share/miniconda3/envs/kerchunk-cookbook/lib/python3.10/site-packages/holoviews/core/spaces.py:979, in DynamicMap._execute_callback(self, *args)
    976     kwargs['_memoization_hash_'] = hash_items
    978 with dynamicmap_memoization(self.callback, self.streams):
--> 979     retval = self.callback(*args, **kwargs)
    980 return self._style(retval)

File /usr/share/miniconda3/envs/kerchunk-cookbook/lib/python3.10/site-packages/holoviews/core/spaces.py:579, in Callable.__call__(self, *args, **kwargs)
    576     args, kwargs = (), dict(pos_kwargs, **kwargs)
    578 try:
--> 579     ret = self.callable(*args, **kwargs)
    580 except KeyError:
    581     # KeyError is caught separately because it is used to signal
    582     # invalid keys on DynamicMap and should not warn
    583     raise

File /usr/share/miniconda3/envs/kerchunk-cookbook/lib/python3.10/site-packages/holoviews/util/__init__.py:1024, in Dynamic._dynamic_operation.<locals>.dynamic_operation(*key, **kwargs)
   1023 def dynamic_operation(*key, **kwargs):
-> 1024     key, obj = resolve(key, kwargs)
   1025     return apply(obj, *key, **kwargs)

File /usr/share/miniconda3/envs/kerchunk-cookbook/lib/python3.10/site-packages/holoviews/util/__init__.py:1013, in Dynamic._dynamic_operation.<locals>.resolve(key, kwargs)
   1011 elif isinstance(map_obj, DynamicMap) and map_obj._posarg_keys and not key:
   1012     key = tuple(kwargs[k] for k in map_obj._posarg_keys)
-> 1013 return key, map_obj[key]

File /usr/share/miniconda3/envs/kerchunk-cookbook/lib/python3.10/site-packages/holoviews/core/spaces.py:1212, in DynamicMap.__getitem__(self, key)
   1210 # Not a cross product and nothing cached so compute element.
   1211 if cache is not None: return cache
-> 1212 val = self._execute_callback(*tuple_key)
   1213 if data_slice:
   1214     val = self._dataslice(val, data_slice)

File /usr/share/miniconda3/envs/kerchunk-cookbook/lib/python3.10/site-packages/holoviews/core/spaces.py:979, in DynamicMap._execute_callback(self, *args)
    976     kwargs['_memoization_hash_'] = hash_items
    978 with dynamicmap_memoization(self.callback, self.streams):
--> 979     retval = self.callback(*args, **kwargs)
    980 return self._style(retval)

File /usr/share/miniconda3/envs/kerchunk-cookbook/lib/python3.10/site-packages/holoviews/core/spaces.py:579, in Callable.__call__(self, *args, **kwargs)
    576     args, kwargs = (), dict(pos_kwargs, **kwargs)
    578 try:
--> 579     ret = self.callable(*args, **kwargs)
    580 except KeyError:
    581     # KeyError is caught separately because it is used to signal
    582     # invalid keys on DynamicMap and should not warn
    583     raise

File /usr/share/miniconda3/envs/kerchunk-cookbook/lib/python3.10/site-packages/holoviews/util/__init__.py:1025, in Dynamic._dynamic_operation.<locals>.dynamic_operation(*key, **kwargs)
   1023 def dynamic_operation(*key, **kwargs):
   1024     key, obj = resolve(key, kwargs)
-> 1025     return apply(obj, *key, **kwargs)

File /usr/share/miniconda3/envs/kerchunk-cookbook/lib/python3.10/site-packages/holoviews/util/__init__.py:1017, in Dynamic._dynamic_operation.<locals>.apply(element, *key, **kwargs)
   1015 def apply(element, *key, **kwargs):
   1016     kwargs = dict(util.resolve_dependent_kwargs(self.p.kwargs), **kwargs)
-> 1017     processed = self._process(element, key, kwargs)
   1018     if (self.p.link_dataset and isinstance(element, Dataset) and
   1019         isinstance(processed, Dataset) and processed._dataset is None):
   1020         processed._dataset = element.dataset

File /usr/share/miniconda3/envs/kerchunk-cookbook/lib/python3.10/site-packages/holoviews/util/__init__.py:1001, in Dynamic._process(self, element, key, kwargs)
    999     return self.p.operation.process_element(element, key, **kwargs)
   1000 else:
-> 1001     return self.p.operation(element, **kwargs)

File /usr/share/miniconda3/envs/kerchunk-cookbook/lib/python3.10/site-packages/hvplot/converter.py:1201, in HoloViewsConverter.__call__.<locals>.method_wrapper(ds, x, y)
   1200 def method_wrapper(ds, x, y):
-> 1201     el = method(x, y, data=ds.data)
   1202     el._transforms = dataset._transforms
   1203     el._dataset = ds

File /usr/share/miniconda3/envs/kerchunk-cookbook/lib/python3.10/site-packages/hvplot/converter.py:2104, in HoloViewsConverter.image(self, x, y, z, data)
   2102 element = self._get_element('image')
   2103 if self.geo: params['crs'] = self.crs
-> 2104 return (element(data, [x, y], z, **params).redim(**redim)
   2105         .apply(self._set_backends_opts, cur_opts=cur_opts, compat_opts=compat_opts))

File /usr/share/miniconda3/envs/kerchunk-cookbook/lib/python3.10/site-packages/holoviews/element/raster.py:310, in Image.__init__(self, data, kdims, vdims, bounds, extents, xdensity, ydensity, rtol, **params)
    308     xdensity = xdensity if xdensity else util.compute_density(l, r, dim1, self._time_unit)
    309     ydensity = ydensity if ydensity else util.compute_density(b, t, dim2, self._time_unit)
--> 310 SheetCoordinateSystem.__init__(self, bounds, xdensity, ydensity)
    311 if non_finite:
    312    self.bounds = BoundingBox(points=((np.nan, np.nan), (np.nan, np.nan)))

File /usr/share/miniconda3/envs/kerchunk-cookbook/lib/python3.10/site-packages/holoviews/core/sheetcoords.py:166, in SheetCoordinateSystem.__init__(self, bounds, xdensity, ydensity)
    162 self.__set_ydensity(ydensity or xdensity)
    164 self.lbrt = np.array(bounds.lbrt())
--> 166 r1,r2,c1,c2 = Slice._boundsspec2slicespec(self.lbrt,self)
    167 self.__shape = (r2-r1,c2-c1)

File /usr/share/miniconda3/envs/kerchunk-cookbook/lib/python3.10/site-packages/holoviews/core/sheetcoords.py:518, in Slice._boundsspec2slicespec(boundsspec, scs)
    515 t_m,l_m = scs.sheet2matrix(l,t)
    516 b_m,r_m = scs.sheet2matrix(r,b)
--> 518 l_idx = int(np.ceil(l_m-0.5))
    519 t_idx = int(np.ceil(t_m-0.5))
    520 r_idx = int(np.floor(r_m+0.5))

ValueError: cannot convert float NaN to integer
:DynamicMap   [time]