Repository: pandas-dev/pandas
Pull request: REGR: DataFrame.__setitem__(slice, val) is positional 
- [x] closes #31469 
- [x] tests added / passed
- [x] passes `black pandas`
- [x] passes `git diff upstream/master -u -- "*.py" | flake8 --diff`
- [x] whatsnew entry


Problem statement:
REGR: __setitem__ with integer slices on Int/RangeIndex is broken (label instead of positional)
There's an backward incompatible change in pandas 1.0 that I didn't find in the changelog. I might have just overlooked it though.

```python
import numpy as np
X = pd.DataFrame(np.zeros((100, 1)))
X[-4:] = 1
X
```
In pandas 0.25.3 or lower, this results in the last four entries of X to be 1 and all the others zero. In pandas 1.0, it results in all entries of X being 1.
I assume it's a change of indexing axis 0 or axis 1?

Hints from issue discussion:
I wonder if it's related to #31449 but I'm not using a multi-index.
Thanks for the report.

Seems this doesn't affect `.iloc`:
```
In [26]: import numpy as np 
    ...: X = pd.DataFrame(np.zeros((5, 1))) 
    ...: X.iloc[-4:] = 1 
    ...: X                                                                      
Out[26]: 
     0
0  0.0
1  1.0
2  1.0
3  1.0
4  1.0
```
will look into it
you are label indexing with a slice with loc 
since none of the labels exist nothing is set

did this actually work previously?

this should never have worked with .loc

it might have with [] which has fallback integer indexing
I do not remember any specific discussion about this, so I think it is definitely a regression. 

Slicing rows in `[]` has always worked positional if there is an integer index (surprising, yes, but longstanding behaviour, see eg my summary of this of 5 years ago https://github.com/pandas-dev/pandas/issues/9595)
> since none of the labels exist nothing is set

@jreback if I've understood correctly, the issue is that _everything_ is being set

```
>>> import numpy as np
>>> import pandas as pd

>>> X = pd.DataFrame(np.zeros((5, 1)))
>>> X                                                                     
     0
0  0.0
1  0.0
2  0.0
3  0.0
4  0.0

>>> X[-4:]  # only prints the last 4 rows of X...
     0
1  0.0
2  0.0
3  0.0
4  0.0

>>> X[-4:] = 1
>>> X  # ...but everything (including the first row) has now been set
     0
0  1.0
1  1.0
2  1.0
3  1.0
4  1.0
```
> I do not remember any specific discussion about this, so I think it is definitely a regression.
> 
> Slicing rows in `[]` has always worked positional if there is an integer index (surprising, yes, but longstanding behaviour, see eg my summary of this of 5 years ago #9595)

maybe but indexing with an out or range label on both sides should return nothing 

so the results are correct
> > since none of the labels exist nothing is set
> 
> @jreback if I've understood correctly, the issue is that _everything_ is being set
> 
> ```
> >>> import numpy as np
> >>> import pandas as pd
> 
> >>> X = pd.DataFrame(np.zeros((5, 1)))
> >>> X                                                                     
>      0
> 0  0.0
> 1  0.0
> 2  0.0
> 3  0.0
> 4  0.0
> 
> >>> X[-4:]  # only prints the last 4 rows of X...
>      0
> 1  0.0
> 2  0.0
> 3  0.0
> 4  0.0
> 
> >>> X[-4:] = 1
> >>> X  # ...but everything (including the first row) has now been set
>      0
> 0  1.0
> 1  1.0
> 2  1.0
> 3  1.0
> 4  1.0
> ```

ahh ok that is not correct; i would expect this indexer to return noting 
might be https://github.com/pandas-dev/pandas/pull/31393
> ahh ok that is not correct; i would expect this indexer to return noting

Asking for the shape, both in 0.25 and 1.0, you get
```
>>> X[-4:].shape
(4, 1)
```
but assignment in version 1.0 assigns to everything.
> maybe but indexing with an out or range label on both sides should return nothing

This is about positional indexing, so there is no "out of range label". The `-4` means start from the fourth last element to the end.

Again, I agree this is surprising behaviour. You would think it is label-based indexing, but it is not. I already described this 5 years in ago #9595. 

Some examples to illustrate this:

```
In [21]: df = pd.DataFrame({'a': [0., 1., 2., 3.]}, index=[2, 3, 4, 5])

In [22]: df 
Out[22]: 
     a
2  0.0
3  1.0
4  2.0
5  3.0

In [23]: df[2:] 
Out[23]: 
     a
4  2.0
5  3.0

In [24]: df[:3]  
Out[24]: 
     a
2  0.0
3  1.0
4  2.0
```

This those examples are for `__getitem__`, and work clearly positionally if you look at the index of the results (and both on 0.25 and 1.0, and for both Int64Index as RangeIndex). 
And so it is ``__setitem__`` is broken in 1.0.0.

Commit message: whatsnew

Patch to review:
diff --git a/doc/source/whatsnew/v1.0.1.rst b/doc/source/whatsnew/v1.0.1.rst
index ff8433c7cafd9..6529921ee3b09 100644
--- a/doc/source/whatsnew/v1.0.1.rst
+++ b/doc/source/whatsnew/v1.0.1.rst
@@ -67,7 +67,7 @@ Interval
 
 Indexing
 ^^^^^^^^
-
+- Bug in :class:`DataFrame` setting values with a slice (e.g. ``df[-4:] = 1``) indexing by label instead of position (:issue:`31469`)
 -
 -
 
diff --git a/pandas/core/frame.py b/pandas/core/frame.py
index 70e440b49ae6c..0dea8235e9d3f 100644
--- a/pandas/core/frame.py
+++ b/pandas/core/frame.py
@@ -2937,8 +2937,11 @@ def __setitem__(self, key, value):
             self._set_item(key, value)
 
     def _setitem_slice(self, key, value):
+        # NB: we can't just use self.loc[key] = value because that
+        #  operates on labels and we need to operate positional for
+        #  backwards-compat, xref GH#31469
         self._check_setitem_copy()
-        self.loc[key] = value
+        self.loc._setitem_with_indexer(key, value)
 
     def _setitem_array(self, key, value):
         # also raises Exception if object array with NA values
diff --git a/pandas/tests/frame/indexing/test_indexing.py b/pandas/tests/frame/indexing/test_indexing.py
index cbb9dd09bbede..64d0f9ee2b062 100644
--- a/pandas/tests/frame/indexing/test_indexing.py
+++ b/pandas/tests/frame/indexing/test_indexing.py
@@ -860,6 +860,15 @@ def test_fancy_getitem_slice_mixed(self, float_frame, float_string_frame):
 
         assert (float_frame["C"] == 4).all()
 
+    def test_setitem_slice_position(self):
+        # GH#31469
+        df = pd.DataFrame(np.zeros((100, 1)))
+        df[-4:] = 1
+        arr = np.zeros((100, 1))
+        arr[-4:] = 1
+        expected = pd.DataFrame(arr)
+        tm.assert_frame_equal(df, expected)
+
     def test_getitem_setitem_non_ix_labels(self):
         df = tm.makeTimeDataFrame()
 


Code review comments:
diff_hunk: @@ -67,7 +67,7 @@ Interval
 
 Indexing
 ^^^^^^^^
-
+- Bug in :class:`DataFrame` setting values with a slice (e.g. ``df[-4:] = 1``) indexing by label instead of position (:issue:`31469`)
path: doc/source/whatsnew/v1.0.1.rst
text: @user1:
```suggestion
- Fixed regression in :class:`DataFrame` setting values with a slice (e.g. ``df[-4:] = 1``) indexing by label instead of position (:issue:`31469`)
```