Repository: pandas-dev/pandas
Pull request: .ne fails if comparing a list of columns containing column name 'dtype' #22383
hello, this is my first pr in open source.
so , i think some things have problem.

if this pr has problem , tell me. i will fix again.

this issue occur Dataframe has column defined 'dtype' when execute  .ne function.

i asked some advice which way is more better.

i) when make dataframe instance column name is dtype, make exception.
ii) make exception in ne function.

i receive answer try first. so i try to modifiy _has_bool_dtype function.

- [x] closes #22383
- [x] tests added / passed
- [x] passes `git diff upstream/master -u -- "*.py" | flake8 --diff`
- [x] whatsnew entry


Problem statement:
.ne fails with abiguous message if comparing a list of columns containing column name 'dtype'
#### Code Sample, a copy-pastable example if possible

```python
df = pd.DataFrame([[0,1,2,'aa'],[0,1,2,'aa'],[0,1,5,'bb'],[0,1,5,'bb'],[0,1,5,'bb'],['cc',4,4,4]], columns=['a','b','c','dtype'])

df.loc[:, ['a', 'dtype']].ne(df.loc[:, ['a', 'dtype']])

In [10]: df
Out[10]: 
    a  b  c dtype
0   0  1  2    aa
1   0  1  2    aa
2   0  1  5    bb
3   0  1  5    bb
4   0  1  5    bb
5  cc  4  4     4

```
#### Problem description
Instead of the expected output, I receive:
```python
In [8]: df.loc[:, ['a','dtype']].ne(df.loc[:, ['a', 'dtype']])
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-8-a0710f18822f> in <module>()
----> 1 df.loc[:, ['a','dtype']].ne(df.loc[:, ['a', 'dtype']])

/home/blistein/env/aan/local/lib/python2.7/site-packages/pandas/core/ops.pyc in f(self, other, axis, level)
   1588                 self, other = self.align(other, 'outer',
   1589                                          level=level, copy=False)
-> 1590             return self._compare_frame(other, na_op, str_rep)
   1591 
   1592         elif isinstance(other, ABCSeries):

/home/blistein/env/aan/local/lib/python2.7/site-packages/pandas/core/frame.pyc in _compare_frame(self, other, func, str_rep)
   4790                 return {col: func(a[col], b[col]) for col in a.columns}
   4791 
-> 4792             new_data = expressions.evaluate(_compare, str_rep, self, other)
   4793             return self._constructor(data=new_data, index=self.index,
   4794                                      columns=self.columns, copy=False)

/home/blistein/env/aan/local/lib/python2.7/site-packages/pandas/core/computation/expressions.pyc in evaluate(op, op_str, a, b, use_numexpr, **eval_kwargs)
    201         """
    202 
--> 203     use_numexpr = use_numexpr and _bool_arith_check(op_str, a, b)
    204     if use_numexpr:
    205         return _evaluate(op, op_str, a, b, **eval_kwargs)

/home/blistein/env/aan/local/lib/python2.7/site-packages/pandas/core/computation/expressions.pyc in _bool_arith_check(op_str, a, b, not_allowed, unsupported)
    173         unsupported = {'+': '|', '*': '&', '-': '^'}
    174 
--> 175     if _has_bool_dtype(a) and _has_bool_dtype(b):
    176         if op_str in unsupported:
    177             warnings.warn("evaluating in Python space because the {op!r} "

/home/blistein/env/aan/local/lib/python2.7/site-packages/pandas/core/generic.pyc in __nonzero__(self)
   1574         raise ValueError("The truth value of a {0} is ambiguous. "
   1575                          "Use a.empty, a.bool(), a.item(), a.any() or a.all()."
-> 1576                          .format(self.__class__.__name__))
   1577 
   1578     __bool__ = __nonzero__

ValueError: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().
```
#### Expected Output
```
       a      c
0  False  False
1  False  False
2  False  False
3  False  False
4  False  False
5  False  False
```
Alternative: a descriptive Error Message, telling me I can't use 'dtype' as column name.

#### Output of ``pd.show_versions()``

<details>

[paste the output of ``pd.show_versions()`` here below this line]
INSTALLED VERSIONS
------------------
commit: None
python: 2.7.12.final.0
python-bits: 64
OS: Linux
OS-release: 4.15.0-29-generic
machine: x86_64
processor: x86_64
byteorder: little
LC_ALL: en_US.utf8
LANG: en_US.UTF-8
LOCALE: None.None

pandas: 0.23.4
pytest: 2.8.7
pip: 18.0
setuptools: 40.0.0
Cython: None
numpy: 1.11.0
scipy: 0.17.0
pyarrow: None
xarray: None
IPython: 5.5.0
sphinx: None
patsy: 0.4.1
dateutil: 2.7.3
pytz: 2014.10
blosc: None
bottleneck: None
tables: 3.2.2
numexpr: 2.4.3
feather: None
matplotlib: 1.5.1
openpyxl: 2.3.0
xlrd: 0.9.4
xlwt: 0.7.5
xlsxwriter: None
lxml: 3.5.0
bs4: None
html5lib: 1.0.1
sqlalchemy: None
pymysql: 0.7.2.None
psycopg2: 2.6.1 (dt dec mx pq3 ext lo64)
jinja2: 2.10
s3fs: None
fastparquet: None
pandas_gbq: None
pandas_datareader: None

</details>

Hints from issue discussion:
Thanks for the report - investigation and PRs are always welcome
@WillAyd could i try to solve this issue?
@WillAyd 
i want to discuss some points. 

this issue occurs when column name is dtype.

1) make exception(it is not possible to make column's name dtype) when make a dataframe.

2) make exception in ne function.  
ex) if dataframe's columname == 'dtype": ~~

which method is better ? i wait your response. 

thank you!
Can’t say for sure what the right solution is but I would not do the first option you mention. If you want to try your hand at a PR feel free to find the root cause and just push up a change with tests - the core team can review from there
Hey, thanks for the responses. I am currently overloaded with work. Am goint to keep following here though and, if not solved, come back to it at some point!
The user's code from the original issue should work.

I think `_has_bool_dtype` in `pandsa/core/expressions.py` will need to be updated to work with dataframes that have a column named `dtype`
@WillAyd thank you your reply! i will soon open the pull request!
@TomAugspurger hi, i fix _has_bool_dtype function. but i don't know which test directory is right directory. this is my first try to contribute open source...could you tell me which directory is more appropriate? 
thank you.
Maybe `pandas/tests/test_expressions.py`

Commit message: delete global variable

Patch to review:
diff --git a/doc/source/whatsnew/v0.24.0.rst b/doc/source/whatsnew/v0.24.0.rst
index d8a204abcd93e..069093841de81 100644
--- a/doc/source/whatsnew/v0.24.0.rst
+++ b/doc/source/whatsnew/v0.24.0.rst
@@ -1426,6 +1426,7 @@ Interval
 Indexing
 ^^^^^^^^
 
+- Bug in :meth:`DataFrame.ne` fails if columns contain column name "dtype" (:issue:`22383`)
 - The traceback from a ``KeyError`` when asking ``.loc`` for a single missing label is now shorter and more clear (:issue:`21557`)
 - :class:`PeriodIndex` now emits a ``KeyError`` when a malformed string is looked up, which is consistent with the behavior of :class:`DatetimeIndex` (:issue:`22803`)
 - When ``.ix`` is asked for a missing integer label in a :class:`MultiIndex` with a first level of integer type, it now raises a ``KeyError``, consistently with the case of a flat :class:`Int64Index`, rather than falling back to positional indexing (:issue:`21593`)
diff --git a/pandas/core/computation/expressions.py b/pandas/core/computation/expressions.py
index d44fae624a91c..957c77e2522bd 100644
--- a/pandas/core/computation/expressions.py
+++ b/pandas/core/computation/expressions.py
@@ -10,6 +10,7 @@
 
 import numpy as np
 
+from pandas import DataFrame
 import pandas.core.common as com
 from pandas.core.computation.check import _NUMEXPR_INSTALLED
 from pandas.core.config import get_option
@@ -160,12 +161,12 @@ def _where_numexpr(cond, a, b):
 
 def _has_bool_dtype(x):
     try:
-        return x.dtype == bool
-    except AttributeError:
-        try:
+        if isinstance(x, DataFrame):
             return 'bool' in x.dtypes
-        except AttributeError:
-            return isinstance(x, (bool, np.bool_))
+        else:
+            return x.dtype == bool
+    except AttributeError:
+        return isinstance(x, (bool, np.bool_))
 
 
 def _bool_arith_check(op_str, a, b, not_allowed=frozenset(('/', '//', '**')),
diff --git a/pandas/tests/test_expressions.py b/pandas/tests/test_expressions.py
index 4a61ce930cbab..6a0637f712702 100644
--- a/pandas/tests/test_expressions.py
+++ b/pandas/tests/test_expressions.py
@@ -443,3 +443,22 @@ def test_bool_ops_warn_on_arithmetic(self):
                     r = f(df, True)
                     e = fe(df, True)
                     tm.assert_frame_equal(r, e)
+
+    @pytest.mark.parametrize("test_input,expected", [
+        (DataFrame([[0, 1, 2, 'aa'], [0, 1, 2, 'aa']],
+                   columns=['a', 'b', 'c', 'dtype']).loc[:, ['a', 'dtype']].
+         ne(DataFrame([[0, 1, 2, 'aa'], [0, 1, 2, 'aa']],
+                      columns=['a', 'b', 'c', 'dtype']).
+            loc[:, ['a', 'dtype']]),
+         DataFrame([[False, False], [False, False]], columns=['a', 'dtype'])),
+        (DataFrame([[0, 3, 2, 'aa'], [0, 4, 2, 'aa'], [0, 1, 1, 'bb']],
+                   columns=['a', 'b', 'c', 'dtype']).loc[:, ['a', 'dtype']].
+         ne(DataFrame([[0, 3, 2, 'aa'], [0, 4, 2, 'aa'], [0, 1, 1, 'bb']],
+                      columns=['a', 'b', 'c', 'dtype'])
+            .loc[:, ['a', 'dtype']]),
+         DataFrame([[False, False], [False, False],
+                   [False, False]], columns=['a', 'dtype'])),
+    ])
+    def test_bool_ops_column_name_dtype(self, test_input, expected):
+        # GH 22383 - .ne fails if columns containing column name 'dtype'
+        assert_frame_equal(test_input, expected)


Code review comments:
diff_hunk: @@ -443,3 +443,22 @@ def test_bool_ops_warn_on_arithmetic(self):
                     r = f(df, True)
                     e = fe(df, True)
                     tm.assert_frame_equal(r, e)
+
+    @pytest.mark.parametrize("test_input,expected", [
+        (DataFrame([[0, 1, 2, 'aa'], [0, 1, 2, 'aa']],
+                   columns=['a', 'b', 'c', 'dtype']).loc[:, ['a', 'dtype']].
+         ne(DataFrame([[0, 1, 2, 'aa'], [0, 1, 2, 'aa']],
+                      columns=['a', 'b', 'c', 'dtype']).
+            loc[:, ['a', 'dtype']]),
+         DataFrame([[False, False], [False, False]], columns=['a', 'dtype'])),
+        (DataFrame([[0, 3, 2, 'aa'], [0, 4, 2, 'aa'], [0, 1, 1, 'bb']],
+                   columns=['a', 'b', 'c', 'dtype']).loc[:, ['a', 'dtype']].
+         ne(DataFrame([[0, 3, 2, 'aa'], [0, 4, 2, 'aa'], [0, 1, 1, 'bb']],
+                      columns=['a', 'b', 'c', 'dtype'])
+            .loc[:, ['a', 'dtype']]),
+         DataFrame([[False, False], [False, False],
+                   [False, False]], columns=['a', 'dtype'])),
path: pandas/tests/test_expressions.py
text: @user1:
can you update as I indicated

@author:
@user1 i modify code! is it okay?