@@ -66,6 +66,7 @@ class Resampler(_GroupBy, ShallowMixin):
66
66
"convention" ,
67
67
"loffset" ,
68
68
"base" ,
69
+ "adjust_timestamp" ,
69
70
"kind" ,
70
71
]
71
72
@@ -1309,6 +1310,7 @@ class TimeGrouper(Grouper):
1309
1310
"kind" ,
1310
1311
"convention" ,
1311
1312
"base" ,
1313
+ "adjust_timestamp" ,
1312
1314
)
1313
1315
1314
1316
def __init__ (
@@ -1323,6 +1325,7 @@ def __init__(
1323
1325
loffset = None ,
1324
1326
kind = None ,
1325
1327
convention = None ,
1328
+ adjust_timestamp = None ,
1326
1329
base = 0 ,
1327
1330
** kwargs ,
1328
1331
):
@@ -1365,6 +1368,9 @@ def __init__(
1365
1368
self .fill_method = fill_method
1366
1369
self .limit = limit
1367
1370
self .base = base
1371
+ if adjust_timestamp is not None :
1372
+ adjust_timestamp = Timestamp (adjust_timestamp , "ns" )
1373
+ self .adjust_timestamp = adjust_timestamp
1368
1374
1369
1375
# always sort time groupers
1370
1376
kwargs ["sort" ] = True
@@ -1424,7 +1430,12 @@ def _get_time_bins(self, ax):
1424
1430
return binner , [], labels
1425
1431
1426
1432
first , last = _get_timestamp_range_edges (
1427
- ax .min (), ax .max (), self .freq , closed = self .closed , base = self .base
1433
+ ax .min (),
1434
+ ax .max (),
1435
+ self .freq ,
1436
+ closed = self .closed ,
1437
+ base = self .base ,
1438
+ adjust_timestamp = self .adjust_timestamp ,
1428
1439
)
1429
1440
# GH #12037
1430
1441
# use first/last directly instead of call replace() on them
@@ -1562,10 +1573,15 @@ def _get_period_bins(self, ax):
1562
1573
bin_shift = 0
1563
1574
1564
1575
# GH 23882
1565
- if self .base :
1576
+ if self .base or self . adjust_timestamp :
1566
1577
# get base adjusted bin edge labels
1567
1578
p_start , end = _get_period_range_edges (
1568
- start , end , self .freq , closed = self .closed , base = self .base
1579
+ start ,
1580
+ end ,
1581
+ self .freq ,
1582
+ closed = self .closed ,
1583
+ base = self .base ,
1584
+ adjust_timestamp = self .adjust_timestamp ,
1569
1585
)
1570
1586
1571
1587
# Get offset for bin edge (not label edge) adjustment
@@ -1617,7 +1633,9 @@ def _take_new_index(obj, indexer, new_index, axis=0):
1617
1633
raise ValueError ("'obj' should be either a Series or a DataFrame" )
1618
1634
1619
1635
1620
- def _get_timestamp_range_edges (first , last , offset , closed = "left" , base = 0 ):
1636
+ def _get_timestamp_range_edges (
1637
+ first , last , offset , closed = "left" , base = 0 , adjust_timestamp = None
1638
+ ):
1621
1639
"""
1622
1640
Adjust the `first` Timestamp to the preceding Timestamp that resides on
1623
1641
the provided offset. Adjust the `last` Timestamp to the following
@@ -1637,6 +1655,9 @@ def _get_timestamp_range_edges(first, last, offset, closed="left", base=0):
1637
1655
Which side of bin interval is closed.
1638
1656
base : int, default 0
1639
1657
The "origin" of the adjusted Timestamps.
1658
+ adjust_timestamp : pd.Timestamp, default None
1659
+ The timestamp on which to adjust the grouping. If None is passed, the
1660
+ first day of the time series at midnight is used.
1640
1661
1641
1662
Returns
1642
1663
-------
@@ -1652,7 +1673,12 @@ def _get_timestamp_range_edges(first, last, offset, closed="left", base=0):
1652
1673
last = last .tz_localize (None )
1653
1674
1654
1675
first , last = _adjust_dates_anchored (
1655
- first , last , offset , closed = closed , base = base
1676
+ first ,
1677
+ last ,
1678
+ offset ,
1679
+ closed = closed ,
1680
+ base = base ,
1681
+ adjust_timestamp = adjust_timestamp ,
1656
1682
)
1657
1683
if isinstance (offset , Day ):
1658
1684
first = first .tz_localize (tz )
@@ -1673,7 +1699,9 @@ def _get_timestamp_range_edges(first, last, offset, closed="left", base=0):
1673
1699
return first , last
1674
1700
1675
1701
1676
- def _get_period_range_edges (first , last , offset , closed = "left" , base = 0 ):
1702
+ def _get_period_range_edges (
1703
+ first , last , offset , closed = "left" , base = 0 , adjust_timestamp = None
1704
+ ):
1677
1705
"""
1678
1706
Adjust the provided `first` and `last` Periods to the respective Period of
1679
1707
the given offset that encompasses them.
@@ -1690,6 +1718,9 @@ def _get_period_range_edges(first, last, offset, closed="left", base=0):
1690
1718
Which side of bin interval is closed.
1691
1719
base : int, default 0
1692
1720
The "origin" of the adjusted Periods.
1721
+ adjust_timestamp : pd.Timestamp, default None
1722
+ The timestamp on which to adjust the grouping. If None is passed, the
1723
+ first day of the time series at midnight is used.
1693
1724
1694
1725
Returns
1695
1726
-------
@@ -1705,37 +1736,42 @@ def _get_period_range_edges(first, last, offset, closed="left", base=0):
1705
1736
adjust_last = offset .is_on_offset (last )
1706
1737
1707
1738
first , last = _get_timestamp_range_edges (
1708
- first , last , offset , closed = closed , base = base
1739
+ first , last , offset , closed = closed , base = base , adjust_timestamp = adjust_timestamp
1709
1740
)
1710
1741
1711
1742
first = (first + adjust_first * offset ).to_period (offset )
1712
1743
last = (last - adjust_last * offset ).to_period (offset )
1713
1744
return first , last
1714
1745
1715
1746
1716
- def _adjust_dates_anchored (first , last , offset , closed = "right" , base = 0 ):
1747
+ def _adjust_dates_anchored (
1748
+ first , last , offset , closed = "right" , base = 0 , adjust_timestamp = None
1749
+ ):
1717
1750
# First and last offsets should be calculated from the start day to fix an
1718
1751
# error cause by resampling across multiple days when a one day period is
1719
1752
# not a multiple of the frequency.
1720
1753
#
1721
1754
# See https://github.com/pandas-dev/pandas/issues/8683
1755
+ if adjust_timestamp is None :
1756
+ adjust_timestamp_nanos = first .normalize ().value
1757
+ else :
1758
+ adjust_timestamp_nanos = adjust_timestamp .value
1722
1759
1723
1760
# GH 10117 & GH 19375. If first and last contain timezone information,
1724
1761
# Perform the calculation in UTC in order to avoid localizing on an
1725
1762
# Ambiguous or Nonexistent time.
1726
1763
first_tzinfo = first .tzinfo
1727
1764
last_tzinfo = last .tzinfo
1728
- start_day_nanos = first .normalize ().value
1729
1765
if first_tzinfo is not None :
1730
1766
first = first .tz_convert ("UTC" )
1731
1767
if last_tzinfo is not None :
1732
1768
last = last .tz_convert ("UTC" )
1733
1769
1734
1770
base_nanos = (base % offset .n ) * offset .nanos // offset .n
1735
- start_day_nanos += base_nanos
1771
+ adjust_timestamp_nanos += base_nanos
1736
1772
1737
- foffset = (first .value - start_day_nanos ) % offset .nanos
1738
- loffset = (last .value - start_day_nanos ) % offset .nanos
1773
+ foffset = (first .value - adjust_timestamp_nanos ) % offset .nanos
1774
+ loffset = (last .value - adjust_timestamp_nanos ) % offset .nanos
1739
1775
1740
1776
if closed == "right" :
1741
1777
if foffset > 0 :
0 commit comments