Repository: pandas-dev/pandas

Issue:
Suggestion: Warning for DataFrame colname-methodname clash
Would it be costly to issue a warning when one adds a column to a DataFrame that has the same name as a built-in DataFrame method?
As you know, in that case I can not do Dataframe.name to access the column but instead I get a method pointer. 
But if I'm unaware (or it's late and I forgot.. ;) ) that a method with that name exists, I could be confused for quite a while before I find out what's going on.

Example I just had:

s = Series(somedata)
Investigating the difference with
s.diff()
Looks interesting, so for easier management I want to put things in a dataframe now.
df = DataFrame(s)
df['diff'] = s.diff()

And then I tried to filter the df for some values in diff and got this:

df[df.diff > 10]
KeyError: u'no item named True'

That somehow didn't make me at all think about a name clash. In this case I eventually guessed what was wrong, but if I wouldn't know that a fancy method with a fancy name exists and I picked it as well, I guess I never would find out, what's going on.

So, if it isn't too much costs in terms of performance a name check against core methods would be really nice, I think.

Suggestion: Warning for DataFrame colname-methodname clash
Would it be costly to issue a warning when one adds a column to a DataFrame that has the same name as a built-in DataFrame method?
As you know, in that case I can not do Dataframe.name to access the column but instead I get a method pointer. 
But if I'm unaware (or it's late and I forgot.. ;) ) that a method with that name exists, I could be confused for quite a while before I find out what's going on.

Example I just had:

s = Series(somedata)
Investigating the difference with
s.diff()
Looks interesting, so for easier management I want to put things in a dataframe now.
df = DataFrame(s)
df['diff'] = s.diff()

And then I tried to filter the df for some values in diff and got this:

df[df.diff > 10]
KeyError: u'no item named True'

That somehow didn't make me at all think about a name clash. In this case I eventually guessed what was wrong, but if I wouldn't know that a fancy method with a fancy name exists and I picked it as well, I guess I never would find out, what's going on.

So, if it isn't too much costs in terms of performance a name check against core methods would be really nice, I think.



Discussion:
this isn't hard, just a single if added in setattr in core/generic
want to do a PR?

yes, will try.

this isn't hard, just a single if added in setattr in core/generic
want to do a PR?

yes, will try.


Resolving patch:
diff --git a/pandas/core/generic.py b/pandas/core/generic.py
--- a/pandas/core/generic.py
+++ b/pandas/core/generic.py
@@ -27,7 +27,7 @@
     pandas_dtype)
 from pandas.core.dtypes.cast import maybe_promote, maybe_upcast_putmask
 from pandas.core.dtypes.missing import isna, notna
-from pandas.core.dtypes.generic import ABCSeries, ABCPanel
+from pandas.core.dtypes.generic import ABCSeries, ABCPanel, ABCDataFrame
 
 from pandas.core.common import (_values_from_object,
                                 _maybe_box_datetimelike,
@@ -1907,6 +1907,10 @@ def _slice(self, slobj, axis=0, kind=None):
         return result
 
     def _set_item(self, key, value):
+        if isinstance(key, str) and callable(getattr(self, key, None)):
+            warnings.warn("Column name '{key}' collides with a built-in "
+                          "method, which will cause unexpected attribute "
+                          "behavior".format(key=key), stacklevel=3)
         self._data.set(key, value)
         self._clear_item_cache()
 
@@ -3357,6 +3361,12 @@ def __setattr__(self, name, value):
                 else:
                     object.__setattr__(self, name, value)
             except (AttributeError, TypeError):
+                if isinstance(self, ABCDataFrame) and (is_list_like(value)):
+                    warnings.warn("Pandas doesn't allow Series to be assigned "
+                                  "into nonexistent columns - see "
+                                  "https://pandas.pydata.org/pandas-docs/"
+                                  "stable/indexing.html#attribute-access",
+                                  stacklevel=2)
                 object.__setattr__(self, name, value)
 
     # ----------------------------------------------------------------------


Test patch:
diff --git a/pandas/tests/dtypes/test_generic.py b/pandas/tests/dtypes/test_generic.py
--- a/pandas/tests/dtypes/test_generic.py
+++ b/pandas/tests/dtypes/test_generic.py
@@ -4,6 +4,7 @@
 import numpy as np
 import pandas as pd
 from pandas.core.dtypes import generic as gt
+from pandas.util import testing as tm
 
 
 class TestABCClasses(object):
@@ -38,3 +39,40 @@ def test_abc_types(self):
         assert isinstance(self.sparse_array, gt.ABCSparseArray)
         assert isinstance(self.categorical, gt.ABCCategorical)
         assert isinstance(pd.Period('2012', freq='A-DEC'), gt.ABCPeriod)
+
+
+def test_setattr_warnings():
+    # GH5904 - Suggestion: Warning for DataFrame colname-methodname clash
+    # GH7175 - GOTCHA: You can't use dot notation to add a column...
+    d = {'one': pd.Series([1., 2., 3.], index=['a', 'b', 'c']),
+         'two': pd.Series([1., 2., 3., 4.], index=['a', 'b', 'c', 'd'])}
+    df = pd.DataFrame(d)
+
+    with catch_warnings(record=True) as w:
+        #  successfully add new column
+        #  this should not raise a warning
+        df['three'] = df.two + 1
+        assert len(w) == 0
+        assert df.three.sum() > df.two.sum()
+
+    with catch_warnings(record=True) as w:
+        #  successfully modify column in place
+        #  this should not raise a warning
+        df.one += 1
+        assert len(w) == 0
+        assert df.one.iloc[0] == 2
+
+    with catch_warnings(record=True) as w:
+        #  successfully add an attribute to a series
+        #  this should not raise a warning
+        df.two.not_an_index = [1, 2]
+        assert len(w) == 0
+
+    with tm.assert_produces_warning(UserWarning):
+        #  warn when setting column to nonexistent name
+        df.four = df.two + 2
+        assert df.four.sum() > df.two.sum()
+
+    with tm.assert_produces_warning(UserWarning):
+        #  warn when column has same name as method
+        df['sum'] = df.two
diff --git a/pandas/tests/io/test_pytables.py b/pandas/tests/io/test_pytables.py
--- a/pandas/tests/io/test_pytables.py
+++ b/pandas/tests/io/test_pytables.py
@@ -2011,7 +2011,7 @@ def check(obj, comparator):
         df['string'] = 'foo'
         df['float322'] = 1.
         df['float322'] = df['float322'].astype('float32')
-        df['bool'] = df['float322'] > 0
+        df['boolean'] = df['float322'] > 0
         df['time1'] = Timestamp('20130101')
         df['time2'] = Timestamp('20130102')
         check(df, tm.assert_frame_equal)
@@ -2141,7 +2141,7 @@ def test_table_values_dtypes_roundtrip(self):
             df1['string'] = 'foo'
             df1['float322'] = 1.
             df1['float322'] = df1['float322'].astype('float32')
-            df1['bool'] = df1['float32'] > 0
+            df1['boolean'] = df1['float32'] > 0
             df1['time1'] = Timestamp('20130101')
             df1['time2'] = Timestamp('20130102')