Repository: pandas-dev/pandas

Issue:
DataFrame/Series.tz_convert with copy=False modifies original data
Hi. Not sure if it is a bug, or something which needs to be clarified. 

Consider the code

```
s = pd.Series(np.arange(0,5), index=pd.date_range('20131027', periods=5, freq='1H', tz='Europe/Berlin'))
s.tz_convert("UTC", copy=False)
```

`s` index is still the same as before.  If i do the same for frames.

```
d = pd.DataFrame(s)
d.tz_convert("UTC", copy=False)
```

This time index of `d` has changed. From the code it is not clear if `DataFrame` is doing the right thing either.

So is it a bug or is it just inconsistent, or is it an intention?

Update:

```
In [1]: s = pd.Series(np.arange(0,5), index=pd.date_range('20131027', periods=5, freq='1H', tz='Europe/Berlin'))
      s.tz_convert("UTC", copy=False)
Out[1]: 
2013-10-26 22:00:00+00:00    0
2013-10-26 23:00:00+00:00    1
2013-10-27 00:00:00+00:00    2
2013-10-27 01:00:00+00:00    3
2013-10-27 02:00:00+00:00    4
Freq: H, dtype: int32

In [2]: s
Out[2]: 
2013-10-27 00:00:00+02:00    0
2013-10-27 01:00:00+02:00    1
2013-10-27 02:00:00+02:00    2
2013-10-27 02:00:00+01:00    3
2013-10-27 03:00:00+01:00    4
Freq: H, dtype: int32

In [3]: d = pd.DataFrame(s)
      d.tz_convert("UTC", copy=False)
Out[3]: 
2013-10-26 22:00:00+00:00 0
2013-10-26 23:00:00+00:00 1
2013-10-27 00:00:00+00:00 2
2013-10-27 01:00:00+00:00 3
2013-10-27 02:00:00+00:00 4

In [214]: d
Out[214]: 
2013-10-26 22:00:00+00:00 0
2013-10-26 23:00:00+00:00 1
2013-10-27 00:00:00+00:00 2
2013-10-27 01:00:00+00:00 3
2013-10-27 02:00:00+00:00 4
```



Discussion:
looks ok in 0.13.1..

```
In [1]: pd.__version__
Out[1]: '0.13.1'

In [2]: s = pd.Series(np.arange(0,5), index=pd.date_range('20131027', periods=5, freq='1H', tz='Europe/Berlin'))

In [3]: s.tz_convert("UTC", copy=False)
Out[3]: 
2013-10-26 22:00:00+00:00    0
2013-10-26 23:00:00+00:00    1
2013-10-27 00:00:00+00:00    2
2013-10-27 01:00:00+00:00    3
2013-10-27 02:00:00+00:00    4
Freq: H, dtype: int64

In [4]: d = pd.DataFrame(s)

In [5]: d.tz_convert("UTC", copy=False)
Out[5]: 
                           0
2013-10-26 22:00:00+00:00  0
2013-10-26 23:00:00+00:00  1
2013-10-27 00:00:00+00:00  2
2013-10-27 01:00:00+00:00  3
2013-10-27 02:00:00+00:00  4

[5 rows x 1 columns]
```

If you inspect `s`. it hasn't changed under 0.13.1.

```
>>> s = pd.Series(np.arange(0,5), index=pd.date_range('20131027', periods=5, freq='1H', tz='Europe/Berlin'))
>>> s.tz_convert(None, copy=False)
2013-10-26 22:00:00    0
2013-10-26 23:00:00    1
2013-10-27 00:00:00    2
2013-10-27 01:00:00    3
2013-10-27 02:00:00    4
Freq: H, dtype: int32
>>> s
2013-10-27 00:00:00+02:00    0
2013-10-27 01:00:00+02:00    1
2013-10-27 02:00:00+02:00    2
2013-10-27 02:00:00+01:00    3
2013-10-27 03:00:00+01:00    4
Freq: H, dtype: int32
>>> 
```

why would you expect `s` to change? most pandas methods return a new object

the `copy` flag is just tries not to actually copy the index if it doesn't need to; in this case it does so its irrelevant

`DataFrame` does change though. If you inspect `d` it will have a new index. It might be that index is part of the data in the `DataFrame`, it's just the behaviour is inconsistent. 

Updated the comment above with the output.

ahh..ok....will mark as a bug...thanks for the report

@hendrics 

pls run this again on master....I am pretty sure this is fixed (and if you want to add an explicty test for this, would be gr8)

see here: https://github.com/pydata/pandas/commit/b1687b819320e0f0d64eda58ddf6971aaebdc3fe

The Series case is actually wrong now as well.

```
In [13]: s = pd.Series(np.arange(0,5), index=pd.date_range('20131027', periods=5, freq='1H', tz='Europe/Berlin'))

In [14]: s
Out[14]:
2013-10-27 00:00:00+02:00    0
2013-10-27 01:00:00+02:00    1
2013-10-27 02:00:00+02:00    2
2013-10-27 02:00:00+01:00    3
2013-10-27 03:00:00+01:00    4
Freq: H, dtype: int64

In [15]: s.tz_convert('UTC', copy=False)
Out[15]:
2013-10-26 22:00:00+00:00    0
2013-10-26 23:00:00+00:00    1
2013-10-27 00:00:00+00:00    2
2013-10-27 01:00:00+00:00    3
2013-10-27 02:00:00+00:00    4
Freq: H, dtype: int64

In [16]: s
Out[16]:
2013-10-26 22:00:00+00:00    0
2013-10-26 23:00:00+00:00    1
2013-10-27 00:00:00+00:00    2
2013-10-27 01:00:00+00:00    3
2013-10-27 02:00:00+00:00    4
Freq: H, dtype: int64

In [17]: pd.__version__
Out[17]: '0.24.0.dev0+1505.gcb31b2b09.dirty'
```

Resolving patch:
diff --git a/pandas/core/generic.py b/pandas/core/generic.py
--- a/pandas/core/generic.py
+++ b/pandas/core/generic.py
@@ -9226,7 +9226,7 @@ def _tz_convert(ax, tz):
             ax = _tz_convert(ax, tz)
 
         result = self._constructor(self._data, copy=copy)
-        result.set_axis(ax, axis=axis, inplace=True)
+        result = result.set_axis(ax, axis=axis, inplace=False)
         return result.__finalize__(self)
 
     def tz_localize(self, tz, axis=0, level=None, copy=True,
@@ -9390,7 +9390,7 @@ def _tz_localize(ax, tz, ambiguous, nonexistent):
             ax = _tz_localize(ax, tz, ambiguous, nonexistent)
 
         result = self._constructor(self._data, copy=copy)
-        result.set_axis(ax, axis=axis, inplace=True)
+        result = result.set_axis(ax, axis=axis, inplace=False)
         return result.__finalize__(self)
 
     # ----------------------------------------------------------------------


Test patch:
diff --git a/pandas/tests/frame/test_timezones.py b/pandas/tests/frame/test_timezones.py
--- a/pandas/tests/frame/test_timezones.py
+++ b/pandas/tests/frame/test_timezones.py
@@ -180,3 +180,19 @@ def test_boolean_compare_transpose_tzindex_with_dst(self, tz):
         result = df.T == df.T
         expected = DataFrame(True, index=list('ab'), columns=idx)
         tm.assert_frame_equal(result, expected)
+
+    @pytest.mark.parametrize('copy', [True, False])
+    @pytest.mark.parametrize('method, tz', [
+        ['tz_localize', None],
+        ['tz_convert', 'Europe/Berlin']
+    ])
+    def test_tz_localize_convert_copy_inplace_mutate(self, copy, method, tz):
+        # GH 6326
+        result = DataFrame(np.arange(0, 5),
+                           index=date_range('20131027', periods=5,
+                                            freq='1H', tz=tz))
+        getattr(result, method)('UTC', copy=copy)
+        expected = DataFrame(np.arange(0, 5),
+                             index=date_range('20131027', periods=5,
+                                              freq='1H', tz=tz))
+        tm.assert_frame_equal(result, expected)
diff --git a/pandas/tests/series/test_timezones.py b/pandas/tests/series/test_timezones.py
--- a/pandas/tests/series/test_timezones.py
+++ b/pandas/tests/series/test_timezones.py
@@ -348,3 +348,19 @@ def test_series_truncate_datetimeindex_tz(self):
         result = s.truncate(datetime(2005, 4, 2), datetime(2005, 4, 4))
         expected = Series([1, 2, 3], index=idx[1:4])
         tm.assert_series_equal(result, expected)
+
+    @pytest.mark.parametrize('copy', [True, False])
+    @pytest.mark.parametrize('method, tz', [
+        ['tz_localize', None],
+        ['tz_convert', 'Europe/Berlin']
+    ])
+    def test_tz_localize_convert_copy_inplace_mutate(self, copy, method, tz):
+        # GH 6326
+        result = Series(np.arange(0, 5),
+                        index=date_range('20131027', periods=5, freq='1H',
+                                         tz=tz))
+        getattr(result, method)('UTC', copy=copy)
+        expected = Series(np.arange(0, 5),
+                          index=date_range('20131027', periods=5, freq='1H',
+                                           tz=tz))
+        tm.assert_series_equal(result, expected)