Repository: pandas-dev/pandas

Issue:
ENH/BUG: broadcast a lower-dim assignment if possible
http://stackoverflow.com/questions/19346033/how-to-collectively-set-the-values-of-multiple-columns-for-certain-selected-rows/19346925#19346925

This does not work as we are using a rhs of a series which is not broadcast
to the assignment

```
df.loc[df['A'],['A','B']] = df['C']
```

This works, but a tad cumbersome

```
In [17]: df = DataFrame(dict(A = [1,2,0,0,0],B=[0,0,0,10,11],C=[3,4,5,6,7]))

In [18]: df
Out[18]: 
   A   B  C
0  1   0  3
1  2   0  4
2  0   0  5
3  0  10  6
4  0  11  7

In [19]: mask = df['A'] == 0

In [20]: for col in ['A','B']:
   ....:     df.loc[mask,col] = df['C']
   ....:     

In [21]: df
Out[21]: 
   A  B  C
0  1  0  3
1  2  0  4
2  5  5  5
3  6  6  6
4  7  7  7
```



Discussion:

Resolving patch:
diff --git a/pandas/core/indexing.py b/pandas/core/indexing.py
--- a/pandas/core/indexing.py
+++ b/pandas/core/indexing.py
@@ -370,9 +370,11 @@ def _align_series(self, indexer, ser):
         if isinstance(indexer, tuple):
 
             aligners = [ not _is_null_slice(idx) for idx in indexer ]
-            single_aligner = sum(aligners) == 1
+            sum_aligners = sum(aligners)
+            single_aligner = sum_aligners == 1
             is_frame = self.obj.ndim == 2
             is_panel = self.obj.ndim >= 3
+            obj = self.obj
 
             # are we a single alignable value on a non-primary
             # dim (e.g. panel: 1,2, or frame: 0) ?
@@ -387,7 +389,15 @@ def _align_series(self, indexer, ser):
             elif is_panel:
                 single_aligner = single_aligner and (aligners[1] or aligners[2])
 
-            obj = self.obj
+            # we have a frame, with multiple indexers on both axes; and a series,
+            # so need to broadcast (see GH5206)
+            if sum_aligners == self.ndim and all([ com._is_sequence(_) for _ in indexer ]):
+
+                ser = ser.reindex(obj.axes[0][indexer[0].ravel()],copy=True).values
+                l = len(indexer[1].ravel())
+                ser = np.tile(ser,l).reshape(l,-1).T
+                return ser
+
             for i, idx in enumerate(indexer):
                 ax = obj.axes[i]
 
@@ -398,6 +408,8 @@ def _align_series(self, indexer, ser):
                     new_ix = ax[idx]
                     if not is_list_like(new_ix):
                         new_ix = Index([new_ix])
+                    else:
+                        new_ix = Index(new_ix.ravel())
                     if ser.index.equals(new_ix):
                         return ser.values.copy()
                     return ser.reindex(new_ix).values


Test patch:
diff --git a/pandas/tests/test_indexing.py b/pandas/tests/test_indexing.py
--- a/pandas/tests/test_indexing.py
+++ b/pandas/tests/test_indexing.py
@@ -1037,6 +1037,17 @@ def test_multi_assign(self):
         df2.ix[mask, cols]= dft.ix[mask, cols].values
         assert_frame_equal(df2,expected)
 
+        # broadcasting on the rhs is required
+        df = DataFrame(dict(A = [1,2,0,0,0],B=[0,0,0,10,11],C=[0,0,0,10,11],D=[3,4,5,6,7]))
+
+        expected = df.copy()
+        mask = expected['A'] == 0
+        for col in ['A','B']:
+            expected.loc[mask,col] = df['D']
+
+        df.loc[df['A']==0,['A','B']] = df['D']
+        assert_frame_equal(df,expected)
+
     def test_ix_assign_column_mixed(self):
         # GH #1142
         df = DataFrame(tm.getSeriesData())