Repository: pandas-dev/pandas

Issue:
Correct place for SettingWithCopyWarning?
I am really happy about the SettingWithCopyWarning but is this situation the right place to pop up? Because what I want to do seems to work, maybe I don't see the danger of a possible case?

``` python
In [19]: df.convert_objects().dtypes
Out[19]: 
hour      float64
minute    float64
second    float64
tb        float64
dtype: object

In [20]: df['hour'] = df.hour.astype('int')
-c:1: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_index,col_indexer] = value instead

In [21]: df.dtypes
Out[21]: 
hour        int64
minute    float64
second    float64
tb        float64
dtype: object
```

using master.



Discussion:
can you post a full transcript?

`df.convert_objects()` returns a copy (which is correct), but you seem to be using the original frame (and am not sure what/where that came from). It could be _that_ one is a copy.

but convert_objects() shouldn't have `is_copy` set, because you actually
want to use the final one.

it doesn't as it's a new object

So, yes, I created this dataframe as a slice from a bigger one, `sps` is a list with 3 dataframes:

``` python
df = sps[0]['hour minute second tb'.split()]
```

and I confirmed, that if I just to the type conversion in the big one, I don't get this warning.

But what if all I want to work with is this new dataframe, and I do lot's of other stuff with it, and then eventually I set some values in my copy, isn't that legitimate, if all I want is to work with this smaller copy?
Or is there a better way and I'm doing it wrong?

Just use ix there and you're good.

I do think we're going to need some kind of method (eg `mark_not_copy()`)
to remove the copy in the case where you want to work with the selected
DataFrame

there is

`df._setitem_copy(False)` or `df.copy()` which will do that

I could make `.setitem_nocopy()` a public method?

@michaelaye 

the warning is exactly for the case above, e.g. when you do `df[col][row]` which implicity creates a copy
which are then trying to assign

Better idea: just make is_copy a public property and let people handle it
themselves.


Resolving patch:
diff --git a/pandas/core/generic.py b/pandas/core/generic.py
--- a/pandas/core/generic.py
+++ b/pandas/core/generic.py
@@ -77,11 +77,11 @@ class NDFrame(PandasObject):
     axes : list
     copy : boolean, default False
     """
-    _internal_names = ['_data', 'name', '_cacher', '_is_copy', '_subtyp',
+    _internal_names = ['_data', 'name', '_cacher', 'is_copy', '_subtyp',
                        '_index', '_default_kind', '_default_fill_value']
     _internal_names_set = set(_internal_names)
     _metadata = []
-    _is_copy = None
+    is_copy = None
 
     def __init__(self, data, axes=None, copy=False, dtype=None,
                  fastpath=False):
@@ -96,7 +96,7 @@ def __init__(self, data, axes=None, copy=False, dtype=None,
                 for i, ax in enumerate(axes):
                     data = data.reindex_axis(ax, axis=i)
 
-        object.__setattr__(self, '_is_copy', False)
+        object.__setattr__(self, 'is_copy', False)
         object.__setattr__(self, '_data', data)
         object.__setattr__(self, '_item_cache', {})
 
@@ -1016,7 +1016,7 @@ def _set_item(self, key, value):
 
     def _setitem_copy(self, copy):
         """ set the _is_copy of the iiem """
-        self._is_copy = copy
+        self.is_copy = copy
         return self
 
     def _check_setitem_copy(self, stacklevel=4):
@@ -1024,7 +1024,7 @@ def _check_setitem_copy(self, stacklevel=4):
 
         If you call this function, be sure to set the stacklevel such that the
         user will see the error *at the level of setting*"""
-        if self._is_copy:
+        if self.is_copy:
             value = config.get_option('mode.chained_assignment')
 
             t = ("A value is trying to be set on a copy of a slice from a "


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
@@ -11125,7 +11125,7 @@ def test_xs_view(self):
         self.assert_((dm.xs(2) == 5).all())
 
         # prior to chained assignment (GH5390)
-        # this would raise, but now just rrens a copy (and sets _is_copy)
+        # this would raise, but now just returns a copy (and sets is_copy)
         # TODO (?): deal with mixed-type fiasco?
         # with assertRaisesRegexp(TypeError, 'cannot get view of mixed-type'):
         #    self.mixed_frame.xs(self.mixed_frame.index[2], copy=False)
diff --git a/pandas/tests/test_index.py b/pandas/tests/test_index.py
--- a/pandas/tests/test_index.py
+++ b/pandas/tests/test_index.py
@@ -1380,10 +1380,10 @@ def test_set_value_keeps_names(self):
             columns=['one', 'two', 'three', 'four'],
             index=idx)
         df = df.sortlevel()
-        self.assert_(df._is_copy is False)
+        self.assert_(df.is_copy is False)
         self.assertEqual(df.index.names, ('Name', 'Number'))
         df = df.set_value(('grethe', '4'), 'one', 99.34)
-        self.assert_(df._is_copy is False)
+        self.assert_(df.is_copy is False)
         self.assertEqual(df.index.names, ('Name', 'Number'))
 
     def test_names(self):
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
@@ -1776,7 +1776,7 @@ def test_detect_chained_assignment(self):
         # work with the chain
         expected = DataFrame([[-5,1],[-6,3]],columns=list('AB'))
         df = DataFrame(np.arange(4).reshape(2,2),columns=list('AB'),dtype='int64')
-        self.assert_(not df._is_copy)
+        self.assert_(not df.is_copy)
 
         df['A'][0] = -5
         df['A'][1] = -6
@@ -1784,11 +1784,11 @@ def test_detect_chained_assignment(self):
 
         expected = DataFrame([[-5,2],[np.nan,3.]],columns=list('AB'))
         df = DataFrame({ 'A' : Series(range(2),dtype='int64'), 'B' : np.array(np.arange(2,4),dtype=np.float64)})
-        self.assert_(not df._is_copy)
+        self.assert_(not df.is_copy)
         df['A'][0] = -5
         df['A'][1] = np.nan
         assert_frame_equal(df, expected)
-        self.assert_(not df['A']._is_copy)
+        self.assert_(not df['A'].is_copy)
 
         # using a copy (the chain), fails
         df = DataFrame({ 'A' : Series(range(2),dtype='int64'), 'B' : np.array(np.arange(2,4),dtype=np.float64)})
@@ -1800,7 +1800,7 @@ def f():
         df = DataFrame({'a' : ['one', 'one', 'two',
                                'three', 'two', 'one', 'six'],
                         'c' : Series(range(7),dtype='int64') })
-        self.assert_(not df._is_copy)
+        self.assert_(not df.is_copy)
         expected = DataFrame({'a' : ['one', 'one', 'two',
                                      'three', 'two', 'one', 'six'],
                               'c' : [42,42,2,3,4,42,6]})
@@ -1826,10 +1826,10 @@ def f():
         with tm.assert_produces_warning(expected_warning=com.SettingWithCopyWarning):
             df.loc[0]['A'] = 111
 
-        # make sure that _is_copy is picked up reconstruction
+        # make sure that is_copy is picked up reconstruction
         # GH5475
         df = DataFrame({"A": [1,2]})
-        self.assert_(df._is_copy is False)
+        self.assert_(df.is_copy is False)
         with tm.ensure_clean('__tmp__pickle') as path:
             df.to_pickle(path)
             df2 = pd.read_pickle(path)
@@ -1854,21 +1854,21 @@ def random_text(nobs=100):
 
         # always a copy
         x = df.iloc[[0,1,2]]
-        self.assert_(x._is_copy is True)
+        self.assert_(x.is_copy is True)
         x = df.iloc[[0,1,2,4]]
-        self.assert_(x._is_copy is True)
+        self.assert_(x.is_copy is True)
 
         # explicity copy
         indexer = df.letters.apply(lambda x : len(x) > 10)
         df = df.ix[indexer].copy()
-        self.assert_(df._is_copy is False)
+        self.assert_(df.is_copy is False)
         df['letters'] = df['letters'].apply(str.lower)
 
         # implicity take
         df = random_text(100000)
         indexer = df.letters.apply(lambda x : len(x) > 10)
         df = df.ix[indexer]
-        self.assert_(df._is_copy is True)
+        self.assert_(df.is_copy is True)
         df.loc[:,'letters'] = df['letters'].apply(str.lower)
 
         # this will raise
@@ -1880,7 +1880,7 @@ def random_text(nobs=100):
 
         # an identical take, so no copy
         df = DataFrame({'a' : [1]}).dropna()
-        self.assert_(df._is_copy is False)
+        self.assert_(df.is_copy is False)
         df['a'] += 1
 
         pd.set_option('chained_assignment','warn')