Skip to content

Commit c35f942

Browse files
authored
Fix assignment to Datasets with non-unique indexes (#945)
* Fix assignment to Datasets with non-unique indexes Fixes GH943 * Fix failing test
1 parent 633aa11 commit c35f942

File tree

4 files changed

+58
-4
lines changed

4 files changed

+58
-4
lines changed

doc/whats-new.rst

+11
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,17 @@ What's New
1313
import xarray as xr
1414
np.random.seed(123456)
1515
16+
.. _whats-new.0.8.1:
17+
18+
v0.8.1 (unreleased)
19+
-------------------
20+
21+
Bug fixes
22+
~~~~~~~~~
23+
24+
- Fix regression in v0.8.0 that broke assignment to Datasets with non-unique
25+
indexes (:issue:`943`).
26+
1627
.. _whats-new.0.8.0:
1728

1829
v0.8.0 (2 August 2016)

xarray/core/alignment.py

+18-2
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,23 @@ def partial_align(*objects, **kwargs):
9696
exclude = kwargs.pop('exclude', None)
9797
if exclude is None:
9898
exclude = set()
99+
skip_single_target = kwargs.pop('skip_single_target', False)
99100
if kwargs:
100101
raise TypeError('align() got unexpected keyword arguments: %s'
101102
% list(kwargs))
102103

104+
if len(objects) == 1:
105+
obj, = objects
106+
if (indexes is None or
107+
(skip_single_target and
108+
all(obj.indexes[k].equals(v) for k, v in indexes.items()
109+
if k in obj.indexes))):
110+
# We don't need to align, so don't bother with reindexing, which
111+
# fails for non-unique indexes.
112+
# `skip_single_target` is a hack so we can skip alignment of a
113+
# single object in merge.
114+
return (obj.copy() if copy else obj,)
115+
103116
joined_indexes = _join_indexes(join, objects, exclude=exclude)
104117
if indexes is not None:
105118
joined_indexes.update(indexes)
@@ -109,14 +122,16 @@ def partial_align(*objects, **kwargs):
109122
valid_indexers = dict((k, v) for k, v in joined_indexes.items()
110123
if k in obj.dims)
111124
result.append(obj.reindex(copy=copy, **valid_indexers))
125+
112126
return tuple(result)
113127

114128

115129
def is_alignable(obj):
116130
return hasattr(obj, 'indexes') and hasattr(obj, 'reindex')
117131

118132

119-
def deep_align(list_of_variable_maps, join='outer', copy=True, indexes=None):
133+
def deep_align(list_of_variable_maps, join='outer', copy=True, indexes=None,
134+
skip_single_target=False):
120135
"""Align objects, recursing into dictionary values.
121136
"""
122137
if indexes is None:
@@ -145,7 +160,8 @@ def deep_align(list_of_variable_maps, join='outer', copy=True, indexes=None):
145160
else:
146161
out.append(variables)
147162

148-
aligned = partial_align(*targets, join=join, copy=copy, indexes=indexes)
163+
aligned = partial_align(*targets, join=join, copy=copy, indexes=indexes,
164+
skip_single_target=skip_single_target)
149165

150166
for key, aligned_obj in zip(keys, aligned):
151167
if isinstance(key, tuple):

xarray/core/merge.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,8 @@ def align_and_merge_coords(objs, compat='minimal', join='outer',
315315
"""Align and merge coordinate variables."""
316316
_assert_compat_valid(compat)
317317
coerced = coerce_pandas_values(objs)
318-
aligned = deep_align(coerced, join=join, copy=False, indexes=indexes)
318+
aligned = deep_align(coerced, join=join, copy=False, indexes=indexes,
319+
skip_single_target=True)
319320
expanded = expand_variable_dicts(aligned)
320321
priority_vars = _get_priority_vars(aligned, priority_arg, compat=compat)
321322
variables = merge_variables(expanded, priority_vars, compat=compat)
@@ -370,7 +371,8 @@ def merge_core(objs, compat='broadcast_equals', join='outer', priority_arg=None,
370371
_assert_compat_valid(compat)
371372

372373
coerced = coerce_pandas_values(objs)
373-
aligned = deep_align(coerced, join=join, copy=False, indexes=indexes)
374+
aligned = deep_align(coerced, join=join, copy=False, indexes=indexes,
375+
skip_single_target=True)
374376
expanded = expand_variable_dicts(aligned)
375377

376378
coord_names, noncoord_names = determine_coords(coerced)

xarray/test/test_dataset.py

+25
Original file line numberDiff line numberDiff line change
@@ -1453,6 +1453,13 @@ def test_setitem_auto_align(self):
14531453
expected = Dataset({'x': ('y', [4, 5, 6])})
14541454
self.assertDatasetIdentical(ds, expected)
14551455

1456+
def test_setitem_align_new_indexes(self):
1457+
ds = Dataset({'foo': ('x', [1, 2, 3])}, {'x': [0, 1, 2]})
1458+
ds['bar'] = DataArray([2, 3, 4], [('x', [1, 2, 3])])
1459+
expected = Dataset({'foo': ('x', [1, 2, 3]),
1460+
'bar': ('x', [np.nan, 2, 3])})
1461+
self.assertDatasetIdentical(ds, expected)
1462+
14561463
def test_assign(self):
14571464
ds = Dataset()
14581465
actual = ds.assign(x = [0, 1, 2], y = 2)
@@ -1482,6 +1489,24 @@ def test_assign(self):
14821489
expected = expected.set_coords('c')
14831490
self.assertDatasetIdentical(actual, expected)
14841491

1492+
def test_setitem_non_unique_index(self):
1493+
# regression test for GH943
1494+
original = Dataset({'data': ('x', np.arange(5))},
1495+
coords={'x': [0, 1, 2, 0, 1]})
1496+
expected = Dataset({'data': ('x', np.arange(5))})
1497+
1498+
actual = original.copy()
1499+
actual['x'] = list(range(5))
1500+
self.assertDatasetIdentical(actual, expected)
1501+
1502+
actual = original.copy()
1503+
actual['x'] = ('x', list(range(5)))
1504+
self.assertDatasetIdentical(actual, expected)
1505+
1506+
actual = original.copy()
1507+
actual.coords['x'] = list(range(5))
1508+
self.assertDatasetIdentical(actual, expected)
1509+
14851510
def test_delitem(self):
14861511
data = create_test_data()
14871512
all_items = set(data)

0 commit comments

Comments
 (0)