Repository: pandas-dev/pandas

Issue:
BUG: numexpr / ops with boolean and conversion
these cases should give the same results. Somehow the ops order is being reversed or something with numexpr.

numpy 1.8.1, numexpr 2.4, pandas 0.14rc1
Can be worked around like:

```
In [39]: df = DataFrame(dict(A=np.random.randn(25000)))

In [40]: df.iloc[0:5] = np.nan

In [41]: (1-np.isnan(df)).iloc[0:25]
Out[41]: 
       A
0      0
1      0
2      0
3      0
4      0
5     -1
6     -1
7     -1
8     -1
9     -1
10    -1
11    -1
12    -1
13    -1
14    -1
15    -1
16    -1
17    -1
18    -1
19    -1
20    -1
21    -1
22    -1
23    -1
24    -1
25    -1
```

```
In [43]: df = DataFrame(dict(A=np.random.randn(25)))

In [44]: df.iloc[0:5] = np.nan

In [45]: (1-np.isnan(df)).iloc[0:25] 
Out[45]: 
    A
0   0
1   0
2   0
3   0
4   0
5   1
6   1
7   1
8   1
9   1
10  1
11  1
12  1
13  1
14  1
15  1
16  1
17  1
18  1
19  1
20  1
21  1
22  1
23  1
24  1
```



Discussion:
@cpcloud can you have a look.

How are these different? I can't see it. 

Or are they not and that's the point 

they are not, that's the point.

I _think_ it has to do with numexpr and reversing bool ops (e.g. `1-np.nan(df)` is evaluated as `-np.nan(df) + 1` but in bool land that's not _exactly_ the same (with numpy 1.8.1), I was running 1.7.2 before today....

you know those pandas devs wanted to test things out :)

Haha yep ok. I can take a look. I did make some changes there recently.


Resolving patch:
diff --git a/pandas/computation/expressions.py b/pandas/computation/expressions.py
--- a/pandas/computation/expressions.py
+++ b/pandas/computation/expressions.py
@@ -89,12 +89,18 @@ def _can_use_numexpr(op, op_str, a, b, dtype_check):
     return False
 
 
-def _evaluate_numexpr(op, op_str, a, b, raise_on_error=False, truediv=True,
+def _evaluate_numexpr(op, op_str, a, b, raise_on_error=False, truediv=True, reversed=False,
                       **eval_kwargs):
     result = None
 
     if _can_use_numexpr(op, op_str, a, b, 'evaluate'):
         try:
+
+            # we were originally called by a reversed op
+            # method
+            if reversed:
+                a,b = b,a
+
             a_value = getattr(a, "values", a)
             b_value = getattr(b, "values", b)
             result = ne.evaluate('a_value %s b_value' % op_str,
diff --git a/pandas/core/frame.py b/pandas/core/frame.py
--- a/pandas/core/frame.py
+++ b/pandas/core/frame.py
@@ -2256,7 +2256,7 @@ def set_index(self, keys, drop=True, append=False, inplace=False,
                 names.append(col.name)
             elif isinstance(col, Index):
                 level = col
-                names.append(col.name)                
+                names.append(col.name)
             elif isinstance(col, (list, np.ndarray)):
                 level = col
                 names.append(None)
diff --git a/pandas/core/ops.py b/pandas/core/ops.py
--- a/pandas/core/ops.py
+++ b/pandas/core/ops.py
@@ -68,19 +68,21 @@ def names(x):
         # not entirely sure why this is necessary, but previously was included
         # so it's here to maintain compatibility
         rmul=arith_method(operator.mul, names('rmul'), op('*'),
-                          default_axis=default_axis),
+                          default_axis=default_axis, reversed=True),
         rsub=arith_method(lambda x, y: y - x, names('rsub'), op('-'),
-                          default_axis=default_axis),
+                          default_axis=default_axis, reversed=True),
         rtruediv=arith_method(lambda x, y: operator.truediv(y, x),
                               names('rtruediv'), op('/'), truediv=True,
-                              fill_zeros=np.inf, default_axis=default_axis),
+                              fill_zeros=np.inf, default_axis=default_axis,
+                              reversed=True),
         rfloordiv=arith_method(lambda x, y: operator.floordiv(y, x),
                                names('rfloordiv'), op('//'),
-                               default_axis=default_axis, fill_zeros=np.inf),
+                               default_axis=default_axis, fill_zeros=np.inf,
+                               reversed=True),
         rpow=arith_method(lambda x, y: y ** x, names('rpow'), op('**'),
-                          default_axis=default_axis),
+                          default_axis=default_axis, reversed=True),
         rmod=arith_method(lambda x, y: y % x, names('rmod'), op('%'),
-                          default_axis=default_axis),
+                          default_axis=default_axis, reversed=True),
     )
     new_methods['div'] = new_methods['truediv']
     new_methods['rdiv'] = new_methods['rtruediv']


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
@@ -8351,6 +8351,47 @@ def test_combine_multiple_frames_dtypes(self):
         expected = Series(dict( float64 = 2, float32 = 2 ))
         assert_series_equal(results,expected)
 
+    def test_ops(self):
+
+        # tst ops and reversed ops in evaluation
+        # GH7198
+
+        # smaller hits python, larger hits numexpr
+        for n in [ 4, 4000 ]:
+
+            df = DataFrame(1,index=range(n),columns=list('abcd'))
+            df.iloc[0] = 2
+            m = df.mean()
+
+            for op_str, op, rop in [('+','__add__','__radd__'),
+                                    ('-','__sub__','__rsub__'),
+                                    ('*','__mul__','__rmul__'),
+                                    ('/','__truediv__','__rtruediv__')]:
+
+                base = DataFrame(np.tile(m.values,n).reshape(n,-1),columns=list('abcd'))
+                expected = eval("base{op}df".format(op=op_str))
+
+                # ops as strings
+                result = eval("m{op}df".format(op=op_str))
+                assert_frame_equal(result,expected)
+
+                # these are commutative
+                if op in ['+','*']:
+                    result = getattr(df,op)(m)
+                    assert_frame_equal(result,expected)
+
+                # these are not
+                elif op in ['-','/']:
+                    result = getattr(df,rop)(m)
+                    assert_frame_equal(result,expected)
+
+        # GH7192
+        df = DataFrame(dict(A=np.random.randn(25000)))
+        df.iloc[0:5] = np.nan
+        expected = (1-np.isnan(df.iloc[0:25]))
+        result = (1-np.isnan(df)).iloc[0:25]
+        assert_frame_equal(result,expected)
+
     def test_truncate(self):
         offset = datetools.bday