Repository: pandas-dev/pandas
Pull request: BUG: fix mutation of DTI backing Series/DataFrame
Fixes (at least some) verify_integrity bugs discussed in #24074.

- [x] closes #21907
- [x] tests added / passed
- [x] passes `git diff upstream/master -u -- "*.py" | flake8 --diff`
- [ ] whatsnew entry


Problem statement:
pd.Series(dti) shares data with dti --> DatetimeIndex mutated
Appears to be specific to tz-aware

```
dti = pd.date_range('2016-01-01', periods=10, tz='US/Pacific')
ser = pd.Series(dti)
ser[::3] = pd.NaT

>>> dti
DatetimeIndex([                      'NaT', '2016-01-02 00:00:00-08:00',
               '2016-01-03 00:00:00-08:00',                       'NaT',
               '2016-01-05 00:00:00-08:00', '2016-01-06 00:00:00-08:00',
                                     'NaT', '2016-01-08 00:00:00-08:00',
               '2016-01-09 00:00:00-08:00',                       'NaT'],
              dtype='datetime64[ns, US/Pacific]', freq='D')
```

Hints from issue discussion:
The problem is not prevented by passing `copy=True` to `pd.Series`
This may be partially similar to https://github.com/pandas-dev/pandas/issues/6326?

Commit message: typo fixup

Patch to review:
diff --git a/pandas/core/internals/blocks.py b/pandas/core/internals/blocks.py
index 198e832ca4603..d897c4b3c5509 100644
--- a/pandas/core/internals/blocks.py
+++ b/pandas/core/internals/blocks.py
@@ -860,6 +860,11 @@ def setitem(self, indexer, value):
                     b = self.astype(dtype)
                     return b.setitem(indexer, value)
 
+        if (self._holder is not None and
+                issubclass(self._holder, ABCIndexClass)):
+            # avoid altering Index objects in place
+            values = values.copy()
+
         # value must be storeable at this moment
         arr_value = np.array(value)
 
@@ -2923,7 +2928,9 @@ def _try_coerce_result(self, result):
             # allow passing of > 1dim if its trivial
             if result.ndim > 1:
                 result = result.reshape(np.prod(result.shape))
-            result = self.values._shallow_copy(result)
+
+            # new values invalidates a frequency
+            result = self.values._shallow_copy(result, freq=None)
 
         return result
 
diff --git a/pandas/tests/frame/test_block_internals.py b/pandas/tests/frame/test_block_internals.py
index 224e56777f6b4..631d63dc28f11 100644
--- a/pandas/tests/frame/test_block_internals.py
+++ b/pandas/tests/frame/test_block_internals.py
@@ -28,6 +28,37 @@
 
 
 class TestDataFrameBlockInternals():
+    def test_setitem_invalidates_datetime_index_freq(self):
+        # altering a datetime64tz column inplace invalidates the `freq`
+        #  attribute on the underlying DatetimeIndex
+
+        df = DataFrame({'B': date_range('20130101', periods=3,
+                                        tz='US/Eastern')})
+        assert df['B']._values.freq == 'D'
+
+        df.iloc[1, 0] = pd.NaT
+        assert df['B']._values.freq is None
+
+        ser = Series(date_range('20130101', periods=3,
+                                tz='US/Eastern'))
+        ts = ser[1]
+        dti = ser._values
+        assert dti.freq == 'D'
+        ser.iloc[1] = pd.NaT
+        assert ser._values.freq is None
+
+        # check that the DatetimeIndex was not altered in place
+        assert ser._values is not dti
+        assert dti[1] == ts
+
+    def test_dt64tz_setitem_does_not_mutate_dti(self):
+        # GH#21907
+        dti = pd.date_range('2016-01-01', periods=10, tz='US/Pacific')
+        ts = dti[0]
+        ser = pd.Series(dti)
+        ser[::3] = pd.NaT
+        assert ser[0] is pd.NaT
+        assert dti[0] == ts
 
     def test_cast_internals(self, float_frame):
         casted = DataFrame(float_frame._data, dtype=int)


Code review comments:
diff_hunk: @@ -28,6 +28,37 @@
 
 
 class TestDataFrameBlockInternals():
+    def test_setitem_invalidates_datetime_index_freq(self):
+        # altering a datetime64tz column inplace invalidates the `freq`
+        #  attribute on the underlying DatetimeIndex
+
+        df = DataFrame({'B': date_range('20130101', periods=3,
+                                        tz='US/Eastern')})
+        assert df['B']._values.freq == 'D'
+
+        df.iloc[1, 0] = pd.NaT
+        assert df['B']._values.freq is None
+
+        ser = Series(date_range('20130101', periods=3,
+                                tz='US/Eastern'))
+        ts = ser[1]
+        dti = ser._values
+        assert dti.freq == 'D'
+        ser.iloc[1] = pd.NaT
+        assert ser._values.freq is None
+
+        # check that the DatetimeIndex was not altered in place
+        assert ser._values is not dti
+        assert dti[1] == ts
+
+    def test_dt64tz_setitem_does_not_mutate_dti(self):
path: pandas/tests/frame/test_block_internals.py
text: @author:
there is not a series.test_block_internals; not sure if there is somewhere else this might belong

@user1:
pls create one

@author:
sure