Repository: pandas-dev/pandas

Issue:
BUG: Boolean indexed assignment
Take the simple example below:

```
s = pd.Series([1.0, 2.0], dtype=float)
s[[True, False]] = np.array([2.0])
```

The expectation would be that the first item in the series will be set to 2. Unfortunately due to a bug in broadcasting, pandas will set the first item in the series to 2*len(s). The culprit appears to be the below code (series.py:757 - where() method definition):

```
if len(other) == 1:
    other = np.array(other[0]*len(ser))
```

`other` in this case is the length 1 array we need to broadcast. I believe the code made the assumption that other will be perhaps a list of lists? As it stands, this code will take the single element out of the array, multiply it by the length of the series and then carry out the update.

I propose that we fix this by using np.repeat which does the correct thing (pending testing).



Discussion:
It appears that this issue is a side effect of #2745.

this is very tricky...trying to do the right thing in all cases

go ahead and try the fix, the existing tests should catch if you change the behavior....

but I am thinking

```
s = Series([1,2])
s[[True,False]] = [0]
```

_works_ by accident (as 0 \* 2 == 0)....

I can't see what was the original code trying to achieve there. At no point before does it ensure that `other` will be a nested list. It must be a list or tuple, since the logic would break down for numpy arrays as well.

Or rather, I see what it was trying to achieve, but I can't see why it was doing it the way it is written :)

Ok, I'll do the fix and add a bunch of tests. I'll try to think of some other edge cases as well.

great! always nice to have more tests

Any chance of shipping this in 0.12? When is the deadline for submissions? This is a pretty nasty one and I'd like to make sure 0.12 isn't affected by it.

sure....do it quick! (should be straightforward)


Resolving patch:
diff --git a/pandas/core/series.py b/pandas/core/series.py
--- a/pandas/core/series.py
+++ b/pandas/core/series.py
@@ -751,11 +751,12 @@ def where(self, cond, other=nan, inplace=False):
 
         if len(other) != len(ser):
             icond = ~cond
-
+            print "First", other
             # GH 2745
             # treat like a scalar
             if len(other) == 1:
-                other = np.array(other[0]*len(ser))
+                other = np.array(other[0])
+                print "EQ 1", other
 
             # GH 3235
             # match True cond to other
@@ -767,7 +768,10 @@ def where(self, cond, other=nan, inplace=False):
                 other = new_other
 
             else:
+                print "Raising", other
                 raise ValueError('Length of replacements must equal series length')
+                
+            print "Finished", other
 
         change = ser if inplace else None
         com._maybe_upcast_putmask(ser,~cond,other,change=change)


Test patch:
diff --git a/pandas/tests/test_series.py b/pandas/tests/test_series.py
--- a/pandas/tests/test_series.py
+++ b/pandas/tests/test_series.py
@@ -1090,11 +1090,6 @@ def test_where(self):
         expected = Series([0,2])
         assert_series_equal(s,expected)
 
-        s = Series([1,2])
-        s[[True, False]] = [0]
-        expected = Series([0,2])
-        assert_series_equal(s,expected)
-
         # failures
         self.assertRaises(ValueError, s.__setitem__, tuple([[[True, False]]]), [0,2,3])
         self.assertRaises(ValueError, s.__setitem__, tuple([[[True, False]]]), [])
@@ -1142,6 +1137,24 @@ def test_where(self):
         s = Series(np.arange(10))
         mask = s > 5
         self.assertRaises(ValueError, s.__setitem__, mask, ([0]*5,))
+        
+    def test_where_broadcast(self):
+        # Test a variety of differently sized series
+        for size in range(2, 6):
+            # Test a variety of boolean indices
+            for selection in [np.resize([True, False, False, False, False], size), # First element should be set
+                              np.resize([True, False], size), # Set alternating elements]
+                              np.resize([False], size)]: # No element should be set
+                # Test a variety of different numbers as content
+                for item in [2.0, np.nan, np.finfo(np.float).max, np.finfo(np.float).min]:
+                    # Test numpy arrays, lists and tuples as the input to be broadcast
+                    for arr in [np.array([item]), [item], (item,)]:
+                        data = np.arange(size, dtype=float)
+                        s = Series(data)
+                        s[selection] = arr
+                        # Construct the expected series by taking the source data or item based on the selection
+                        expected = Series([item if use_item else data[i] for i, use_item in enumerate(selection)])
+                        assert_series_equal(s,expected)
 
     def test_where_inplace(self):
         s = Series(np.random.randn(5))