Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BUG: .iloc[:] and .loc[:] return a copy of the original object #13873 #16443

Merged
merged 13 commits into from
Jun 14, 2017
Merged
1 change: 1 addition & 0 deletions doc/source/whatsnew/v0.21.0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 7 additions & 3 deletions pandas/core/indexing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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):
Expand Down
18 changes: 18 additions & 0 deletions pandas/tests/indexing/test_iloc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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])
25 changes: 25 additions & 0 deletions pandas/tests/indexing/test_loc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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])