holoviz / geoviews

Simple, concise geographical visualization in Python
http://geoviews.org
BSD 3-Clause "New" or "Revised" License
590 stars 76 forks source link

plot dataset with 2D coordinate variables #57

Closed rabernat closed 6 years ago

rabernat commented 7 years ago

I have an xarray dataset with the following structure:

<xarray.Dataset>
Dimensions:   (face: 13, i: 4320, j: 4320,  k: 90, time: 1)
Coordinates:
  * k         (k) int64 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ...
  * j         (j) float64 0.0 1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0 10.0 11.0 ...
  * i         (i) int64 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ...
  * face      (face) int64 0 1 2 3 4 5 6 7 8 9 10 11 12
    iter      (time) int64 487152
  * time      (time) int64 487152
    XC        (face, j, i) >f4 -114.932 -114.932 -114.932 -114.932 -114.932 ...
    YC        (face, j, i) >f4 -88.1771 -88.1801 -88.183 -88.1859 -88.1888 ...
    Z         (k) >f4 -0.5 -1.57 -2.79 -4.185 -5.78 -7.595 -9.66 -12.01 ...
Data variables:
    Theta     (time, k, face, j, i) >f4 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ...
    Salt      (time, k, face, j, i) >f4 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ...

This is an enormous dataset from a ocean simulation using a faceted curvilinear grid. The longitudes latitudes, and depths of the data are encoded by the variables 'XC' and 'YC'.

I am trying, unsuccessfully, to use geoviews to visualize this data. Here is what I am doing:

import geoviews as gv
gvds_from_xr = gv.Dataset(ds, kdims=['XC', 'YC', 'Z', 'time'], vdims=['Theta', 'Salt'])
gvds_from_xr.to(gv.Image, ['XC', 'YC'])

This raises the error

ValueError: dimensions or multi-index levels ['Z'] do not exist

If I try

gvds_from_xr = gv.Dataset(ds, kdims=['XC', 'YC', 'k', 'time'], vdims=['Theta', 'Salt'])
gvds_from_xr.to(gv.Image, ['XC', 'YC'])

(replacing physical dimension 'Z' with logical dimension 'k') I get the following stack trace:

ValueErrorTraceback (most recent call last)
<ipython-input-27-02d44a5c1c08> in <module>()
      2 get_ipython().magic(u"time gvds_from_xr = gv.Dataset(ds, kdims=['XC', 'YC', 'k', 'time'], vdims=['Theta', 'Salt'])")
      3 print(gvds_from_xr)
----> 4 get_ipython().magic(u"time gvds_from_xr.to(gv.Image, ['XC', 'YC'])")

/home/rpa/.conda/envs/lagrangian_vorticity/lib/python2.7/site-packages/IPython/core/interactiveshell.pyc in magic(self, arg_s)
   2156         magic_name, _, magic_arg_s = arg_s.partition(' ')
   2157         magic_name = magic_name.lstrip(prefilter.ESC_MAGIC)
-> 2158         return self.run_line_magic(magic_name, magic_arg_s)
   2159 
   2160     #-------------------------------------------------------------------------

/home/rpa/.conda/envs/lagrangian_vorticity/lib/python2.7/site-packages/IPython/core/interactiveshell.pyc in run_line_magic(self, magic_name, line)
   2077                 kwargs['local_ns'] = sys._getframe(stack_depth).f_locals
   2078             with self.builtin_trap:
-> 2079                 result = fn(*args,**kwargs)
   2080             return result
   2081 

<decorator-gen-59> in time(self, line, cell, local_ns)

/home/rpa/.conda/envs/lagrangian_vorticity/lib/python2.7/site-packages/IPython/core/magic.pyc in <lambda>(f, *a, **k)
    186     # but it's overkill for just that one bit of state.
    187     def magic_deco(arg):
--> 188         call = lambda f, *a, **k: f(*a, **k)
    189 
    190         if callable(arg):

/home/rpa/.conda/envs/lagrangian_vorticity/lib/python2.7/site-packages/IPython/core/magics/execution.pyc in time(self, line, cell, local_ns)
   1174         if mode=='eval':
   1175             st = clock2()
-> 1176             out = eval(code, glob, local_ns)
   1177             end = clock2()
   1178         else:

<timed eval> in <module>()

/home/rpa/.conda/envs/lagrangian_vorticity/lib/python2.7/site-packages/geoviews/element/__init__.pyc in __call__(self, *args, **kwargs)
     24         if 'crs' not in kwargs and issubclass(group_type, _Element):
     25             kwargs['crs'] = self._element.crs
---> 26         return super(GeoConversion, self).__call__(*args, **kwargs)
     27 
     28     def linecontours(self, kdims=None, vdims=None, mdims=None, **kwargs):

/home/rpa/.conda/envs/lagrangian_vorticity/lib/python2.7/site-packages/holoviews/core/data/__init__.pyc in __call__(self, new_type, kdims, vdims, mdims, sort, **kwargs)
     92             return element.sort() if sort else element
     93         group = selected.groupby(mdims, container_type=HoloMap,
---> 94                                  group_type=new_type, **params)
     95         if sort:
     96             return group.map(lambda x: x.sort(), [new_type])

/home/rpa/.conda/envs/lagrangian_vorticity/lib/python2.7/site-packages/holoviews/core/data/__init__.pyc in groupby(self, dimensions, container_type, group_type, dynamic, **kwargs)
    409 
    410         return self.interface.groupby(self, dim_names, container_type,
--> 411                                       group_type, **kwargs)
    412 
    413     def __len__(self):

/home/rpa/.conda/envs/lagrangian_vorticity/lib/python2.7/site-packages/holoviews/core/data/xarray.pyc in groupby(cls, dataset, dimensions, container_type, group_type, **kwargs)
    110             data = [(k, group_type(dataset.data.sel(**dict(zip(dimensions, k))),
    111                                    **group_kwargs))
--> 112                     for k in indexes]
    113 
    114         if issubclass(container_type, NdMapping):

/home/rpa/.conda/envs/lagrangian_vorticity/lib/python2.7/site-packages/geoviews/element/geo.pyc in __init__(self, data, **kwargs)
     83         elif crs:
     84             kwargs['crs'] = crs
---> 85         super(_Element, self).__init__(data, **kwargs)
     86 
     87 

/home/rpa/.conda/envs/lagrangian_vorticity/lib/python2.7/site-packages/holoviews/element/raster.pyc in __init__(self, data, **params)
    657 
    658     def __init__(self, data, **params):
--> 659         super(GridImage, self).__init__(data, **params)
    660         (l, r), (b, t) = self.interface.range(self, 0), self.interface.range(self, 1)
    661         (ys, xs) = self.dimension_values(2, flat=False).shape

/home/rpa/.conda/envs/lagrangian_vorticity/lib/python2.7/site-packages/holoviews/core/data/__init__.pyc in __init__(self, data, **kwargs)
    135                                            datatype=kwargs.get('datatype'))
    136         (data, self.interface, dims, extra_kws) = initialized
--> 137         super(Dataset, self).__init__(data, **dict(extra_kws, **dict(kwargs, **dims)))
    138         self.interface.validate(self)
    139 

/home/rpa/.conda/envs/lagrangian_vorticity/lib/python2.7/site-packages/holoviews/core/dimension.pyc in __init__(self, data, **params)
    593                                   for d in params.pop(group)]
    594                 params[group] = dimensions
--> 595         super(Dimensioned, self).__init__(data, **params)
    596         self.ndims = len(self.kdims)
    597         cdims = [(d.name, val) for d, val in self.cdims.items()]

/home/rpa/.conda/envs/lagrangian_vorticity/lib/python2.7/site-packages/holoviews/core/dimension.pyc in __init__(self, data, id, **params)
    297             params['group'] = long_name
    298 
--> 299         super(LabelledData, self).__init__(**params)
    300         if not group_sanitizer.allowable(self.group):
    301             raise ValueError("Supplied group %r contains invalid characters." %

/home/rpa/.conda/envs/lagrangian_vorticity/lib/python2.7/site-packages/param/parameterized.pyc in __init__(self, **params)
   1039         self.__generate_name()
   1040 
-> 1041         self._setup_params(**params)
   1042         object_count += 1
   1043 

/home/rpa/.conda/envs/lagrangian_vorticity/lib/python2.7/site-packages/param/parameterized.pyc in override_initialization(parameterized_instance, *args, **kw)
    976         original_initialized=parameterized_instance.initialized
    977         parameterized_instance.initialized=False
--> 978         fn(parameterized_instance,*args,**kw)
    979         parameterized_instance.initialized=original_initialized
    980     return override_initialization

/home/rpa/.conda/envs/lagrangian_vorticity/lib/python2.7/site-packages/param/parameterized.pyc in _setup_params(self, **params)
   1439                 self.warning("Setting non-parameter attribute %s=%s using a mechanism intended only for parameters",name,val)
   1440             # i.e. if not desc it's setting an attribute in __dict__, not a Parameter
-> 1441             setattr(self,name,val)
   1442 
   1443 

/home/rpa/.conda/envs/lagrangian_vorticity/lib/python2.7/site-packages/param/__init__.pyc in __set__(self, obj, val)
   1151     def __set__(self,obj,val):
   1152         """Set to the given value, raising an exception if out of bounds."""
-> 1153         self._check_bounds(val)
   1154         super(List,self).__set__(obj,val)
   1155 

/home/rpa/.conda/envs/lagrangian_vorticity/lib/python2.7/site-packages/param/__init__.pyc in _check_bounds(self, val)
   1170             if min_length is not None and max_length is not None:
   1171                 if not (min_length <= l <= max_length):
-> 1172                     raise ValueError("%s: list length must be between %s and %s (inclusive)"%(self._attrib_name,min_length,max_length))
   1173             elif min_length is not None:
   1174                 if not min_length <= l:

ValueError: vdims: list length must be between 1 and 1 (inclusive)

Clearly I am doing something wrong, but I can't figure out what.

Thanks for any help you can provide.

philippjfr commented 7 years ago

Hi @rabernat, the issue is that the to interface eagerly populates the value dimensions of the gv.Image with all available value dimensions, and the Image is complaining because it's only expecting a single value dimension. I'll think about providing better exception messages for this case but the straightforward fix should be this:

gvds_from_xr = gv.Dataset(ds, kdims=['XC', 'YC', 'k', 'time'], vdims=['Theta', 'Salt'])
gvds_from_xr.to(gv.Image, ['XC', 'YC'], 'Theta', dynamic=True)

Here we explicitly told it that the Image should show the Theta values of your dataset, you can of course visualize Salt in the same way. If your dataset is really large I'd also strongly recommend using dynamic=True in this case which will ensure it lazily loads your data. Unlike the non-dynamic case this won't auto-compute a global normalization range for your images so if you want you can use redim to set an absolute range:

gvds_from_xr.to(gv.Image, ['XC', 'YC'], 'Theta', dynamic=True).redim(Theta=dict(range=(0,100)))

Let me know if you have any more questions we're usually available on Gitter.

rabernat commented 7 years ago

Thanks for your quick reply! Your code worked, but I hit a new problem when trying to actually view the map. I will follow up on gitter.

rabernat commented 7 years ago

Just for completeness, here is the error I get when I print that last line:

AttributeErrorTraceback (most recent call last)
/home/rpa/.conda/envs/lagrangian_vorticity/lib/python2.7/site-packages/IPython/core/formatters.pyc in __call__(self, obj)
    305                 pass
    306             else:
--> 307                 return printer(obj)
    308             # Finally look for special method names
    309             method = get_real_method(obj, self.print_method)

/home/rpa/.conda/envs/lagrangian_vorticity/lib/python2.7/site-packages/holoviews/ipython/display_hooks.pyc in pprint_display(obj)
    214     if not ip.display_formatter.formatters['text/plain'].pprint:
    215         return None
--> 216     return display(obj, raw=True)
    217 
    218 

/home/rpa/.conda/envs/lagrangian_vorticity/lib/python2.7/site-packages/holoviews/ipython/display_hooks.pyc in display(obj, raw, **kwargs)
    200         html = layout_display(obj)
    201     elif isinstance(obj, (HoloMap, DynamicMap)):
--> 202         html = map_display(obj)
    203     else:
    204         return repr(obj) if raw else IPython.display.display(obj, **kwargs)

/home/rpa/.conda/envs/lagrangian_vorticity/lib/python2.7/site-packages/holoviews/ipython/display_hooks.pyc in wrapped(element)
    101             html = fn(element,
    102                       max_frames=OutputMagic.options['max_frames'],
--> 103                       max_branches = OutputMagic.options['max_branches'])
    104 
    105             # Only want to add to the archive for one display hook...

/home/rpa/.conda/envs/lagrangian_vorticity/lib/python2.7/site-packages/holoviews/ipython/display_hooks.pyc in map_display(vmap, max_frames, max_branches)
    154         return sanitize_HTML(vmap)
    155 
--> 156     return render(vmap)
    157 
    158 

/home/rpa/.conda/envs/lagrangian_vorticity/lib/python2.7/site-packages/holoviews/ipython/display_hooks.pyc in render(obj, **kwargs)
     49 
     50     backend = Store.current_backend
---> 51     return Store.renderers[backend].html(obj, **kwargs)
     52 
     53 

/home/rpa/.conda/envs/lagrangian_vorticity/lib/python2.7/site-packages/holoviews/plotting/renderer.pyc in html(self, obj, fmt, css)
    248         Renders plot or data structure and wraps the output in HTML.
    249         """
--> 250         plot, fmt =  self._validate(obj, fmt)
    251         figdata, _ = self(plot, fmt)
    252         if css is None: css = self.css

/home/rpa/.conda/envs/lagrangian_vorticity/lib/python2.7/site-packages/holoviews/plotting/renderer.pyc in _validate(self, obj, fmt)
    188         if isinstance(obj, tuple(self.widgets.values())):
    189             return obj, 'html'
--> 190         plot = self.get_plot(obj)
    191 
    192         fig_formats = self.mode_formats['fig'][self.mode]

/home/rpa/.conda/envs/lagrangian_vorticity/lib/python2.7/site-packages/holoviews/plotting/renderer.pyc in get_plot(self_or_cls, obj)
    164                 continue
    165             if dmap.call_mode == 'key':
--> 166                 dmap[dmap._initial_key()]
    167             else:
    168                 try:

/home/rpa/.conda/envs/lagrangian_vorticity/lib/python2.7/site-packages/holoviews/core/spaces.pyc in __getitem__(self, key)
    669         # Not a cross product and nothing cached so compute element.
    670         if cache: return cache
--> 671         val = self._execute_callback(*tuple_key)
    672         if self.call_mode == 'counter':
    673             val = val[1]

/home/rpa/.conda/envs/lagrangian_vorticity/lib/python2.7/site-packages/holoviews/core/spaces.pyc in _execute_callback(self, *args)
    542             retval = next(self.callback)
    543         else:
--> 544             retval = self.callback(*args)
    545 
    546         if self.call_mode=='key':

/home/rpa/.conda/envs/lagrangian_vorticity/lib/python2.7/site-packages/holoviews/core/data/__init__.pyc in load_subset(*args)
    403                     return group_type(([group],), group=self.group,
    404                                       label=self.label, vdims=self.vdims)
--> 405                 return group_type(group.reindex(group_dims), **group_kwargs)
    406             dynamic_dims = [d(values=list(self.interface.values(self, d.name, False)))
    407                             for d in dimensions]

/home/rpa/.conda/envs/lagrangian_vorticity/lib/python2.7/site-packages/geoviews/element/geo.pyc in __init__(self, data, **kwargs)
     83         elif crs:
     84             kwargs['crs'] = crs
---> 85         super(_Element, self).__init__(data, **kwargs)
     86 
     87 

/home/rpa/.conda/envs/lagrangian_vorticity/lib/python2.7/site-packages/holoviews/element/raster.pyc in __init__(self, data, **params)
    658     def __init__(self, data, **params):
    659         super(GridImage, self).__init__(data, **params)
--> 660         (l, r), (b, t) = self.interface.range(self, 0), self.interface.range(self, 1)
    661         (ys, xs) = self.dimension_values(2, flat=False).shape
    662         xsampling = (float(r-l)/(xs-1))/2.

/home/rpa/.conda/envs/lagrangian_vorticity/lib/python2.7/site-packages/holoviews/core/data/xarray.pyc in range(cls, dataset, dimension)
     80             data = dataset.data[dim]
     81             dmin, dmax = data.min().data, data.max().data
---> 82             dmin = dmin if np.isscalar(dmin) else dmin.item()
     83             dmax = dmax if np.isscalar(dmax) else dmax.item()
     84             return dmin, dmax

AttributeError: 'Array' object has no attribute 'item'

Out[41]:
:DynamicMap   [k,time]
philippjfr commented 7 years ago

@rabernat We have now released both HoloViews 1.7.0 and GeoViews 1.2.0 and are now focusing on improving the documentation. Could you try updating to the latest versions using:

conda install -c ioam -c conda-forge holoviews geoviews

And report back if everything is working for you?

philippjfr commented 6 years ago

hv/gv.Dataset now natively understands datasets with multi-dimensional coordinates and they can now be plotted with a QuadMesh. Closing.