Repository: pandas-dev/pandas
Pull request: BUG - anchoring dates for resample with Day(n>1)
- [ ] closes #24127
- [ ] tests added / passed
- [ ] passes `git diff upstream/master -u -- "*.py" | flake8 --diff`
- [ ] whatsnew entry


Problem statement:
pandas.DataFrame.resample  behaves inconsistently when the rule is set to 24H/1D and 48H/2D
#### Code Sample, a copy-pastable example if possible

```python
>>> pd.__version__
'0.23.4'
>>> index = pd.date_range('11/11/2000 06:00:00', periods=50, freq='H')
>>> series = pd.Series(range(50), index=index)
>>> series.head(10)
2000-11-11 06:00:00    0
2000-11-11 07:00:00    1
2000-11-11 08:00:00    2
2000-11-11 09:00:00    3
2000-11-11 10:00:00    4
2000-11-11 11:00:00    5
2000-11-11 12:00:00    6
2000-11-11 13:00:00    7
2000-11-11 14:00:00    8
2000-11-11 15:00:00    9
Freq: H, dtype: int64
>>> series.resample('24H').count()
2000-11-11    18
2000-11-12    24
2000-11-13     8
Freq: 24H, dtype: int64
>>> series.resample('1D').count()
2000-11-11    18
2000-11-12    24
2000-11-13     8
Freq: D, dtype: int64
>>> series.resample('48H').count()
2000-11-11    42
2000-11-13     8
Freq: 48H, dtype: int64
>>> series.resample('2D').count()
2000-11-11 06:00:00    48
2000-11-13 06:00:00     2
dtype: int64

```
#### Problem description

As you can see, when I set rule to '24H' or '1D', the behavior of resample is consistent (in both cases it starts at 0 o'clock on the first day), but for '48H' and '2D' it's obviously not.

#### Expected Output
If the behavior consistency of ‘24H’ and ‘1D’ is correct, then the behaviors of '48H' and '2D' should also be consistent.
#### Output of ``pd.show_versions()``

<details>

commit: None
python: 3.6.6.final.0
python-bits: 64
OS: Windows
OS-release: 10
machine: AMD64
processor: Intel64 Family 6 Model 158 Stepping 9, GenuineIntel
byteorder: little
LC_ALL: None
LANG: None
LOCALE: None.None
pandas: 0.23.4
pytest: None
pip: 18.1
setuptools: 40.4.3
Cython: None
numpy: 1.15.2
scipy: 1.1.0
pyarrow: None
xarray: None
IPython: None
sphinx: None
patsy: None
dateutil: 2.7.3
pytz: 2018.5
blosc: None
bottleneck: None
tables: None
numexpr: None
feather: None
matplotlib: None
openpyxl: None
xlrd: None
xlwt: None
xlsxwriter: None
lxml: None
bs4: None
html5lib: None
sqlalchemy: None
pymysql: None
psycopg2: None
jinja2: None
s3fs: None
fastparquet: None
pandas_gbq: None
pandas_datareader: None

</details>

Hints from issue discussion:
Seems the issue is in `pandas.core.resample._get_range_edges`, line 1591.

I think `day_nanos % offset.nanos == 0` should be the other way around. This will only ever evaluate to True for Day(n=1). I’m guessing this wasn’t the inention as it would be overly complicated compared to `day_nanos == offset.nanos`.
Added the fix in this commit since I was already updating _get_range_edges. Tests pass and now gives the expected output.

https://github.com/pandas-dev/pandas/pull/23941/commits/eb0550195b00ae18582b7353aab6710f1c97f4f0

Edit: It was suggested that a new PR be opened for this (#24195)

Commit message: CLN - parametrize tests and add issue reference

Patch to review:
diff --git a/doc/source/whatsnew/v0.24.0.rst b/doc/source/whatsnew/v0.24.0.rst
index 0b2b526dfe9e7..488971c13508a 100644
--- a/doc/source/whatsnew/v0.24.0.rst
+++ b/doc/source/whatsnew/v0.24.0.rst
@@ -1516,6 +1516,7 @@ Groupby/Resample/Rolling
 
 - Bug in :func:`pandas.core.groupby.GroupBy.first` and :func:`pandas.core.groupby.GroupBy.last` with ``as_index=False`` leading to the loss of timezone information (:issue:`15884`)
 - Bug in :meth:`DatetimeIndex.resample` when downsampling across a DST boundary (:issue:`8531`)
+- Bug in date anchoring for :meth:`DatetimeIndex.resample` with offset :class:`Day` when n > 1 (:issue:`24127`)
 - Bug where ``ValueError`` is wrongly raised when calling :func:`~pandas.core.groupby.SeriesGroupBy.count` method of a
   ``SeriesGroupBy`` when the grouping variable only contains NaNs and numpy version < 1.13 (:issue:`21956`).
 - Multiple bugs in :func:`pandas.core.Rolling.min` with ``closed='left'`` and a
diff --git a/pandas/core/resample.py b/pandas/core/resample.py
index dc1f94c479a37..6d80d747f21b3 100644
--- a/pandas/core/resample.py
+++ b/pandas/core/resample.py
@@ -1587,8 +1587,8 @@ def _get_range_edges(first, last, offset, closed='left', base=0):
         is_day = isinstance(offset, Day)
         day_nanos = delta_to_nanoseconds(timedelta(1))
 
-        # #1165
-        if (is_day and day_nanos % offset.nanos == 0) or not is_day:
+        # #1165 and #24127
+        if (is_day and not offset.nanos % day_nanos) or not is_day:
             return _adjust_dates_anchored(first, last, offset,
                                           closed=closed, base=base)
 
diff --git a/pandas/tests/groupby/test_timegrouper.py b/pandas/tests/groupby/test_timegrouper.py
index 183ccfb5182a2..cb7b419710837 100644
--- a/pandas/tests/groupby/test_timegrouper.py
+++ b/pandas/tests/groupby/test_timegrouper.py
@@ -43,8 +43,8 @@ def test_groupby_with_timegrouper(self):
 
             expected = DataFrame(
                 {'Quantity': 0},
-                index=date_range('20130901 13:00:00',
-                                 '20131205 13:00:00', freq='5D',
+                index=date_range('20130901',
+                                 '20131205', freq='5D',
                                  name='Date', closed='left'))
             expected.iloc[[0, 6, 18], 0] = np.array([24, 6, 9], dtype='int64')
 
diff --git a/pandas/tests/resample/test_datetime_index.py b/pandas/tests/resample/test_datetime_index.py
index b287eb468cd94..992af07e55af8 100644
--- a/pandas/tests/resample/test_datetime_index.py
+++ b/pandas/tests/resample/test_datetime_index.py
@@ -1463,3 +1463,25 @@ def f(data, add_arg):
         result = df.groupby("A").resample("D").agg(f, multiplier)
         expected = df.groupby("A").resample('D').mean().multiply(multiplier)
         assert_frame_equal(result, expected)
+
+    @pytest.mark.parametrize('k', [1, 2, 3])
+    @pytest.mark.parametrize('n1, freq1, n2, freq2', [
+        (60, 'Min', 1, 'H'),
+        (1440, 'Min', 1, 'D'),
+        (24, 'H', 1, 'D'),
+        (60, 'S', 1, 'Min'),
+        (3600, 'S', 1, 'H'),
+        (86400, 'S', 1, 'D')
+    ])
+    def test_resample_equivalent_offsets(self, n1, freq1, n2, freq2, k):
+        # GH 24127
+        n1_ = n1 * k
+        n2_ = n2 * k
+        s = pd.Series(0, index=pd.date_range('19910905 13:00',
+                                             '19911005 07:00',
+                                             freq=freq1))
+        s = s + range(len(s))
+
+        result1 = s.resample(str(n1_) + freq1).mean()
+        result2 = s.resample(str(n2_) + freq2).mean()
+        assert_series_equal(result1, result2)


Code review comments:
diff_hunk: @@ -1463,3 +1463,25 @@ def f(data, add_arg):
         result = df.groupby("A").resample("D").agg(f, multiplier)
         expected = df.groupby("A").resample('D').mean().multiply(multiplier)
         assert_frame_equal(result, expected)
+
+    @pytest.mark.parametrize('k', [1, 2, 3])
+    @pytest.mark.parametrize('n1, freq1, n2, freq2', [
+        (60, 'Min', 1, 'H'),
+        (1440, 'Min', 1, 'D'),
+        (24, 'H', 1, 'D'),
+        (60, 'S', 1, 'Min'),
+        (3600, 'S', 1, 'H'),
+        (86400, 'S', 1, 'D')
path: pandas/tests/resample/test_datetime_index.py
text: @user1:
Could you also add a frequency where the n=0.5? e.g. 0.5D and 12 H

@author:
done.