Skip to content

Global option to always keep/discard attrs on operations #2482

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Oct 30, 2018
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion doc/faq.rst
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ conventions`_. (An exception is serialization to and from netCDF files.)

An implication of this choice is that we do not propagate ``attrs`` through
most operations unless explicitly flagged (some methods have a ``keep_attrs``
option). Similarly, xarray does not check for conflicts between ``attrs`` when
option, and there is a global flag for setting this to be always True or
False). Similarly, xarray does not check for conflicts between ``attrs`` when
combining arrays and datasets, unless explicitly requested with the option
``compat='identical'``. The guiding principle is that metadata should not be
allowed to get in the way.
Expand Down
16 changes: 10 additions & 6 deletions xarray/core/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from .arithmetic import SupportsArithmetic
from .pycompat import OrderedDict, basestring, dask_array_type, suppress
from .utils import Frozen, ReprObject, SortedKeysDict, either_dict_or_kwargs
from .options import _set_keep_attrs

# Used as a sentinel value to indicate a all dimensions
ALL_DIMS = ReprObject('<all-dims>')
Expand All @@ -21,12 +22,12 @@ class ImplementsArrayReduce(object):
def _reduce_method(cls, func, include_skipna, numeric_only):
if include_skipna:
def wrapped_func(self, dim=None, axis=None, skipna=None,
keep_attrs=False, **kwargs):
keep_attrs=_set_keep_attrs(False), **kwargs):
return self.reduce(func, dim, axis, keep_attrs=keep_attrs,
skipna=skipna, allow_lazy=True, **kwargs)
else:
def wrapped_func(self, dim=None, axis=None, keep_attrs=False,
**kwargs):
def wrapped_func(self, dim=None, axis=None,
keep_attrs=_set_keep_attrs(False), **kwargs):
return self.reduce(func, dim, axis, keep_attrs=keep_attrs,
allow_lazy=True, **kwargs)
return wrapped_func
Expand All @@ -51,13 +52,15 @@ class ImplementsDatasetReduce(object):
@classmethod
def _reduce_method(cls, func, include_skipna, numeric_only):
if include_skipna:
def wrapped_func(self, dim=None, keep_attrs=False, skipna=None,
def wrapped_func(self, dim=None,
keep_attrs=_set_keep_attrs(False), skipna=None,
**kwargs):
return self.reduce(func, dim, keep_attrs, skipna=skipna,
numeric_only=numeric_only, allow_lazy=True,
**kwargs)
else:
def wrapped_func(self, dim=None, keep_attrs=False, **kwargs):
def wrapped_func(self, dim=None,
keep_attrs=_set_keep_attrs(False), **kwargs):
return self.reduce(func, dim, keep_attrs,
numeric_only=numeric_only, allow_lazy=True,
**kwargs)
Expand Down Expand Up @@ -590,7 +593,8 @@ def rolling(self, dim=None, min_periods=None, center=False, **dim_kwargs):
center=center)

def resample(self, freq=None, dim=None, how=None, skipna=None,
closed=None, label=None, base=0, keep_attrs=False, **indexer):
closed=None, label=None, base=0,
keep_attrs=_set_keep_attrs(False), **indexer):
"""Returns a Resample object for performing resampling operations.

Handles both downsampling and upsampling. If any intervals contain no
Expand Down
10 changes: 6 additions & 4 deletions xarray/core/dataarray.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
assert_coordinate_consistent, remap_label_indexers)
from .dataset import Dataset, merge_indexes, split_indexes
from .formatting import format_item
from .options import OPTIONS
from .options import OPTIONS, _set_keep_attrs
from .pycompat import OrderedDict, basestring, iteritems, range, zip
from .utils import (
decode_numpy_dict_values, either_dict_or_kwargs, ensure_us_time_resolution)
Expand Down Expand Up @@ -1559,7 +1559,8 @@ def combine_first(self, other):
"""
return ops.fillna(self, other, join="outer")

def reduce(self, func, dim=None, axis=None, keep_attrs=False, **kwargs):
def reduce(self, func, dim=None, axis=None,
keep_attrs=_set_keep_attrs(False), **kwargs):
"""Reduce this array by applying `func` along some dimension(s).

Parameters
Expand Down Expand Up @@ -2270,7 +2271,8 @@ def sortby(self, variables, ascending=True):
ds = self._to_temp_dataset().sortby(variables, ascending=ascending)
return self._from_temp_dataset(ds)

def quantile(self, q, dim=None, interpolation='linear', keep_attrs=False):
def quantile(self, q, dim=None, interpolation='linear',
keep_attrs=_set_keep_attrs(False)):
"""Compute the qth quantile of the data along the specified dimension.

Returns the qth quantiles(s) of the array elements.
Expand Down Expand Up @@ -2316,7 +2318,7 @@ def quantile(self, q, dim=None, interpolation='linear', keep_attrs=False):
q, dim=dim, keep_attrs=keep_attrs, interpolation=interpolation)
return self._from_temp_dataset(ds)

def rank(self, dim, pct=False, keep_attrs=False):
def rank(self, dim, pct=False, keep_attrs=_set_keep_attrs(False)):
"""Ranks the data.

Equal values are assigned a rank that is the average of the ranks that
Expand Down
18 changes: 10 additions & 8 deletions xarray/core/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
from .merge import (
dataset_merge_method, dataset_update_method, merge_data_and_coords,
merge_variables)
from .options import OPTIONS
from .options import OPTIONS, _set_keep_attrs
from .pycompat import (
OrderedDict, basestring, dask_array_type, integer_types, iteritems, range)
from .utils import (
Expand Down Expand Up @@ -2870,7 +2870,7 @@ def combine_first(self, other):
out = ops.fillna(self, other, join="outer", dataset_join="outer")
return out

def reduce(self, func, dim=None, keep_attrs=False, numeric_only=False,
def reduce(self, func, dim=None, keep_attrs=_set_keep_attrs(False), numeric_only=False,
allow_lazy=False, **kwargs):
"""Reduce this dataset by applying `func` along some dimension(s).

Expand Down Expand Up @@ -2940,7 +2940,7 @@ def reduce(self, func, dim=None, keep_attrs=False, numeric_only=False,
attrs = self.attrs if keep_attrs else None
return self._replace_vars_and_dims(variables, coord_names, attrs=attrs)

def apply(self, func, keep_attrs=False, args=(), **kwargs):
def apply(self, func, keep_attrs=_set_keep_attrs(False), args=(), **kwargs):
"""Apply a function over the data variables in this dataset.

Parameters
Expand Down Expand Up @@ -3288,7 +3288,7 @@ def from_dict(cls, d):
return obj

@staticmethod
def _unary_op(f, keep_attrs=False):
def _unary_op(f, keep_attrs=_set_keep_attrs(False)):
@functools.wraps(f)
def func(self, *args, **kwargs):
ds = self.coords.to_dataset()
Expand Down Expand Up @@ -3649,7 +3649,7 @@ def sortby(self, variables, ascending=True):
return aligned_self.isel(**indices)

def quantile(self, q, dim=None, interpolation='linear',
numeric_only=False, keep_attrs=False):
numeric_only=False, keep_attrs=_set_keep_attrs(False)):
"""Compute the qth quantile of the data along the specified dimension.

Returns the qth quantiles(s) of the array elements for each variable
Expand Down Expand Up @@ -3735,7 +3735,7 @@ def quantile(self, q, dim=None, interpolation='linear',
new.coords['quantile'] = q
return new

def rank(self, dim, pct=False, keep_attrs=False):
def rank(self, dim, pct=False, keep_attrs=_set_keep_attrs(False)):
"""Ranks the data.

Equal values are assigned a rank that is the average of the ranks that
Expand Down Expand Up @@ -3838,11 +3838,13 @@ def differentiate(self, coord, edge_order=1, datetime_unit=None):

@property
def real(self):
return self._unary_op(lambda x: x.real, keep_attrs=True)(self)
return self._unary_op(lambda x: x.real,
keep_attrs=_set_keep_attrs(True))(self)

@property
def imag(self):
return self._unary_op(lambda x: x.imag, keep_attrs=True)(self)
return self._unary_op(lambda x: x.imag,
keep_attrs=_set_keep_attrs(True))(self)

def filter_by_attrs(self, **kwargs):
"""Returns a ``Dataset`` with variables that match specific conditions.
Expand Down
19 changes: 10 additions & 9 deletions xarray/core/groupby.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from .pycompat import integer_types, range, zip
from .utils import hashable, maybe_wrap_array, peek_at, safe_cast_to_index
from .variable import IndexVariable, Variable, as_variable
from .options import _set_keep_attrs


def unique_value_groups(ar, sort=True):
Expand Down Expand Up @@ -407,12 +408,12 @@ def _first_or_last(self, op, skipna, keep_attrs):
return self.reduce(op, self._group_dim, skipna=skipna,
keep_attrs=keep_attrs, allow_lazy=True)

def first(self, skipna=None, keep_attrs=True):
def first(self, skipna=None, keep_attrs=_set_keep_attrs(True)):
"""Return the first element of each group along the group dimension
"""
return self._first_or_last(duck_array_ops.first, skipna, keep_attrs)

def last(self, skipna=None, keep_attrs=True):
def last(self, skipna=None, keep_attrs=_set_keep_attrs(True)):
"""Return the last element of each group along the group dimension
"""
return self._first_or_last(duck_array_ops.last, skipna, keep_attrs)
Expand Down Expand Up @@ -538,8 +539,8 @@ def _combine(self, applied, shortcut=False):
combined = self._maybe_unstack(combined)
return combined

def reduce(self, func, dim=None, axis=None, keep_attrs=False,
shortcut=True, **kwargs):
def reduce(self, func, dim=None, axis=None,
keep_attrs=_set_keep_attrs(False), shortcut=True, **kwargs):
"""Reduce the items in this group by applying `func` along some
dimension(s).

Expand Down Expand Up @@ -589,12 +590,12 @@ def reduce_array(ar):
def _reduce_method(cls, func, include_skipna, numeric_only):
if include_skipna:
def wrapped_func(self, dim=DEFAULT_DIMS, axis=None, skipna=None,
keep_attrs=False, **kwargs):
keep_attrs=_set_keep_attrs(False), **kwargs):
return self.reduce(func, dim, axis, keep_attrs=keep_attrs,
skipna=skipna, allow_lazy=True, **kwargs)
else:
def wrapped_func(self, dim=DEFAULT_DIMS, axis=None,
keep_attrs=False, **kwargs):
keep_attrs=_set_keep_attrs(False), **kwargs):
return self.reduce(func, dim, axis, keep_attrs=keep_attrs,
allow_lazy=True, **kwargs)
return wrapped_func
Expand Down Expand Up @@ -650,7 +651,7 @@ def _combine(self, applied):
combined = self._maybe_unstack(combined)
return combined

def reduce(self, func, dim=None, keep_attrs=False, **kwargs):
def reduce(self, func, dim=None, keep_attrs=_set_keep_attrs(False), **kwargs):
"""Reduce the items in this group by applying `func` along some
dimension(s).

Expand Down Expand Up @@ -700,13 +701,13 @@ def reduce_dataset(ds):
@classmethod
def _reduce_method(cls, func, include_skipna, numeric_only):
if include_skipna:
def wrapped_func(self, dim=DEFAULT_DIMS, keep_attrs=False,
def wrapped_func(self, dim=DEFAULT_DIMS, keep_attrs=_set_keep_attrs(False),
skipna=None, **kwargs):
return self.reduce(func, dim, keep_attrs, skipna=skipna,
numeric_only=numeric_only, allow_lazy=True,
**kwargs)
else:
def wrapped_func(self, dim=DEFAULT_DIMS, keep_attrs=False,
def wrapped_func(self, dim=DEFAULT_DIMS, keep_attrs=_set_keep_attrs(False),
**kwargs):
return self.reduce(func, dim, keep_attrs,
numeric_only=numeric_only, allow_lazy=True,
Expand Down
7 changes: 4 additions & 3 deletions xarray/core/missing.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from .pycompat import iteritems
from .utils import OrderedSet, datetime_to_numeric, is_scalar
from .variable import Variable, broadcast_variables
from .options import _set_keep_attrs


class BaseInterpolator(object):
Expand Down Expand Up @@ -218,7 +219,7 @@ def interp_na(self, dim=None, use_coordinate=True, method='linear', limit=None,
output_dtypes=[self.dtype],
dask='parallelized',
vectorize=True,
keep_attrs=True).transpose(*self.dims)
keep_attrs=_set_keep_attrs(True)).transpose(*self.dims)

if limit is not None:
arr = arr.where(valids)
Expand Down Expand Up @@ -269,7 +270,7 @@ def ffill(arr, dim=None, limit=None):

return apply_ufunc(bn.push, arr,
dask='parallelized',
keep_attrs=True,
keep_attrs=_set_keep_attrs(True),
output_dtypes=[arr.dtype],
kwargs=dict(n=_limit, axis=axis)).transpose(*arr.dims)

Expand All @@ -283,7 +284,7 @@ def bfill(arr, dim=None, limit=None):

return apply_ufunc(_bfill, arr,
dask='parallelized',
keep_attrs=True,
keep_attrs=_set_keep_attrs(True),
output_dtypes=[arr.dtype],
kwargs=dict(n=_limit, axis=axis)).transpose(*arr.dims)

Expand Down
5 changes: 3 additions & 2 deletions xarray/core/ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from . import dtypes, duck_array_ops
from .nputils import array_eq, array_ne
from .pycompat import PY3
from .options import _set_keep_attrs

try:
import bottleneck as bn
Expand Down Expand Up @@ -153,7 +154,7 @@ def fillna(data, other, join="left", dataset_join="left"):
dask="allowed",
dataset_join=dataset_join,
dataset_fill_value=np.nan,
keep_attrs=True)
keep_attrs=_set_keep_attrs(True))


def where_method(self, cond, other=dtypes.NA):
Expand All @@ -179,7 +180,7 @@ def where_method(self, cond, other=dtypes.NA):
join=join,
dataset_join=join,
dask='allowed',
keep_attrs=True)
keep_attrs=_set_keep_attrs(True))


def _call_possibly_missing_method(arg, name, args, kwargs):
Expand Down
20 changes: 20 additions & 0 deletions xarray/core/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
FILE_CACHE_MAXSIZE = 'file_cache_maxsize'
CMAP_SEQUENTIAL = 'cmap_sequential'
CMAP_DIVERGENT = 'cmap_divergent'
KEEP_ATTRS = 'keep_attrs'


OPTIONS = {
DISPLAY_WIDTH: 80,
Expand All @@ -14,6 +16,7 @@
FILE_CACHE_MAXSIZE: 128,
CMAP_SEQUENTIAL: 'viridis',
CMAP_DIVERGENT: 'RdBu_r',
KEEP_ATTRS: 'default'
}

_JOIN_OPTIONS = frozenset(['inner', 'outer', 'left', 'right', 'exact'])
Expand All @@ -28,6 +31,7 @@ def _positive_integer(value):
ARITHMETIC_JOIN: _JOIN_OPTIONS.__contains__,
ENABLE_CFTIMEINDEX: lambda value: isinstance(value, bool),
FILE_CACHE_MAXSIZE: _positive_integer,
KEEP_ATTRS: lambda choice: choice in [True, False, 'default']
}


Expand All @@ -41,6 +45,17 @@ def _set_file_cache_maxsize(value):
}


def _set_keep_attrs(func_default):
global_choice = OPTIONS['keep_attrs']

if global_choice is 'default':
return func_default
elif global_choice in [True, False]:
return global_choice
else:
raise ValueError('The global option keep_attrs is set to an invalid value.')


class set_options(object):
"""Set options for xarray in a controlled context.

Expand All @@ -63,6 +78,11 @@ class set_options(object):
- ``cmap_divergent``: colormap to use for divergent data plots.
Default: ``RdBu_r``. If string, must be matplotlib built-in colormap.
Can also be a Colormap object (e.g. mpl.cm.magma)
- ``keep_attrs``: rule for whether to keep attributes on xarray
Datasets/dataarrays after operations. Either ``True`` to always keep
attrs, ``False`` to always discard them, or ``'default'`` to use original
logic that attrs should only be kept in unambiguous circumstances.
Default: ``'default'``.

f You can use ``set_options`` either as a context manager:

Expand Down
3 changes: 2 additions & 1 deletion xarray/core/resample.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from . import ops
from .groupby import DEFAULT_DIMS, DataArrayGroupBy, DatasetGroupBy
from .pycompat import OrderedDict, dask_array_type
from .options import _set_keep_attrs

RESAMPLE_DIM = '__resample_dim__'

Expand Down Expand Up @@ -273,7 +274,7 @@ def apply(self, func, **kwargs):

return combined.rename({self._resample_dim: self._dim})

def reduce(self, func, dim=None, keep_attrs=False, **kwargs):
def reduce(self, func, dim=None, keep_attrs=_set_keep_attrs(False), **kwargs):
"""Reduce the items in this group by applying `func` along the
pre-defined resampling dimension.

Expand Down
5 changes: 3 additions & 2 deletions xarray/core/variable.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from .pycompat import (
OrderedDict, basestring, dask_array_type, integer_types, zip)
from .utils import OrderedSet, either_dict_or_kwargs
from .options import _set_keep_attrs

try:
import dask.array as da
Expand Down Expand Up @@ -1303,8 +1304,8 @@ def fillna(self, value):
def where(self, cond, other=dtypes.NA):
return ops.where_method(self, cond, other)

def reduce(self, func, dim=None, axis=None, keep_attrs=False,
allow_lazy=False, **kwargs):
def reduce(self, func, dim=None, axis=None,
keep_attrs=_set_keep_attrs(False), allow_lazy=False, **kwargs):
"""Reduce this array by applying `func` along some dimension(s).

Parameters
Expand Down
Loading