Skip to content

Commit 9d49344

Browse files
committed
Reconcile union behavior with eval
1 parent 63d4590 commit 9d49344

File tree

9 files changed

+65
-67
lines changed

9 files changed

+65
-67
lines changed

doc/source/whatsnew/v0.23.0.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -796,7 +796,7 @@ Indexing
796796
- Bug in :func:`IntervalIndex.symmetric_difference` where the symmetric difference with a non-``IntervalIndex`` did not raise (:issue:`18475`)
797797
- Bug in :class:`IntervalIndex` where set operations that returned an empty ``IntervalIndex`` had the wrong dtype (:issue:`19101`)
798798
- Bug in :meth:`DataFrame.drop_duplicates` where no ``KeyError`` is raised when passing in columns that don't exist on the ``DataFrame`` (issue:`19726`)
799-
- Bug in :func:`Index.union` where resulting names were not computed correrctly (:issue:`9943`, :issue:`9862`)
799+
- Bug in :func:`Index.union` where resulting names were not computed correctly (:issue:`9943`, :issue:`9862`)
800800

801801

802802
MultiIndex

pandas/core/indexes/base.py

+18-38
Original file line numberDiff line numberDiff line change
@@ -1121,7 +1121,7 @@ def _convert_can_do_setop(self, other):
11211121
other = Index(other, name=self.name)
11221122
result_name = self.name
11231123
else:
1124-
result_name = self._get_intersection_name(other)
1124+
result_name = self._get_setop_name(other)
11251125
return other, result_name
11261126

11271127
def _convert_for_op(self, value):
@@ -2190,33 +2190,22 @@ def __or__(self, other):
21902190
def __xor__(self, other):
21912191
return self.symmetric_difference(other)
21922192

2193-
def _get_union_name(self, other):
2193+
def _get_setop_name(self, other):
21942194
# GH 9943 9862
21952195
"""
2196-
Given 2 indexes, give the union name meaning
2197-
we take the not None one, or None if the names differ.
2196+
Given 2 indexes, give the name appropriate for set
2197+
operations meaning we take the name if both names
2198+
are the same, or None if the names differ.
21982199
"""
2199-
if self.name != other.name:
2200-
if self.name is None or other.name is None:
2201-
name = self.name or other.name
2202-
else:
2203-
name = None
2204-
else:
2205-
name = self.name
2206-
return name
2207-
2208-
def _get_intersection_name(self, other):
2209-
# GH 9943 9862
22102200
return self.name if self.name == other.name else None
22112201

2212-
def _get_consensus_name_object(self, other, name_converter):
2202+
def _get_setop_name_object(self, other):
22132203
"""
2214-
Given 2 indexes, give a consensus name meaning
2215-
we use the name converter (either _get_union_name or
2216-
get_intersection_name) to determine the name.
2217-
Return a new object if we are resetting the name
2204+
Given 2 indexes, give a setop name and object, meaning
2205+
we use _get_setop_name() to return the name, and then
2206+
return a new object if we are resetting the name
22182207
"""
2219-
name = name_converter(other)
2208+
name = self._get_setop_name(other)
22202209
if self.name != name:
22212210
return self._shallow_copy(name=name)
22222211
return self
@@ -2246,12 +2235,10 @@ def union(self, other):
22462235
other = _ensure_index(other)
22472236

22482237
if len(other) == 0 or self.equals(other):
2249-
return self._get_consensus_name_object(other,
2250-
self._get_union_name)
2238+
return self._get_setop_name_object(other)
22512239

22522240
if len(self) == 0:
2253-
return other._get_consensus_name_object(self,
2254-
other._get_union_name)
2241+
return other._get_setop_name_object(self)
22552242

22562243
# TODO: is_dtype_union_equal is a hack around
22572244
# 1. buggy set ops with duplicates (GH #13432)
@@ -2314,15 +2301,10 @@ def union(self, other):
23142301
stacklevel=3)
23152302

23162303
# for subclasses
2317-
return self._wrap_setop_result(other, result, self._get_union_name)
2304+
return self._wrap_setop_result(other, result)
23182305

2319-
def _wrap_setop_result(self, other, result, name_func):
2320-
# GH 9943 9862
2321-
"""
2322-
name_func is either self._get_union_name or
2323-
self._get_intersection_name
2324-
"""
2325-
return self.__class__(result, name=name_func(other))
2306+
def _wrap_setop_result(self, other, result):
2307+
return self.__class__(result, name=self._get_setop_name(other))
23262308

23272309
def intersection(self, other):
23282310
"""
@@ -2352,8 +2334,7 @@ def intersection(self, other):
23522334
other = _ensure_index(other)
23532335

23542336
if self.equals(other):
2355-
return self._get_consensus_name_object(other,
2356-
self._get_intersection_name)
2337+
return self._get_setop_name_object(other)
23572338

23582339
if not is_dtype_equal(self.dtype, other.dtype):
23592340
this = self.astype('O')
@@ -2373,8 +2354,7 @@ def intersection(self, other):
23732354
if self.is_monotonic and other.is_monotonic:
23742355
try:
23752356
result = self._inner_indexer(lvals, rvals)[0]
2376-
return self._wrap_setop_result(other, result,
2377-
self._get_intersection_name)
2357+
return self._wrap_setop_result(other, result)
23782358
except TypeError:
23792359
pass
23802360

@@ -3503,7 +3483,7 @@ def _join_monotonic(self, other, how='left', return_indexers=False):
35033483
return join_index
35043484

35053485
def _wrap_joined_index(self, joined, other):
3506-
name = self._get_intersection_name(other)
3486+
name = self._get_setop_name(other)
35073487
return Index(joined, name=name)
35083488

35093489
def _get_string_slice(self, key, use_lhs=True, use_rhs=True):

pandas/core/indexes/datetimes.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -1245,7 +1245,7 @@ def _maybe_utc_convert(self, other):
12451245
return this, other
12461246

12471247
def _wrap_joined_index(self, joined, other):
1248-
name = self._get_intersection_name(other)
1248+
name = self._get_setop_name(other)
12491249
if (isinstance(other, DatetimeIndex) and
12501250
self.offset == other.offset and
12511251
self._can_fast_union(other)):
@@ -1341,8 +1341,8 @@ def __iter__(self):
13411341
box="timestamp")
13421342
return iter(converted)
13431343

1344-
def _wrap_setop_result(self, other, result, name_func):
1345-
name = name_func(other)
1344+
def _wrap_setop_result(self, other, result):
1345+
name = self._get_setop_name(other)
13461346
if not timezones.tz_compare(self.tz, other.tz):
13471347
raise ValueError('Passed item and index have different timezone')
13481348
return self._simple_new(result, name=name, freq=None, tz=self.tz)

pandas/core/indexes/interval.py

+1-4
Original file line numberDiff line numberDiff line change
@@ -1351,10 +1351,7 @@ def func(self, other):
13511351
raise TypeError(msg.format(op=op_name))
13521352

13531353
result = getattr(self._multiindex, op_name)(other._multiindex)
1354-
if op_name == 'union':
1355-
result_name = self._get_union_name(other)
1356-
else:
1357-
result_name = self._get_intersection_name(other)
1354+
result_name = self._get_setop_name(other)
13581355

13591356
# GH 19101: ensure empty results have correct dtype
13601357
if result.empty:

pandas/core/indexes/numeric.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ def _convert_scalar_indexer(self, key, kind=None):
187187
._convert_scalar_indexer(key, kind=kind))
188188

189189
def _wrap_joined_index(self, joined, other):
190-
name = self._get_intersection_name(other)
190+
name = self._get_setop_name(other)
191191
return Int64Index(joined, name=name)
192192

193193
@classmethod
@@ -264,7 +264,7 @@ def _convert_index_indexer(self, keyarr):
264264
return keyarr
265265

266266
def _wrap_joined_index(self, joined, other):
267-
name = self._get_intersection_name(other)
267+
name = self._get_setop_name(other)
268268
return UInt64Index(joined, name=name)
269269

270270
@classmethod

pandas/core/indexes/period.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -998,8 +998,8 @@ def _assert_can_do_setop(self, other):
998998
msg = _DIFFERENT_FREQ_INDEX.format(self.freqstr, other.freqstr)
999999
raise IncompatibleFrequency(msg)
10001000

1001-
def _wrap_setop_result(self, other, result, name_func):
1002-
name = name_func(other)
1001+
def _wrap_setop_result(self, other, result):
1002+
name = self._get_setop_name(other)
10031003
result = self._apply_meta(result)
10041004
result.name = name
10051005
return result

pandas/core/indexes/timedeltas.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -586,7 +586,7 @@ def join(self, other, how='left', level=None, return_indexers=False,
586586
sort=sort)
587587

588588
def _wrap_joined_index(self, joined, other):
589-
name = self._get_intersection_name(other)
589+
name = self._get_setop_name(other)
590590
if (isinstance(other, TimedeltaIndex) and self.freq == other.freq and
591591
self._can_fast_union(other)):
592592
joined = self._shallow_copy(joined, name=name)
@@ -646,8 +646,8 @@ def _fast_union(self, other):
646646
else:
647647
return left
648648

649-
def _wrap_setop_result(self, other, result, name_func):
650-
name = name_func(other)
649+
def _wrap_setop_result(self, other, result):
650+
name = self._get_setop_name(other)
651651
return self._simple_new(result, name=name, freq=None)
652652

653653
def intersection(self, other):

pandas/tests/indexes/test_base.py

+32-9
Original file line numberDiff line numberDiff line change
@@ -729,7 +729,15 @@ def test_union(self):
729729
union = Index([]).union(first)
730730
assert union is first
731731

732-
# preserve names
732+
# preserve names when they are the same
733+
# GH 9943 9862
734+
735+
first = Index(list('ab'), name='A')
736+
second = Index(list('abc'), name='A')
737+
union = first.union(second)
738+
expected = Index(list('abc'), name='A')
739+
tm.assert_index_equal(union, expected)
740+
733741
first = Index(list('ab'), name='A')
734742
second = Index(list('ab'), name='B')
735743
union = first.union(second)
@@ -751,51 +759,66 @@ def test_union(self):
751759
first = Index(list('ab'))
752760
second = Index(list('ab'), name='B')
753761
union = first.union(second)
754-
expected = Index(list('ab'), name='B')
762+
expected = Index(list('ab'), name=None)
755763
tm.assert_index_equal(union, expected)
756764

757765
# GH 9943 9862
758766
first = Index(list('abc'))
759767
second = Index(list('ab'), name='B')
760768
union = first.union(second)
761-
expected = Index(list('abc'), name='B')
769+
expected = Index(list('abc'), name=None)
762770
tm.assert_index_equal(union, expected)
763771

764772
first = Index([])
765773
second = Index(list('ab'), name='B')
766774
union = first.union(second)
767-
expected = Index(list('ab'), name='B')
775+
expected = Index(list('ab'), name=None)
768776
tm.assert_index_equal(union, expected)
769777

770778
first = Index(list('ab'))
771779
second = Index([], name='B')
772780
union = first.union(second)
773-
expected = Index(list('ab'), name='B')
781+
expected = Index(list('ab'), name=None)
774782
tm.assert_index_equal(union, expected)
775783

776784
first = Index(list('ab'), name='A')
777785
second = Index(list('ab'))
778786
union = first.union(second)
779-
expected = Index(list('ab'), name='A')
787+
expected = Index(list('ab'), name=None)
780788
tm.assert_index_equal(union, expected)
781789

782790
# GH 9943 9862
783791
first = Index(list('ab'), name='A')
784792
second = Index(list('abc'))
785793
union = first.union(second)
786-
expected = Index(list('abc'), name='A')
794+
expected = Index(list('abc'), name=None)
787795
tm.assert_index_equal(union, expected)
788796

789797
first = Index(list('ab'), name='A')
790798
second = Index([])
791799
union = first.union(second)
792-
expected = Index(list('ab'), name='A')
800+
expected = Index(list('ab'), name=None)
793801
tm.assert_index_equal(union, expected)
794802

795803
first = Index([], name='A')
796804
second = Index(list('ab'))
797805
union = first.union(second)
798-
expected = Index(list('ab'), name='A')
806+
expected = Index(list('ab'), name=None)
807+
tm.assert_index_equal(union, expected)
808+
809+
# Chained unions handles names correctly
810+
i1 = Index([1, 2], name='i1')
811+
i2 = Index([3, 4], name='i2')
812+
i3 = Index([5, 6], name='i3')
813+
union = i1.union(i2.union(i3))
814+
expected = i1.union(i2).union(i3)
815+
tm.assert_index_equal(union, expected)
816+
817+
j1 = Index([1, 2], name='j1')
818+
j2 = Index([], name='j2')
819+
j3 = Index([], name='j3')
820+
union = j1.union(j2.union(j3))
821+
expected = j1.union(j2).union(j3)
799822
tm.assert_index_equal(union, expected)
800823

801824
with tm.assert_produces_warning(RuntimeWarning):

pandas/tests/indexes/test_range.py

+3-5
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,7 @@ def test_join_outer(self):
432432
tm.assert_index_equal(res, noidx_res)
433433

434434
eres = Int64Index([0, 2, 4, 6, 8, 10, 12, 14, 15, 16, 17, 18, 19, 20,
435-
21, 22, 23, 24, 25], name=self.index.name)
435+
21, 22, 23, 24, 25])
436436
elidx = np.array([0, 1, 2, 3, 4, 5, 6, 7, -1, 8, -1, 9,
437437
-1, -1, -1, -1, -1, -1, -1], dtype=np.intp)
438438
eridx = np.array([-1, -1, -1, -1, -1, -1, -1, -1, 10, 9, 8, 7, 6,
@@ -667,13 +667,11 @@ def test_union_noncomparable(self):
667667
now = datetime.now()
668668
other = Index([now + timedelta(i) for i in range(4)], dtype=object)
669669
result = self.index.union(other)
670-
expected = Index(np.concatenate((self.index, other)),
671-
name=self.index.name)
670+
expected = Index(np.concatenate((self.index, other)))
672671
tm.assert_index_equal(result, expected)
673672

674673
result = other.union(self.index)
675-
expected = Index(np.concatenate((other, self.index)),
676-
name=self.index.name)
674+
expected = Index(np.concatenate((other, self.index)))
677675
tm.assert_index_equal(result, expected)
678676

679677
def test_union(self):

0 commit comments

Comments
 (0)