Repository: pandas-dev/pandas

Issue:
BUG: binary operator methods (eg mul) don't handle integer level
Suppose I have the following dataframe and series:

```
In [20]: df = pd.DataFrame(np.ones((3,4)), columns=pd.MultiIndex.from_product([['A', 'B'],['a', 'b']]))

In [21]: s = pd.Series({'a':1, 'b':2})

In [22]: df
Out[22]: 
   A     B   
   a  b  a  b
0  1  1  1  1
1  1  1  1  1
2  1  1  1  1

In [23]: s
Out[23]: 
a    1
b    2
dtype: int64
```

Trying to multiply this by the columns, and matching on the second level, gives the following error:

```
In [24]: df.mul(s, axis=1, level=1)
...
  File "C:\Anaconda\lib\site-packages\pandas\core\index.py", line 1786, in _join_multi
    raise ValueError("cannot join with no level specified and no overlapping names")

ValueError: cannot join with no level specified and no overlapping names
```

In any case, this is a wrong error message, as I _did_ specify a level. 
Further, this does work and gives the expected output with a named level instead of a integer level:

```
In [25]: df2 = df.copy()
    ...: df2.columns.names = ['l0', 'l1']

In [35]: df2.mul(s, axis=1, level='l1') # <-- works

Out[35]: 
l0  A     B   
l1  a  b  a  b
0   1  2  1  2
1   1  2  1  2
2   1  2  1  2
```

Am I correct that this should also work with integer levels?



Discussion:
this just looks like a bug, prob `_get_level_number` is not called (which will properly transform the numbers/levels names)

I think there is a `level=level` missing here: https://github.com/pydata/pandas/blob/master/pandas/core/generic.py#L3198. Will try to see if that breaks something else.


Resolving patch:
diff --git a/pandas/core/generic.py b/pandas/core/generic.py
--- a/pandas/core/generic.py
+++ b/pandas/core/generic.py
@@ -3172,20 +3172,15 @@ def _align_series(self, other, join='outer', axis=None, level=None,
 
         else:
 
-            # for join compat if we have an unnamed index, but
-            # are specifying a level join
-            other_index = other.index
-            if level is not None and other.index.name is None:
-                other_index = other_index.set_names([level])
-
             # one has > 1 ndim
             fdata = self._data
             if axis == 0:
                 join_index = self.index
                 lidx, ridx = None, None
-                if not self.index.equals(other_index):
-                    join_index, lidx, ridx = self.index.join(
-                        other_index, how=join, return_indexers=True)
+                if not self.index.equals(other.index):
+                    join_index, lidx, ridx = \
+                        self.index.join(other.index, how=join, level=level,
+                                        return_indexers=True)
 
                 if lidx is not None:
                     fdata = fdata.reindex_indexer(join_index, lidx, axis=1)
@@ -3193,9 +3188,9 @@ def _align_series(self, other, join='outer', axis=None, level=None,
             elif axis == 1:
                 join_index = self.columns
                 lidx, ridx = None, None
-                if not self.columns.equals(other_index):
+                if not self.columns.equals(other.index):
                     join_index, lidx, ridx = \
-                        self.columns.join(other_index, how=join,
+                        self.columns.join(other.index, how=join, level=level,
                                           return_indexers=True)
 
                 if lidx is not None:


Test patch:
diff --git a/pandas/tests/test_frame.py b/pandas/tests/test_frame.py
--- a/pandas/tests/test_frame.py
+++ b/pandas/tests/test_frame.py
@@ -5428,6 +5428,35 @@ def test_binary_ops_align(self):
             expected = pd.concat([ opa(df.loc[idx[:,i],:],v) for i, v in x.iteritems() ]).reindex_like(df).sortlevel()
             assert_frame_equal(result, expected)
 
+        ## GH9463 (alignment level of dataframe with series)
+
+        midx = MultiIndex.from_product([['A', 'B'],['a', 'b']])
+        df = DataFrame(np.ones((2,4), dtype='int64'), columns=midx)
+        s = pd.Series({'a':1, 'b':2})
+
+        df2 = df.copy()
+        df2.columns.names = ['lvl0', 'lvl1']
+        s2 = s.copy()
+        s2.index.name = 'lvl1'
+
+        # different cases of integer/string level names:
+        res1 = df.mul(s, axis=1, level=1)
+        res2 = df.mul(s2, axis=1, level=1)
+        res3 = df2.mul(s, axis=1, level=1)
+        res4 = df2.mul(s2, axis=1, level=1)
+        res5 = df2.mul(s, axis=1, level='lvl1')
+        res6 = df2.mul(s2, axis=1, level='lvl1')
+
+        exp = DataFrame(np.array([[1, 2, 1, 2], [1, 2, 1, 2]], dtype='int64'),
+                        columns=midx)
+
+        for res in [res1, res2]:
+            assert_frame_equal(res, exp)
+
+        exp.columns.names = ['lvl0', 'lvl1']
+        for res in [res3, res4, res5, res6]:
+            assert_frame_equal(res, exp)
+
     def test_arith_mixed(self):
 
         left = DataFrame({'A': ['a', 'b', 'c'],