Repository: pandas-dev/pandas

Issue:
Deprecate Series.ftype / DataFrame.ftypes
With the `ftype(s)` property, you could check if data was sparse:

```
In [26]: df = pd.DataFrame({'a': pd.SparseArray([1, np.nan, 1])})

In [27]: df
Out[27]: 
     a
0  1.0
1  NaN
2  1.0

In [28]: df.dtypes
Out[28]: 
a    Sparse[float64, nan]
dtype: object

In [29]: df.ftypes
Out[29]: 
a    float64:sparse
dtype: object
```

But now that the dtype itself is a EA SparseDtype, I don't think those `ftype` and `ftypes` attributes serve any good purpose. So let's deprecate them.


Discussion:

Resolving patch:
diff --git a/pandas/core/generic.py b/pandas/core/generic.py
--- a/pandas/core/generic.py
+++ b/pandas/core/generic.py
@@ -5525,6 +5525,9 @@ def ftypes(self):
         """
         Return the ftypes (indication of sparse/dense and dtype) in DataFrame.
 
+        .. deprecated:: 0.25.0
+           Use :func:`dtypes` instead.
+
         This returns a Series with the data type of each column.
         The result's index is the original DataFrame's columns. Columns
         with mixed types are stored with the ``object`` dtype.  See
@@ -5562,6 +5565,11 @@ def ftypes(self):
         3    float64:sparse
         dtype: object
         """
+        warnings.warn("DataFrame.ftypes is deprecated and will "
+                      "be removed in a future version. "
+                      "Use DataFrame.dtypes instead.",
+                      FutureWarning, stacklevel=2)
+
         from pandas import Series
         return Series(self._data.get_ftypes(), index=self._info_axis,
                       dtype=np.object_)
diff --git a/pandas/core/series.py b/pandas/core/series.py
--- a/pandas/core/series.py
+++ b/pandas/core/series.py
@@ -419,14 +419,30 @@ def dtypes(self):
     def ftype(self):
         """
         Return if the data is sparse|dense.
+
+        .. deprecated:: 0.25.0
+           Use :func:`dtype` instead.
         """
+        warnings.warn("Series.ftype is deprecated and will "
+                      "be removed in a future version. "
+                      "Use Series.dtype instead.",
+                      FutureWarning, stacklevel=2)
+
         return self._data.ftype
 
     @property
     def ftypes(self):
         """
         Return if the data is sparse|dense.
+
+        .. deprecated:: 0.25.0
+           Use :func:`dtypes` instead.
         """
+        warnings.warn("Series.ftypes is deprecated and will "
+                      "be removed in a future version. "
+                      "Use Series.dtype instead.",
+                      FutureWarning, stacklevel=2)
+
         return self._data.ftype
 
     @property


Test patch:
diff --git a/pandas/tests/frame/test_dtypes.py b/pandas/tests/frame/test_dtypes.py
--- a/pandas/tests/frame/test_dtypes.py
+++ b/pandas/tests/frame/test_dtypes.py
@@ -38,23 +38,34 @@ def test_concat_empty_dataframe_dtypes(self):
     def test_empty_frame_dtypes_ftypes(self):
         empty_df = pd.DataFrame()
         assert_series_equal(empty_df.dtypes, pd.Series(dtype=np.object))
-        assert_series_equal(empty_df.ftypes, pd.Series(dtype=np.object))
+
+        # GH 26705 - Assert .ftypes is deprecated
+        with tm.assert_produces_warning(FutureWarning):
+            assert_series_equal(empty_df.ftypes, pd.Series(dtype=np.object))
 
         nocols_df = pd.DataFrame(index=[1, 2, 3])
         assert_series_equal(nocols_df.dtypes, pd.Series(dtype=np.object))
-        assert_series_equal(nocols_df.ftypes, pd.Series(dtype=np.object))
+
+        # GH 26705 - Assert .ftypes is deprecated
+        with tm.assert_produces_warning(FutureWarning):
+            assert_series_equal(nocols_df.ftypes, pd.Series(dtype=np.object))
 
         norows_df = pd.DataFrame(columns=list("abc"))
         assert_series_equal(norows_df.dtypes, pd.Series(
             np.object, index=list("abc")))
-        assert_series_equal(norows_df.ftypes, pd.Series(
-            'object:dense', index=list("abc")))
+
+        # GH 26705 - Assert .ftypes is deprecated
+        with tm.assert_produces_warning(FutureWarning):
+            assert_series_equal(norows_df.ftypes, pd.Series(
+                'object:dense', index=list("abc")))
 
         norows_int_df = pd.DataFrame(columns=list("abc")).astype(np.int32)
         assert_series_equal(norows_int_df.dtypes, pd.Series(
             np.dtype('int32'), index=list("abc")))
-        assert_series_equal(norows_int_df.ftypes, pd.Series(
-            'int32:dense', index=list("abc")))
+        # GH 26705 - Assert .ftypes is deprecated
+        with tm.assert_produces_warning(FutureWarning):
+            assert_series_equal(norows_int_df.ftypes, pd.Series(
+                'int32:dense', index=list("abc")))
 
         odict = OrderedDict
         df = pd.DataFrame(odict([('a', 1), ('b', True), ('c', 1.0)]),
@@ -66,11 +77,17 @@ def test_empty_frame_dtypes_ftypes(self):
                                      ('b', 'bool:dense'),
                                      ('c', 'float64:dense')]))
         assert_series_equal(df.dtypes, ex_dtypes)
-        assert_series_equal(df.ftypes, ex_ftypes)
+
+        # GH 26705 - Assert .ftypes is deprecated
+        with tm.assert_produces_warning(FutureWarning):
+            assert_series_equal(df.ftypes, ex_ftypes)
 
         # same but for empty slice of df
         assert_series_equal(df[:0].dtypes, ex_dtypes)
-        assert_series_equal(df[:0].ftypes, ex_ftypes)
+
+        # GH 26705 - Assert .ftypes is deprecated
+        with tm.assert_produces_warning(FutureWarning):
+            assert_series_equal(df[:0].ftypes, ex_ftypes)
 
     def test_datetime_with_tz_dtypes(self):
         tzframe = DataFrame({'A': date_range('20130101', periods=3),
@@ -402,7 +419,10 @@ def test_ftypes(self):
                                B='float32:dense',
                                C='float16:dense',
                                D='float64:dense')).sort_values()
-        result = frame.ftypes.sort_values()
+
+        # GH 26705 - Assert .ftypes is deprecated
+        with tm.assert_produces_warning(FutureWarning):
+            result = frame.ftypes.sort_values()
         assert_series_equal(result, expected)
 
     def test_astype(self):
diff --git a/pandas/tests/series/test_combine_concat.py b/pandas/tests/series/test_combine_concat.py
--- a/pandas/tests/series/test_combine_concat.py
+++ b/pandas/tests/series/test_combine_concat.py
@@ -247,21 +247,30 @@ def test_concat_empty_series_dtypes(self):
         result = pd.concat([Series(dtype='float64').to_sparse(), Series(
             dtype='float64').to_sparse()])
         assert result.dtype == 'Sparse[float64]'
-        assert result.ftype == 'float64:sparse'
+
+        # GH 26705 - Assert .ftype is deprecated
+        with tm.assert_produces_warning(FutureWarning):
+            assert result.ftype == 'float64:sparse'
 
         result = pd.concat([Series(dtype='float64').to_sparse(), Series(
             dtype='float64')])
         # TODO: release-note: concat sparse dtype
         expected = pd.core.sparse.api.SparseDtype(np.float64)
         assert result.dtype == expected
-        assert result.ftype == 'float64:sparse'
+
+        # GH 26705 - Assert .ftype is deprecated
+        with tm.assert_produces_warning(FutureWarning):
+            assert result.ftype == 'float64:sparse'
 
         result = pd.concat([Series(dtype='float64').to_sparse(), Series(
             dtype='object')])
         # TODO: release-note: concat sparse dtype
         expected = pd.core.sparse.api.SparseDtype('object')
         assert result.dtype == expected
-        assert result.ftype == 'object:sparse'
+
+        # GH 26705 - Assert .ftype is deprecated
+        with tm.assert_produces_warning(FutureWarning):
+            assert result.ftype == 'object:sparse'
 
     def test_combine_first_dt64(self):
         from pandas.core.tools.datetimes import to_datetime
diff --git a/pandas/tests/series/test_dtypes.py b/pandas/tests/series/test_dtypes.py
--- a/pandas/tests/series/test_dtypes.py
+++ b/pandas/tests/series/test_dtypes.py
@@ -48,8 +48,14 @@ def test_dtype(self, datetime_series):
 
         assert datetime_series.dtype == np.dtype('float64')
         assert datetime_series.dtypes == np.dtype('float64')
-        assert datetime_series.ftype == 'float64:dense'
-        assert datetime_series.ftypes == 'float64:dense'
+
+        # GH 26705 - Assert .ftype is deprecated
+        with tm.assert_produces_warning(FutureWarning):
+            assert datetime_series.ftype == 'float64:dense'
+
+        # GH 26705 - Assert .ftypes is deprecated
+        with tm.assert_produces_warning(FutureWarning):
+            assert datetime_series.ftypes == 'float64:dense'
         tm.assert_series_equal(datetime_series.get_dtype_counts(),
                                Series(1, ['float64']))
         # GH18243 - Assert .get_ftype_counts is deprecated
diff --git a/pandas/tests/sparse/series/test_series.py b/pandas/tests/sparse/series/test_series.py
--- a/pandas/tests/sparse/series/test_series.py
+++ b/pandas/tests/sparse/series/test_series.py
@@ -164,7 +164,10 @@ def test_construct_DataFrame_with_sp_series(self):
 
         # blocking
         expected = Series({'col': 'float64:sparse'})
-        result = df.ftypes
+
+        # GH 26705 - Assert .ftypes is deprecated
+        with tm.assert_produces_warning(FutureWarning):
+            result = df.ftypes
         tm.assert_series_equal(expected, result)
 
     def test_constructor_preserve_attr(self):