implement shift_quarters --> apply_index for quarters and years (#18522) · pandas-dev/pandas@88ab693 (original) (raw)

`@@ -27,7 +27,7 @@

`

27

27

`apply_index_wraps,

`

28

28

`roll_yearday,

`

29

29

`shift_month,

`

30

``

`-

BeginMixin, EndMixin,

`

``

30

`+

EndMixin,

`

31

31

`BaseOffset)

`

32

32

``

33

33

``

`@@ -1028,10 +1028,7 @@ def cbday(self):

`

1028

1028

``

1029

1029

`@cache_readonly

`

1030

1030

`def m_offset(self):

`

1031

``

`-

kwds = self.kwds

`

1032

``

`-

kwds = {key: kwds[key] for key in kwds

`

1033

``

`-

if key not in ['calendar', 'weekmask', 'holidays', 'offset']}

`

1034

``

`-

return MonthEnd(n=1, normalize=self.normalize, **kwds)

`

``

1031

`+

return MonthEnd(n=1, normalize=self.normalize)

`

1035

1032

``

1036

1033

`@apply_wraps

`

1037

1034

`def apply(self, other):

`

`@@ -1106,10 +1103,7 @@ def cbday(self):

`

1106

1103

``

1107

1104

`@cache_readonly

`

1108

1105

`def m_offset(self):

`

1109

``

`-

kwds = self.kwds

`

1110

``

`-

kwds = {key: kwds[key] for key in kwds

`

1111

``

`-

if key not in ['calendar', 'weekmask', 'holidays', 'offset']}

`

1112

``

`-

return MonthBegin(n=1, normalize=self.normalize, **kwds)

`

``

1106

`+

return MonthBegin(n=1, normalize=self.normalize)

`

1113

1107

``

1114

1108

`@apply_wraps

`

1115

1109

`def apply(self, other):

`

`@@ -1254,12 +1248,9 @@ def onOffset(self, dt):

`

1254

1248

``

1255

1249

`def _apply(self, n, other):

`

1256

1250

`# if other.day is not day_of_month move to day_of_month and update n

`

1257

``

`-

if other.day < self.day_of_month:

`

1258

``

`-

other = other.replace(day=self.day_of_month)

`

1259

``

`-

if n > 0:

`

1260

``

`-

n -= 1

`

``

1251

`+

if n > 0 and other.day < self.day_of_month:

`

``

1252

`+

n -= 1

`

1261

1253

`elif other.day > self.day_of_month:

`

1262

``

`-

other = other.replace(day=self.day_of_month)

`

1263

1254

`n += 1

`

1264

1255

``

1265

1256

`months = n // 2

`

`@@ -1309,12 +1300,9 @@ def onOffset(self, dt):

`

1309

1300

`def _apply(self, n, other):

`

1310

1301

`# if other.day is not day_of_month move to day_of_month and update n

`

1311

1302

`if other.day < self.day_of_month:

`

1312

``

`-

other = other.replace(day=self.day_of_month)

`

1313

1303

`n -= 1

`

1314

``

`-

elif other.day > self.day_of_month:

`

1315

``

`-

other = other.replace(day=self.day_of_month)

`

1316

``

`-

if n <= 0:

`

1317

``

`-

n += 1

`

``

1304

`+

elif n <= 0 and other.day > self.day_of_month:

`

``

1305

`+

n += 1

`

1318

1306

``

1319

1307

`months = n // 2 + n % 2

`

1320

1308

`day = 1 if n % 2 else self.day_of_month

`

`@@ -1471,6 +1459,7 @@ def apply(self, other):

`

1471

1459

`def getOffsetOfMonth(self, dt):

`

1472

1460

`w = Week(weekday=self.weekday)

`

1473

1461

`d = datetime(dt.year, dt.month, 1, tzinfo=dt.tzinfo)

`

``

1462

`+

TODO: Is this DST-safe?

`

1474

1463

`d = w.rollforward(d)

`

1475

1464

`return d + timedelta(weeks=self.week)

`

1476

1465

``

`@@ -1550,6 +1539,7 @@ def getOffsetOfMonth(self, dt):

`

1550

1539

`d = datetime(dt.year, dt.month, 1, dt.hour, dt.minute,

`

1551

1540

`dt.second, dt.microsecond, tzinfo=dt.tzinfo)

`

1552

1541

`eom = m.rollforward(d)

`

``

1542

`+

TODO: Is this DST-safe?

`

1553

1543

`w = Week(weekday=self.weekday)

`

1554

1544

`return w.rollback(eom)

`

1555

1545

``

`@@ -1635,6 +1625,12 @@ def onOffset(self, dt):

`

1635

1625

`modMonth = (dt.month - self.startingMonth) % 3

`

1636

1626

`return modMonth == 0 and dt.day == self._get_offset_day(dt)

`

1637

1627

``

``

1628

`+

@apply_index_wraps

`

``

1629

`+

def apply_index(self, dtindex):

`

``

1630

`+

shifted = liboffsets.shift_quarters(dtindex.asi8, self.n,

`

``

1631

`+

self.startingMonth, self._day_opt)

`

``

1632

`+

return dtindex._shallow_copy(shifted)

`

``

1633

+

1638

1634

``

1639

1635

`class BQuarterEnd(QuarterOffset):

`

1640

1636

`"""DateOffset increments between business Quarter dates

`

`@@ -1659,7 +1655,7 @@ class BQuarterBegin(QuarterOffset):

`

1659

1655

`_day_opt = 'business_start'

`

1660

1656

``

1661

1657

``

1662

``

`-

class QuarterEnd(EndMixin, QuarterOffset):

`

``

1658

`+

class QuarterEnd(QuarterOffset):

`

1663

1659

`"""DateOffset increments between business Quarter dates

`

1664

1660

` startingMonth = 1 corresponds to dates like 1/31/2007, 4/30/2007, ...

`

1665

1661

` startingMonth = 2 corresponds to dates like 2/28/2007, 5/31/2007, ...

`

`@@ -1670,25 +1666,14 @@ class QuarterEnd(EndMixin, QuarterOffset):

`

1670

1666

`_prefix = 'Q'

`

1671

1667

`_day_opt = 'end'

`

1672

1668

``

1673

``

`-

@apply_index_wraps

`

1674

``

`-

def apply_index(self, i):

`

1675

``

`-

return self._end_apply_index(i, self.freqstr)

`

1676

``

-

1677

1669

``

1678

``

`-

class QuarterBegin(BeginMixin, QuarterOffset):

`

``

1670

`+

class QuarterBegin(QuarterOffset):

`

1679

1671

`_outputName = 'QuarterBegin'

`

1680

1672

`_default_startingMonth = 3

`

1681

1673

`_from_name_startingMonth = 1

`

1682

1674

`_prefix = 'QS'

`

1683

1675

`_day_opt = 'start'

`

1684

1676

``

1685

``

`-

@apply_index_wraps

`

1686

``

`-

def apply_index(self, i):

`

1687

``

`-

freq_month = 12 if self.startingMonth == 1 else self.startingMonth - 1

`

1688

``

`-

month = liboffsets._int_to_month[freq_month]

`

1689

``

`-

freqstr = 'Q-{month}'.format(month=month)

`

1690

``

`-

return self._beg_apply_index(i, freqstr)

`

1691

``

-

1692

1677

``

1693

1678

`# ---------------------------------------------------------------------

`

1694

1679

`# Year-Based Offset Classes

`

`@@ -1709,6 +1694,13 @@ def apply(self, other):

`

1709

1694

`months = years * 12 + (self.month - other.month)

`

1710

1695

`return shift_month(other, months, self._day_opt)

`

1711

1696

``

``

1697

`+

@apply_index_wraps

`

``

1698

`+

def apply_index(self, dtindex):

`

``

1699

`+

shifted = liboffsets.shift_quarters(dtindex.asi8, self.n,

`

``

1700

`+

self.month, self._day_opt,

`

``

1701

`+

modby=12)

`

``

1702

`+

return dtindex._shallow_copy(shifted)

`

``

1703

+

1712

1704

`def onOffset(self, dt):

`

1713

1705

`if self.normalize and not _is_normalized(dt):

`

1714

1706

`return False

`

`@@ -1752,31 +1744,19 @@ class BYearBegin(YearOffset):

`

1752

1744

`_day_opt = 'business_start'

`

1753

1745

``

1754

1746

``

1755

``

`-

class YearEnd(EndMixin, YearOffset):

`

``

1747

`+

class YearEnd(YearOffset):

`

1756

1748

`"""DateOffset increments between calendar year ends"""

`

1757

1749

`_default_month = 12

`

1758

1750

`_prefix = 'A'

`

1759

1751

`_day_opt = 'end'

`

1760

1752

``

1761

``

`-

@apply_index_wraps

`

1762

``

`-

def apply_index(self, i):

`

1763

``

`-

convert month anchor to annual period tuple

`

1764

``

`-

return self._end_apply_index(i, self.freqstr)

`

1765

1753

``

1766

``

-

1767

``

`-

class YearBegin(BeginMixin, YearOffset):

`

``

1754

`+

class YearBegin(YearOffset):

`

1768

1755

`"""DateOffset increments between calendar year begin dates"""

`

1769

1756

`_default_month = 1

`

1770

1757

`_prefix = 'AS'

`

1771

1758

`_day_opt = 'start'

`

1772

1759

``

1773

``

`-

@apply_index_wraps

`

1774

``

`-

def apply_index(self, i):

`

1775

``

`-

freq_month = 12 if self.month == 1 else self.month - 1

`

1776

``

`-

month = liboffsets._int_to_month[freq_month]

`

1777

``

`-

freqstr = 'A-{month}'.format(month=month)

`

1778

``

`-

return self._beg_apply_index(i, freqstr)

`

1779

``

-

1780

1760

``

1781

1761

`# ---------------------------------------------------------------------

`

1782

1762

`# Special Offset Classes

`

`@@ -2245,7 +2225,8 @@ def eq(self, other):

`

2245

2225

`if isinstance(other, Tick):

`

2246

2226

`return self.delta == other.delta

`

2247

2227

`else:

`

2248

``

`-

return DateOffset.eq(self, other)

`

``

2228

`+

TODO: Are there cases where this should raise TypeError?

`

``

2229

`+

return False

`

2249

2230

``

2250

2231

`# This is identical to DateOffset.hash, but has to be redefined here

`

2251

2232

`# for Python 3, because we've redefined eq.

`

`@@ -2261,7 +2242,8 @@ def ne(self, other):

`

2261

2242

`if isinstance(other, Tick):

`

2262

2243

`return self.delta != other.delta

`

2263

2244

`else:

`

2264

``

`-

return DateOffset.ne(self, other)

`

``

2245

`+

TODO: Are there cases where this should raise TypeError?

`

``

2246

`+

return True

`

2265

2247

``

2266

2248

`@property

`

2267

2249

`def delta(self):

`