Repository: pandas-dev/pandas
Pull request: BUG: Preserve name in DatetimeIndex.snap
Use self.name to construct the result of `DatetimeIndex.snap`, fix #25575.

- [x] closes #25575
- [x] tests added / passed
- [x] passes `git diff upstream/master -u -- "*.py" | flake8 --diff`
- [x] whatsnew entry


Problem statement:
Snap drops name from datetimeindex
#### Code Sample, a copy-pastable example if possible

```python

import pandas as pd

dti = pd.DatetimeIndex([], name='my_dti', freq='H')
print(dti.round('D'))  # Name is preserved
print(dti.snap('D'))  # Name is lost

```
#### Problem description

The DatetimeIndex.snap method loses the name of the DatetimeIndex which is a different behaviour than DatetimeIndex.round (or floor, ceil, etc.) that preserves the name of the DatetimeIndex.

#### Expected Output

DatetimeIndex([], dtype='datetime64[ns]', name='my_dti', freq='D')

#### Output of ``pd.show_versions()``

<details>

INSTALLED VERSIONS
------------------
commit: None
python: 3.6.7.final.0
python-bits: 64
OS: Linux
OS-release: 4.15.0-45-generic
machine: x86_64
processor: x86_64
byteorder: little
LC_ALL: None
LANG: en_CA.UTF-8
LOCALE: en_CA.UTF-8

pandas: 0.24.1
pytest: 4.0.0
pip: 19.0.3
setuptools: 40.4.3
Cython: 0.29.6
numpy: 1.16.2
scipy: 1.2.1
pyarrow: None
xarray: None
IPython: 7.3.0
sphinx: None
patsy: 0.5.0
dateutil: 2.7.3
pytz: 2018.3
blosc: None
bottleneck: None
tables: None
numexpr: None
feather: None
matplotlib: 3.0.3
openpyxl: None
xlrd: None
xlwt: None
xlsxwriter: None
lxml.etree: 4.2.1
bs4: None
html5lib: 1.0.1
sqlalchemy: 1.2.18
pymysql: None
psycopg2: 2.7.7 (dt dec pq3 ext lo64)
jinja2: 2.10
s3fs: None
fastparquet: None
pandas_gbq: 0.9.0
pandas_datareader: None
gcsfs: None


</details>

Hints from issue discussion:
Thanks for the report. There's actually a comment in the code that notes that this is missing (as well as tz). PR's welcome!

https://github.com/pandas-dev/pandas/blob/3e652ac565e054b763ccbe23006c02704f6cd1b1/pandas/core/indexes/datetimes.py#L790-L791
How does snap actually work?

pd.date_range(start='2019-1-1', end='2019-1-2', freq='10T').snap('H')

Doesn't return what I expect. Is this a bigger bug?

Commit message: Use self.name to construct the result of `DatetimeIndex.snap`, fix #25575.

Signed-off-by: HE, Tao <sighingnow@gmail.com>

Patch to review:
diff --git a/doc/source/whatsnew/v0.24.2.rst b/doc/source/whatsnew/v0.24.2.rst
index 4ca9d57f3a2e5..3cbbcf487ae84 100644
--- a/doc/source/whatsnew/v0.24.2.rst
+++ b/doc/source/whatsnew/v0.24.2.rst
@@ -31,6 +31,7 @@ Fixed Regressions
 - Fixed regression in ``IntervalDtype`` construction where passing an incorrect string with 'Interval' as a prefix could result in a ``RecursionError``. (:issue:`25338`)
 - Fixed regression in :class:`Categorical`, where constructing it from a categorical ``Series`` and an explicit ``categories=`` that differed from that in the ``Series`` created an invalid object which could trigger segfaults. (:issue:`25318`)
 - Fixed pip installing from source into an environment without NumPy (:issue:`25193`)
+- Fixed name preserving bug of :meth:`DatetimeIndex.snap` (:issue:`25575`)
 
 .. _whatsnew_0242.enhancements:
 
diff --git a/pandas/core/indexes/datetimes.py b/pandas/core/indexes/datetimes.py
index b8d052ce7be04..1f16d95ea1ac0 100644
--- a/pandas/core/indexes/datetimes.py
+++ b/pandas/core/indexes/datetimes.py
@@ -787,8 +787,7 @@ def snap(self, freq='S'):
             snapped[i] = s
 
         # we know it conforms; skip check
-        return DatetimeIndex._simple_new(snapped, freq=freq)
-        # TODO: what about self.name?  tz? if so, use shallow_copy?
+        return DatetimeIndex._simple_new(snapped, name=self.name, freq=freq)
 
     def join(self, other, how='left', level=None, return_indexers=False,
              sort=False):
diff --git a/pandas/tests/series/indexing/test_datetime.py b/pandas/tests/series/indexing/test_datetime.py
index 0efc9feb0dbd4..e88e496b271cb 100644
--- a/pandas/tests/series/indexing/test_datetime.py
+++ b/pandas/tests/series/indexing/test_datetime.py
@@ -54,12 +54,14 @@ def test_fancy_setitem():
 
 def test_dti_snap():
     dti = DatetimeIndex(['1/1/2002', '1/2/2002', '1/3/2002', '1/4/2002',
-                         '1/5/2002', '1/6/2002', '1/7/2002'], freq='D')
+                         '1/5/2002', '1/6/2002', '1/7/2002'],
+                        name='my_dti', freq='D')
 
     res = dti.snap(freq='W-MON')
-    exp = date_range('12/31/2001', '1/7/2002', freq='w-mon')
+    exp = date_range('12/31/2001', '1/7/2002', name='my_dti', freq='w-mon')
     exp = exp.repeat([3, 4])
     assert (res == exp).all()
+    assert res.name == exp.name
 
     res = dti.snap(freq='B')
 


Code review comments:
diff_hunk: @@ -54,12 +54,14 @@ def test_fancy_setitem():
 
 def test_dti_snap():
     dti = DatetimeIndex(['1/1/2002', '1/2/2002', '1/3/2002', '1/4/2002',
-                         '1/5/2002', '1/6/2002', '1/7/2002'], freq='D')
+                         '1/5/2002', '1/6/2002', '1/7/2002'],
+                        name='my_dti', freq='D')
 
     res = dti.snap(freq='W-MON')
-    exp = date_range('12/31/2001', '1/7/2002', freq='w-mon')
+    exp = date_range('12/31/2001', '1/7/2002', name='my_dti', freq='w-mon')
     exp = exp.repeat([3, 4])
     assert (res == exp).all()
path: pandas/tests/series/indexing/test_datetime.py
text: @user1:
can you change this test to instead use assert_index_equal 

@author:
Fixed.

diff_hunk: @@ -787,8 +787,7 @@ def snap(self, freq='S'):
             snapped[i] = s
 
         # we know it conforms; skip check
-        return DatetimeIndex._simple_new(snapped, freq=freq)
-        # TODO: what about self.name?  tz? if so, use shallow_copy?
path: pandas/core/indexes/datetimes.py
text: @user1:
Can you also address the `tz` portion of the TODO, as that also appears to not be preserved?

@user2:
Is there a reason we couldn't address that issue here (in a separate commit) ?

@user1:
@user2 : a little unclear on my part, but that's what I meant; I think it should be addressed in this PR as I think the fix should be the same as with `name` and just be a matter of adding `tz=self.tz` to the return.

@author:
Fixed. Thanks for point the `tz` out to me!

diff_hunk: @@ -31,6 +31,7 @@ Fixed Regressions
 - Fixed regression in ``IntervalDtype`` construction where passing an incorrect string with 'Interval' as a prefix could result in a ``RecursionError``. (:issue:`25338`)
 - Fixed regression in :class:`Categorical`, where constructing it from a categorical ``Series`` and an explicit ``categories=`` that differed from that in the ``Series`` created an invalid object which could trigger segfaults. (:issue:`25318`)
 - Fixed pip installing from source into an environment without NumPy (:issue:`25193`)
+- Fixed name preserving bug of :meth:`DatetimeIndex.snap` (:issue:`25575`)
path: doc/source/whatsnew/v0.24.2.rst
text: @user1:
move to 0.25.0

Bug in ....snap.....not preserving the `name` of the input ``Index``

diff_hunk: @@ -54,12 +54,14 @@ def test_fancy_setitem():
 
 def test_dti_snap():
     dti = DatetimeIndex(['1/1/2002', '1/2/2002', '1/3/2002', '1/4/2002',
-                         '1/5/2002', '1/6/2002', '1/7/2002'], freq='D')
+                         '1/5/2002', '1/6/2002', '1/7/2002'],
+                        name='my_dti', freq='D')
path: pandas/tests/series/indexing/test_datetime.py
text: @user2:
Can you parametrize over `name` so that we test both cases where there is and is not a name?  So for `name` you'd basically use: `@user1.mark.parametrize('name', [None, 'my_dti'])`. And similar for `tz` if you fix that too?

@author:
Fixed.