Skip to content
This repository has been archived by the owner on Apr 30, 2021. It is now read-only.

Trigger errors when coordinates don't match #46

Closed
matt-long opened this issue Jan 31, 2019 · 7 comments · Fixed by #64
Closed

Trigger errors when coordinates don't match #46

matt-long opened this issue Jan 31, 2019 · 7 comments · Fixed by #64

Comments

@matt-long
Copy link
Contributor

matt-long commented Jan 31, 2019

I have encountered issues operating on files with lossy compression applied to coordinates. In these cases, xarray can do the wrong thing when applying its logic for coordinate alignment.

If the coordinates don't match, it takes the 'inner' set by default:

>>> da1 = xr.DataArray(np.ones(4), dims=('x'), coords={'x': [1.,2.,3.,4.]})
>>> da2 = xr.DataArray(2*np.ones(4), dims=('x'), coords={'x': [1.,2.,3.,4.01]})
>>> da1 * da2
<xarray.DataArray (x: 3)>
array([2., 2., 2.])
Coordinates:
  * x        (x) float64 1.0 2.0 3.0

This is seldom desired in the applications I am familiar with.

We could therefore wrap all instances where we are relying on automatic alignment in a context manager as follows.

with xr.set_options(arithmetic_join='exact'):
   dao = da1 * da2

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-19-4bb80efa7a54> in <module>()
      9 with xr.set_options(arithmetic_join='exact'):
---> 10     dao = da1 * da2

/glade/work/mclong/miniconda3/envs/py3/lib/python3.6/site-packages/xarray/core/dataarray.py in func(self, other)
   1965                 align_type = (OPTIONS['arithmetic_join']
   1966                               if join is None else join)
-> 1967                 self, other = align(self, other, join=align_type, copy=False)
   1968             other_variable = getattr(other, 'variable', other)
   1969             other_coords = getattr(other, 'coords', None)

/glade/work/mclong/miniconda3/envs/py3/lib/python3.6/site-packages/xarray/core/alignment.py in align(*objects, **kwargs)
    130                     raise ValueError(
    131                         'indexes along dimension {!r} are not equal'
--> 132                         .format(dim))
    133                 index = joiner(matching_indexes)
    134                 joined_indexes[dim] = index

ValueError: indexes along dimension 'x' are not equal
@matt-long
Copy link
Contributor Author

cc @klindsay28

@matt-long
Copy link
Contributor Author

@andersy005, I am reopening this issue. I am concerned that the implementation we chose in #54 sets xarray options globally, so that on import of esmlab, a user will get different behavior from xarray than its defaults. While I think the xarray settings are not the best choice for defaults, I don't think we should be mucking around with these settings on the user's behalf, but rather confine our application of the non-default to within esmlab.

How can we do this without wrapping every computation in a context manager?

One option is to define a function decorator:

from contextlib import ContextDecorator

class xarray_setoptions(ContextDecorator):
    def __init__(self, **kwargs):
        self.old = xr.set_options(**kwargs).old
    def __enter__(self):
        return self
    def __exit__(self, *exc):
        xr.set_options(**self.old)
        return

@xarray_setoptions(arithmetic_join="exact")
def compute_mon_climatology(dset, time_coord_name=None):
    # computation

This seems like a nice way to implement this feature at the function level, but it's not Python 2 compatible. We could put

xr_settings_old = xr.set_options(arithmetic_join="exact").old

at the top of every function, and

xr.set_options(**self.old)

at the bottom.

@mnlevy1981, we should discuss plans for marbl-diags; at present the esmlab dependencies are modest, so we might consider dropping py2 support here sooner rather than later.

@mnlevy1981
Copy link
Contributor

@matt-long I think marbl-diags will need to use an updated esmlab if that's where we decide to put the zonal-averaging code... otherwise I'm happy to stick with an older release until the post-processing scripts are py3-compatible. What happens with the code snippet above in python 2? Does it throw an error, or does it just ignore the function decorator?

@matt-long
Copy link
Contributor Author

I don't think ContextDecorator is defined in python 2, so we could protect the class definition from a python 2 invocation. Not sure what it would do with the decorator...but we would not get the desired
settings.

@mnlevy1981
Copy link
Contributor

If we could protect the class definition from python 2, I'd be okay with having marbl-diags define xarray settings before calling esmlab (I think that would work without altering xarray behavior from other calls)

@matt-long
Copy link
Contributor Author

@andersy005, I think Mike's suggestion is a good one, if the decorator is ignored. There would have to be a dummy object in py2 to import into the computational modules so as to keep these clean of version checks.

@andersy005
Copy link
Contributor

@mnlevy1981, there's a contextlib backport module for python2 (https://github.com/jazzband/contextlib2). So I used the approach in #46 (comment) and this should work for both Python2 and Python3

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants