Repository: pandas-dev/pandas

Issue:
Should series have an is_view method?
See reverted PR in #5853 and discussion on synergy with changes to pandas
re SetttingWithCopy.



Discussion:
This would be helped by understanding what code actually needs this on the
_user_ side. @jseabold ?

I just played around with this and I don't understand the original logic - numpy doesn't raise on sorting a view and pandas doesn't either (in fact you get into some weird behavior)...

``` python
In [9]: ser = pd.Series(range(10)[::-1]); ser
Out[9]:
9    9
8    8
7    7
6    6
5    5
4    4
3    3
2    2
1    1
0    0
dtype: int64

In [11]: view = ser.view()

In [12]: view.sort()

In [13]: view
Out[13]:
0    0
1    1
2    2
3    3
4    4
5    5
6    6
7    7
8    8
9    9
dtype: int64

In [14]: ser
Out[14]:
9    0
8    1
7    2
6    3
5    4
4    5
3    6
2    7
1    8
0    9
dtype: int64

In [15]: vals = ser.values.copy()

In [16]: vals
Out[16]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

In [17]: vals = vals[::-1]

In [18]: np_view = vals.view()

In [19]: np_view.sort()

In [20]: np_view
Out[20]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

In [21]: vals
Out[21]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
```

numpy `sort` returns a copy, `series.sort` sorts in place, hence the hazard.

``` python
In [53]: df=mkdf(10,4,data_gen_f=lambda r,c:np.random.random())
    ...: ss= df.iloc[:,0]
    ...: ss.sort()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-53-27670adc5cb8> in <module>()
      1 s=mkdf(10,4,data_gen_f=lambda r,c:np.random.random())
      2 ss= s.iloc[:,0]
----> 3 ss.sort()

/home/user1/src/pandas/pandas/core/series.py in sort(self, axis, kind, order, ascending)
   1674         if (true_base is not None and
   1675                 (true_base.ndim != 1 or true_base.shape != self.shape)):
-> 1676             raise TypeError('This Series is a view of some other array, to '
   1677                             'sort in-place you must create a copy')
   1678 

TypeError: This Series is a view of some other array, to sort in-place you must create a copy
```

ss.base/ss.values.base points at the underlying ndarray held by `df`'s block manager if I understand correctly.

Except `Series.view()` creates an object that _doesn't_ have `is_view() == True`... (or at least that's what I think I found on my computer).

If the method was more accurately called `is_shared`, would you still object? I admit I still don't understand the objections.


Resolving patch:
diff --git a/pandas/core/frame.py b/pandas/core/frame.py
--- a/pandas/core/frame.py
+++ b/pandas/core/frame.py
@@ -1604,10 +1604,15 @@ def _ixs(self, i, axis=0, copy=False):
                 values = self._data.iget(i)
                 if not len(values):
                     values = np.array([np.nan] * len(self.index), dtype=object)
-                return self._constructor_sliced.from_array(
+                result = self._constructor_sliced.from_array(
                     values, index=self.index,
                     name=label, fastpath=True)
 
+                # this is a cached value, mark it so
+                result._set_as_cached(i, self)
+
+                return result
+
     def iget_value(self, i, j):
         return self.iat[i, j]
 
diff --git a/pandas/core/generic.py b/pandas/core/generic.py
--- a/pandas/core/generic.py
+++ b/pandas/core/generic.py
@@ -983,9 +983,14 @@ def _get_item_cache(self, item):
             values = self._data.get(item)
             res = self._box_item_values(item, values)
             cache[item] = res
-            res._cacher = (item, weakref.ref(self))
+            res._set_as_cached(item, self)
         return res
 
+    def _set_as_cached(self, item, cacher):
+        """ set the _cacher attribute on the calling object with
+            a weakref to cacher """
+        self._cacher = (item, weakref.ref(cacher))
+
     def _box_item_values(self, key, values):
         raise NotImplementedError
 
@@ -994,6 +999,12 @@ def _maybe_cache_changed(self, item, value):
         maybe it has changed """
         self._data.set(item, value)
 
+    @property
+    def _is_cached(self):
+        """ boolean : return if I am cached """
+        cacher = getattr(self, '_cacher', None)
+        return cacher is not None
+
     def _maybe_update_cacher(self, clear=False):
         """ see if we need to update our parent cacher
             if clear, then clear our cache """
diff --git a/pandas/core/series.py b/pandas/core/series.py
--- a/pandas/core/series.py
+++ b/pandas/core/series.py
@@ -1664,20 +1664,16 @@ def sort(self, axis=0, kind='quicksort', order=None, ascending=True):
         --------
         pandas.Series.order
         """
-        sortedSeries = self.order(na_last=True, kind=kind,
-                                  ascending=ascending)
 
-        true_base = self.values
-        while true_base.base is not None:
-            true_base = true_base.base
+        # GH 5856/5863
+        if self._is_cached:
+            raise ValueError("This Series is a view of some other array, to "
+                             "sort in-place you must create a copy")
 
-        if (true_base is not None and
-                (true_base.ndim != 1 or true_base.shape != self.shape)):
-            raise TypeError('This Series is a view of some other array, to '
-                            'sort in-place you must create a copy')
+        result = self.order(na_last=True, kind=kind,
+                            ascending=ascending)
 
-        self._data = sortedSeries._data.copy()
-        self.index = sortedSeries.index
+        self._update_inplace(result)
 
     def sort_index(self, ascending=True):
         """


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
@@ -9547,7 +9547,7 @@ def test_sort_datetimes(self):
 
     def test_frame_column_inplace_sort_exception(self):
         s = self.frame['A']
-        with assertRaisesRegexp(TypeError, "This Series is a view"):
+        with assertRaisesRegexp(ValueError, "This Series is a view"):
             s.sort()
 
         cp = s.copy()
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
@@ -2019,6 +2019,20 @@ def f():
             zed['eyes']['right'].fillna(value=555, inplace=True)
         self.assertRaises(com.SettingWithCopyError, f)
 
+        # GH 5856/5863
+        # Series.sort operating on a view
+        df = DataFrame(np.random.randn(10,4))
+        s = df.iloc[:,0]
+        def f():
+            s.sort()
+        self.assertRaises(ValueError, f)
+
+        df = DataFrame(np.random.randn(10,4))
+        s = df.iloc[:,0]
+        s = s.order()
+        assert_series_equal(s,df.iloc[:,0].order())
+        assert_series_equal(s,df[0].order())
+
         pd.set_option('chained_assignment','warn')
 
     def test_float64index_slicing_bug(self):