Skip to content

Support "None" as a variable name and use it as a default #147

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 2 commits into from
Jun 9, 2014
Merged
Show file tree
Hide file tree
Changes from all 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
9 changes: 6 additions & 3 deletions test/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ def assertArrayEqual(self, a1, a2):
def assertDatasetEqual(self, d1, d2):
# this method is functionally equivalent to `assert d1 == d2`, but it
# checks each aspect of equality separately for easier debugging
self.assertEqual(sorted(d1.variables), sorted(d2.variables))
self.assertEqual(sorted(d1.variables, key=str),
sorted(d2.variables, key=str))
for k in d1:
v1 = d1.variables[k]
v2 = d2.variables[k]
Expand All @@ -93,14 +94,16 @@ def assertDatasetIdentical(self, d1, d2):
# this method is functionally equivalent to `assert d1.identical(d2)`,
# but it checks each aspect of equality separately for easier debugging
assert utils.dict_equiv(d1.attrs, d2.attrs), (d1.attrs, d2.attrs)
self.assertEqual(sorted(d1.variables), sorted(d2.variables))
self.assertEqual(sorted(d1.variables, key=str),
sorted(d2.variables, key=str))
for k in d1:
v1 = d1.variables[k]
v2 = d2.variables[k]
assert v1.identical(v2), (v1, v2)

def assertDatasetAllClose(self, d1, d2, rtol=1e-05, atol=1e-08):
self.assertEqual(sorted(d1.variables), sorted(d2.variables))
self.assertEqual(sorted(d1.variables, key=str),
sorted(d2.variables, key=str))
for k in d1:
v1 = d1.variables[k]
v2 = d2.variables[k]
Expand Down
5 changes: 5 additions & 0 deletions test/test_backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ def test_roundtrip_test_data(self):
actual = self.roundtrip(expected)
self.assertDatasetAllClose(expected, actual)

def test_roundtrip_None_variable(self):
expected = Dataset({None: (('x', 'y'), [[0, 1], [2, 3]])})
actual = self.roundtrip(expected)
self.assertDatasetAllClose(expected, actual)

def test_roundtrip_string_data(self):
expected = Dataset({'x': ('t', ['abc', 'def'])})
actual = self.roundtrip(expected)
Expand Down
20 changes: 20 additions & 0 deletions test/test_data_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,21 @@ def test_dataset_math(self):
'x': sim['x']})['tmin']
self.assertDataArrayEqual(actual, expected)

def test_math_name(self):
# Verify that name is preserved only when it can be done unambiguously.
# The rule (copied from pandas.Series) is keep the current name only if
# the other object has no name attribute and this object isn't a
# coordinate; otherwise reset to None.
ds = self.ds
a = self.dv
self.assertEqual((+a).name, 'foo')
self.assertEqual((a + 0).name, 'foo')
self.assertIs((a + a.rename(None)).name, None)
self.assertIs((a + a).name, None)
self.assertIs((+ds['x']).name, None)
self.assertIs((ds['x'] + 0).name, None)
self.assertIs((a + ds['x']).name, None)

def test_coord_math(self):
ds = Dataset({'x': ('x', 1 + np.arange(3))})
expected = ds.copy()
Expand Down Expand Up @@ -381,3 +396,8 @@ def test_to_and_from_series(self):
self.assertEqual('foo', actual.name)
# test roundtrip
self.assertDataArrayIdentical(self.dv, DataArray.from_series(actual))
# test name is None
actual.name = None
expected_da = self.dv.rename(None)
self.assertDataArrayIdentical(expected_da,
DataArray.from_series(actual))
4 changes: 4 additions & 0 deletions test/test_dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,10 @@ def test_equals_and_identical(self):
self.assertFalse(data.equals(data2))
self.assertTrue(data != data2)

data = create_test_data(seed=42).rename({'var1': None})
self.assertTrue(data.equals(data))
self.assertTrue(data.identical(data))

def test_attrs(self):
data = create_test_data(seed=42)
data.attrs = {'foobar': 'baz'}
Expand Down
20 changes: 18 additions & 2 deletions xray/backends/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,21 @@
from xray.pycompat import iteritems


NONE_VAR_NAME = '__values__'


def _encode_variable_name(name):
if name is None:
name = NONE_VAR_NAME
return name


def _decode_variable_name(name):
if name == NONE_VAR_NAME:
name = None
return name


class AbstractDataStore(object):
def open_store_variable(self, v):
raise NotImplementedError
Expand All @@ -12,7 +27,8 @@ def store_variables(self):

@property
def variables(self):
return FrozenOrderedDict((k, self.open_store_variable(v))
return FrozenOrderedDict((_decode_variable_name(k),
self.open_store_variable(v))
for k, v in iteritems(self.store_variables))

def sync(self):
Expand All @@ -39,7 +55,7 @@ def set_attributes(self, attributes):

def set_variables(self, variables):
for vn, v in iteritems(variables):
self.set_variable(vn, v)
self.set_variable(_encode_variable_name(vn), v)

def set_necessary_dimensions(self, variable):
for d, l in zip(variable.dimensions, variable.shape):
Expand Down
5 changes: 4 additions & 1 deletion xray/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,10 @@ def _wrap_indent(text, start='', length=None):


def array_repr(arr):
name_str = ('%r ' % arr.name) if hasattr(arr, 'name') else ''
if hasattr(arr, 'name') and arr.name is not None:
name_str = '%r ' % arr.name
else:
name_str = ''
dim_summary = ', '.join('%s: %s' % (k, v) for k, v
in zip(arr.dimensions, arr.shape))
summary = ['<xray.%s %s(%s)>'% (type(arr).__name__, name_str, dim_summary)]
Expand Down
41 changes: 18 additions & 23 deletions xray/data_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,8 @@ def rename(self, new_name_or_name_dict):
--------
Dataset.rename
"""
if isinstance(new_name_or_name_dict, basestring):
if (isinstance(new_name_or_name_dict, basestring)
or new_name_or_name_dict is None):
new_name = new_name_or_name_dict
name_dict = {self.name: new_name}
else:
Expand Down Expand Up @@ -597,10 +598,9 @@ def from_series(cls, series):
with NaN). Thus this operation should be the inverse of the `to_series`
method.
"""
name = series.name if series.name is not None else 'values'
df = pd.DataFrame({name: series})
df = pd.DataFrame({series.name: series})
ds = xray.Dataset.from_dataframe(df)
return ds[name]
return ds[series.name]

def equals(self, other):
"""True if two DataArrays have the same dimensions, coordinates and
Expand Down Expand Up @@ -638,29 +638,22 @@ def identical(self, other):
def _select_coordinates(self):
return xray.Dataset(self.coordinates)

def _refocus(self, new_var, name=None):
"""Returns a copy of this DataArray's dataset with this
DataArray's focus variable replaced by `new_var`.

If `new_var` is a DataArray, its contents will be merged in.
"""
if not hasattr(new_var, 'dimensions'):
new_var = variable.Variable(self.variable.dimensions, new_var)
def __array_wrap__(self, obj, context=None):
new_var = self.variable.__array_wrap__(obj, context)
ds = self._select_coordinates()
if name is None:
name = self.name + '_'
if (self.name,) == self.dimensions:
# use a new name for coordinate variables
name = None
else:
name = self.name
ds[name] = new_var
return ds[name]

def __array_wrap__(self, obj, context=None):
return self._refocus(self.variable.__array_wrap__(obj, context))

@staticmethod
def _unary_op(f):
@functools.wraps(f)
def func(self, *args, **kwargs):
return self._refocus(f(self.variable, *args, **kwargs),
self.name + '_' + f.__name__)
return self.__array_wrap__(f(self.variable, *args, **kwargs))
return func

def _check_coordinates_compat(self, other):
Expand All @@ -682,11 +675,13 @@ def func(self, other):
if hasattr(other, 'coordinates'):
ds.merge(other.coordinates, inplace=True)
other_array = getattr(other, 'variable', other)
other_name = getattr(other, 'name', 'other')
name = self.name + '_' + f.__name__ + '_' + other_name
if hasattr(other, 'name') or (self.name,) == self.dimensions:
name = None
else:
name = self.name
ds[name] = (f(self.variable, other_array)
if not reflexive
else f(other_array, self.variable))
if not reflexive
else f(other_array, self.variable))
return ds[name]
return func

Expand Down
20 changes: 16 additions & 4 deletions xray/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,14 @@ def _assert_compat_valid(compat):
"'identical'" % compat)


def _item0_str(items):
"""Key function for use in sorted on a list of variables.

This is useful because None is not comparable to strings in Python 3.
"""
return str(items[0])


def as_dataset(obj):
"""Cast the given object to a Dataset.

Expand Down Expand Up @@ -469,8 +477,10 @@ def equals(self, other):
return (len(self) == len(other)
and all(k1 == k2 and v1.equals(v2)
for (k1, v1), (k2, v2)
in zip(sorted(self.variables.items()),
sorted(other.variables.items()))))
in zip(sorted(self.variables.items(),
key=_item0_str),
sorted(other.variables.items(),
key=_item0_str))))
except (TypeError, AttributeError):
return False

Expand All @@ -489,8 +499,10 @@ def identical(self, other):
and len(self) == len(other)
and all(k1 == k2 and v1.identical(v2)
for (k1, v1), (k2, v2)
in zip(sorted(self.variables.items()),
sorted(other.variables.items()))))
in zip(sorted(self.variables.items(),
key=_item0_str),
sorted(other.variables.items(),
key=_item0_str))))
except (TypeError, AttributeError):
return False

Expand Down