Skip to content

Commit 0555ab8

Browse files
committed
ENH: joins, setops, alignment for PeriodIndex and objects having one as index, close pandas-dev#1138
1 parent 0883fab commit 0555ab8

File tree

4 files changed

+125
-3
lines changed

4 files changed

+125
-3
lines changed

pandas/core/format.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ def _get_footer(self):
7070
if getattr(self.series.index, 'freq', None):
7171
footer += 'Freq: %s' % self.series.index.freqstr
7272

73-
if footer:
73+
if footer and self.series.name:
7474
footer += ', '
7575
footer += ("Name: %s" % str(self.series.name)
7676
if self.series.name else '')

pandas/core/index.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,9 @@ def to_datetime(self, dayfirst=False):
131131
else:
132132
return DatetimeIndex(self.values)
133133

134+
def _assert_can_do_setop(self, other):
135+
return True
136+
134137
@property
135138
def dtype(self):
136139
return self.values.dtype
@@ -465,6 +468,8 @@ def union(self, other):
465468
if len(self) == 0:
466469
return _ensure_index(other)
467470

471+
self._assert_can_do_setop(other)
472+
468473
if self.dtype != other.dtype:
469474
this = self.astype('O')
470475
other = other.astype('O')
@@ -493,7 +498,10 @@ def union(self, other):
493498
pass
494499
else:
495500
# contained in
496-
result = sorted(self)
501+
try:
502+
result = np.sort(self.values)
503+
except TypeError:
504+
result = self.values
497505

498506
# for subclasses
499507
return self._wrap_union_result(other, result)
@@ -518,6 +526,8 @@ def intersection(self, other):
518526
if not hasattr(other, '__iter__'):
519527
raise Exception('Input must be iterable!')
520528

529+
self._assert_can_do_setop(other)
530+
521531
other = _ensure_index(other)
522532

523533
if self.equals(other):

pandas/tseries/period.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# pylint: disable=E1101,E1103,W0232
2+
13
from datetime import datetime
24
import numpy as np
35

@@ -727,6 +729,40 @@ def get_loc(self, key):
727729
key = to_period(key, self.freq).ordinal
728730
return self._engine.get_loc(key)
729731

732+
def join(self, other, how='left', level=None, return_indexers=False):
733+
"""
734+
See Index.join
735+
"""
736+
self._assert_can_do_setop(other)
737+
738+
result = Int64Index.join(self, other, how=how, level=level,
739+
return_indexers=return_indexers)
740+
741+
if return_indexers:
742+
result, lidx, ridx = result
743+
return self._apply_meta(result), lidx, ridx
744+
else:
745+
return self._apply_meta(result)
746+
747+
def _assert_can_do_setop(self, other):
748+
if not isinstance(other, PeriodIndex):
749+
raise TypeError('can only call with other PeriodIndex-ed objects')
750+
751+
if self.freq != other.freq:
752+
raise ValueError('Only like-indexed PeriodIndexes compatible '
753+
'for join (for now)')
754+
755+
def _wrap_union_result(self, other, result):
756+
name = self.name if self.name == other.name else None
757+
result = self._apply_meta(result)
758+
result.name = name
759+
return result
760+
761+
def _apply_meta(self, rawarr):
762+
idx = rawarr.view(PeriodIndex)
763+
idx.freq = self.freq
764+
return idx
765+
730766
def __getitem__(self, key):
731767
"""Override numpy.ndarray's __getitem__ method to work as desired"""
732768
arr_idx = self.view(np.ndarray)
@@ -784,6 +820,7 @@ def take(self, indices, axis=None):
784820
taken.name = self.name
785821
return taken
786822

823+
787824
def _get_ordinal_range(start, end, periods, freq):
788825
if com._count_not_none(start, end, periods) < 2:
789826
raise ValueError('Must specify 2 of start, end, periods')

pandas/tseries/tests/test_period.py

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
from numpy.ma.testutils import assert_equal
1313

14-
from pandas.tseries.period import Period, PeriodIndex
14+
from pandas.tseries.period import Period, PeriodIndex, period_range
1515
from pandas.tseries.index import DatetimeIndex, date_range
1616
from pandas.tseries.tools import to_datetime
1717

@@ -20,6 +20,7 @@
2020

2121
from pandas import Series, TimeSeries, DataFrame
2222
from pandas.util.testing import assert_series_equal
23+
import pandas.util.testing as tm
2324

2425
class TestPeriodProperties(TestCase):
2526
"Test properties such as year, month, weekday, etc...."
@@ -1184,6 +1185,80 @@ def test_take(self):
11841185
self.assert_(isinstance(taken2, PeriodIndex))
11851186
self.assert_(taken2.freq == index.freq)
11861187

1188+
def test_joins(self):
1189+
index = period_range('1/1/2000', '1/20/2000', freq='D')
1190+
1191+
for kind in ['inner', 'outer', 'left', 'right']:
1192+
joined = index.join(index[:-5], how=kind)
1193+
1194+
self.assert_(isinstance(joined, PeriodIndex))
1195+
self.assert_(joined.freq == index.freq)
1196+
1197+
def test_align_series(self):
1198+
rng = period_range('1/1/2000', '1/1/2010', freq='A')
1199+
ts = Series(np.random.randn(len(rng)), index=rng)
1200+
1201+
result = ts + ts[::2]
1202+
expected = ts + ts
1203+
expected[1::2] = np.nan
1204+
assert_series_equal(result, expected)
1205+
1206+
result = ts + _permute(ts[::2])
1207+
assert_series_equal(result, expected)
1208+
1209+
# it works!
1210+
for kind in ['inner', 'outer', 'left', 'right']:
1211+
ts.align(ts[::2], join=kind)
1212+
1213+
self.assertRaises(Exception, ts.__add__,
1214+
ts.asfreq('D', how='end'))
1215+
1216+
def test_align_frame(self):
1217+
rng = period_range('1/1/2000', '1/1/2010', freq='A')
1218+
ts = DataFrame(np.random.randn(len(rng), 3), index=rng)
1219+
1220+
result = ts + ts[::2]
1221+
expected = ts + ts
1222+
expected.values[1::2] = np.nan
1223+
tm.assert_frame_equal(result, expected)
1224+
1225+
result = ts + _permute(ts[::2])
1226+
tm.assert_frame_equal(result, expected)
1227+
1228+
def test_union(self):
1229+
index = period_range('1/1/2000', '1/20/2000', freq='D')
1230+
1231+
result = index[:-5].union(index[10:])
1232+
self.assert_(result.equals(index))
1233+
1234+
# not in order
1235+
result = _permute(index[:-5]).union(_permute(index[10:]))
1236+
self.assert_(result.equals(index))
1237+
1238+
# raise if different frequencies
1239+
index = period_range('1/1/2000', '1/20/2000', freq='D')
1240+
index2 = period_range('1/1/2000', '1/20/2000', freq='W-WED')
1241+
self.assertRaises(Exception, index.union, index2)
1242+
1243+
def test_intersection(self):
1244+
index = period_range('1/1/2000', '1/20/2000', freq='D')
1245+
1246+
result = index[:-5].intersection(index[10:])
1247+
self.assert_(result.equals(index[10:-5]))
1248+
1249+
# not in order
1250+
left = _permute(index[:-5])
1251+
right = _permute(index[10:])
1252+
result = left.intersection(right).order()
1253+
self.assert_(result.equals(index[10:-5]))
1254+
1255+
# raise if different frequencies
1256+
index = period_range('1/1/2000', '1/20/2000', freq='D')
1257+
index2 = period_range('1/1/2000', '1/20/2000', freq='W-WED')
1258+
self.assertRaises(Exception, index.intersection, index2)
1259+
1260+
def _permute(obj):
1261+
return obj.take(np.random.permutation(len(obj)))
11871262

11881263
class TestMethods(TestCase):
11891264
"Base test class for MaskedArrays."

0 commit comments

Comments
 (0)