Repository: pandas-dev/pandas

Issue:
CategoricalAccessor.categorical removed in 0.24.0rc1
Previously, CategoricalAccessor set `.categorical` in `__init__`. I don't think this was intended to be the way to access things like `.ordered`, but it wasn't prefixed with an underscore, so we should deprecate before removing

0.23.4

```python
In [11]: pd.Series(['a', 'b'], dtype='category').cat.categorical.ordered
Out[11]: False
```

0.24.0rc1

```python
In [3]: pd.Series(['a', 'b'], dtype='category').cat.categorical.ordered
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-3-e5c7f0300480> in <module>
----> 1 pd.Series(['a', 'b'], dtype='category').cat.categorical.ordered

AttributeError: 'CategoricalAccessor' object has no attribute 'categorical'
```

We can deprecate with

```python
def categorical(self):
    warnings.warn(...)
    return self._parent
```

in https://github.com/pandas-dev/pandas/blob/33f91d8f9f2e84f2b5f3ac3f0481b691c977c427/pandas/core/arrays/categorical.py#L2472

We should do the same for `.index` and `.name`.


Discussion:
Will take a look at this one.

Resolving patch:
diff --git a/pandas/core/arrays/categorical.py b/pandas/core/arrays/categorical.py
--- a/pandas/core/arrays/categorical.py
+++ b/pandas/core/arrays/categorical.py
@@ -2498,8 +2498,8 @@ class CategoricalAccessor(PandasDelegate, PandasObject, NoNewAttributesMixin):
     def __init__(self, data):
         self._validate(data)
         self._parent = data.values
-        self.index = data.index
-        self.name = data.name
+        self._index = data.index
+        self._name = data.name
         self._freeze()
 
     @staticmethod
@@ -2520,15 +2520,47 @@ def codes(self):
         Return Series of codes as well as the index.
         """
         from pandas import Series
-        return Series(self._parent.codes, index=self.index)
+        return Series(self._parent.codes, index=self._index)
 
     def _delegate_method(self, name, *args, **kwargs):
         from pandas import Series
         method = getattr(self._parent, name)
         res = method(*args, **kwargs)
         if res is not None:
-            return Series(res, index=self.index, name=self.name)
+            return Series(res, index=self._index, name=self._name)
 
+    @property
+    def categorical(self):
+        # Note: Upon deprecation, `test_tab_completion_with_categorical` will
+        # need to be updated. `categorical` will need to be removed from
+        # `ok_for_cat`.
+        warn("`Series.cat.categorical` has been deprecated. Use the "
+             "attributes on 'Series.cat' directly instead.",
+             FutureWarning,
+             stacklevel=2)
+        return self._parent
+
+    @property
+    def name(self):
+        # Note: Upon deprecation, `test_tab_completion_with_categorical` will
+        # need to be updated. `name` will need to be removed from
+        # `ok_for_cat`.
+        warn("`Series.cat.name` has been deprecated. Use `Series.name` "
+             "instead.",
+             FutureWarning,
+             stacklevel=2)
+        return self._name
+
+    @property
+    def index(self):
+        # Note: Upon deprecation, `test_tab_completion_with_categorical` will
+        # need to be updated. `index` will need to be removed from
+        # ok_for_cat`.
+        warn("`Series.cat.index` has been deprecated. Use `Series.index` "
+             "instead.",
+             FutureWarning,
+             stacklevel=2)
+        return self._index
 
 # utility routines
 


Test patch:
diff --git a/pandas/tests/arrays/categorical/test_warnings.py b/pandas/tests/arrays/categorical/test_warnings.py
--- a/pandas/tests/arrays/categorical/test_warnings.py
+++ b/pandas/tests/arrays/categorical/test_warnings.py
@@ -2,6 +2,7 @@
 
 import pytest
 
+import pandas as pd
 import pandas.util.testing as tm
 
 
@@ -16,3 +17,15 @@ def test_tab_complete_warning(self, ip):
         with tm.assert_produces_warning(None):
             with provisionalcompleter('ignore'):
                 list(ip.Completer.completions('c.', 1))
+
+    def test_CategoricalAccessor_categorical_deprecation(object):
+        with tm.assert_produces_warning(FutureWarning):
+            pd.Series(['a', 'b'], dtype='category').cat.categorical
+
+    def test_CategoricalAccessor_name_deprecation(object):
+        with tm.assert_produces_warning(FutureWarning):
+            pd.Series(['a', 'b'], dtype='category').cat.name
+
+    def test_CategoricalAccessor_index_deprecation(object):
+        with tm.assert_produces_warning(FutureWarning):
+            pd.Series(['a', 'b'], dtype='category').cat.index
diff --git a/pandas/tests/series/test_api.py b/pandas/tests/series/test_api.py
--- a/pandas/tests/series/test_api.py
+++ b/pandas/tests/series/test_api.py
@@ -245,10 +245,11 @@ def test_tab_completion(self):
 
     def test_tab_completion_with_categorical(self):
         # test the tab completion display
-        ok_for_cat = ['categories', 'codes', 'ordered', 'set_categories',
-                      'add_categories', 'remove_categories',
-                      'rename_categories', 'reorder_categories',
-                      'remove_unused_categories', 'as_ordered', 'as_unordered']
+        ok_for_cat = ['name', 'index', 'categorical', 'categories', 'codes',
+                      'ordered', 'set_categories', 'add_categories',
+                      'remove_categories', 'rename_categories',
+                      'reorder_categories', 'remove_unused_categories',
+                      'as_ordered', 'as_unordered']
 
         def get_dir(s):
             results = [r for r in s.cat.__dir__() if not r.startswith('_')]
diff --git a/pandas/tests/test_config.py b/pandas/tests/test_config.py
--- a/pandas/tests/test_config.py
+++ b/pandas/tests/test_config.py
@@ -251,7 +251,6 @@ def test_deprecate_option(self):
                     KeyError,
                     message="Nonexistent option didn't raise KeyError"):
                 self.cf.get_option('foo')
-
             assert len(w) == 1  # should have raised one warning
             assert 'deprecated' in str(w[-1])  # we get the default message