diff --git a/doc/source/whatsnew/v0.21.0.txt b/doc/source/whatsnew/v0.21.0.txt index 79f2816f43a6f..d6b699abdba2d 100644 --- a/doc/source/whatsnew/v0.21.0.txt +++ b/doc/source/whatsnew/v0.21.0.txt @@ -97,6 +97,7 @@ Conversion Indexing ^^^^^^^^ +- When called with a null slice (e.g. ``df.iloc[:]``), the``iloc`` and ``loc`` indexers return a shallow copy of the original object. Previously they returned the original object. (:issue:`13873`). I/O diff --git a/pandas/core/indexing.py b/pandas/core/indexing.py index 50f2f9b52e111..ae0aaf98fdf02 100755 --- a/pandas/core/indexing.py +++ b/pandas/core/indexing.py @@ -988,6 +988,10 @@ def _getitem_lowerdim(self, tup): if len(new_key) == 1: new_key, = new_key + # Slices should return views, but calling iloc/loc with a null + # slice returns a new object. + if is_null_slice(new_key): + return section # This is an elided recursive call to iloc/loc/etc' return getattr(section, self.name)[new_key] @@ -1250,7 +1254,7 @@ def _get_slice_axis(self, slice_obj, axis=0): obj = self.obj if not need_slice(slice_obj): - return obj + return obj.copy(deep=False) indexer = self._convert_slice_indexer(slice_obj, axis) if isinstance(indexer, slice): @@ -1349,7 +1353,7 @@ def _get_slice_axis(self, slice_obj, axis=0): """ this is pretty simple as we just have to deal with labels """ obj = self.obj if not need_slice(slice_obj): - return obj + return obj.copy(deep=False) labels = obj._get_axis(axis) indexer = labels.slice_indexer(slice_obj.start, slice_obj.stop, @@ -1690,7 +1694,7 @@ def _get_slice_axis(self, slice_obj, axis=0): obj = self.obj if not need_slice(slice_obj): - return obj + return obj.copy(deep=False) slice_obj = self._convert_slice_indexer(slice_obj, axis) if isinstance(slice_obj, slice): diff --git a/pandas/tests/indexing/test_iloc.py b/pandas/tests/indexing/test_iloc.py index af4b9e1f0cc25..769cf8ec395dd 100644 --- a/pandas/tests/indexing/test_iloc.py +++ b/pandas/tests/indexing/test_iloc.py @@ -591,3 +591,21 @@ def test_iloc_empty_list_indexer_is_ok(self): tm.assert_frame_equal(df.iloc[[]], df.iloc[:0, :], check_index_type=True, check_column_type=True) + + def test_identity_slice_returns_new_object(self): + # GH13873 + original_df = DataFrame({'a': [1, 2, 3]}) + sliced_df = original_df.iloc[:] + assert sliced_df is not original_df + + # should be a shallow copy + original_df['a'] = [4, 4, 4] + assert (sliced_df['a'] == 4).all() + + original_series = Series([1, 2, 3, 4, 5, 6]) + sliced_series = original_series.iloc[:] + assert sliced_series is not original_series + + # should also be a shallow copy + original_series[:3] = [7, 8, 9] + assert all(sliced_series[:3] == [7, 8, 9]) diff --git a/pandas/tests/indexing/test_loc.py b/pandas/tests/indexing/test_loc.py index fe2318be72eda..3e863a59df67e 100644 --- a/pandas/tests/indexing/test_loc.py +++ b/pandas/tests/indexing/test_loc.py @@ -630,3 +630,28 @@ def test_loc_empty_list_indexer_is_ok(self): tm.assert_frame_equal(df.loc[[]], df.iloc[:0, :], check_index_type=True, check_column_type=True) + + def test_identity_slice_returns_new_object(self): + # GH13873 + original_df = DataFrame({'a': [1, 2, 3]}) + sliced_df = original_df.loc[:] + assert sliced_df is not original_df + assert original_df[:] is not original_df + + # should be a shallow copy + original_df['a'] = [4, 4, 4] + assert (sliced_df['a'] == 4).all() + + # These should not return copies + assert original_df is original_df.loc[:, :] + df = DataFrame(np.random.randn(10, 4)) + assert df[0] is df.loc[:, 0] + + # Same tests for Series + original_series = Series([1, 2, 3, 4, 5, 6]) + sliced_series = original_series.loc[:] + assert sliced_series is not original_series + assert original_series[:] is not original_series + + original_series[:3] = [7, 8, 9] + assert all(sliced_series[:3] == [7, 8, 9])