Repository: pandas-dev/pandas

Issue:
BUG: astype('Int64') raises AttributeError
```
In [8]: df = pd.DataFrame([[1., 2.], [3., 4.], [5., 6.]], columns=['a', 'b'])

In [9]: df
Out[9]: 
     a    b
0  1.0  2.0
1  3.0  4.0
2  5.0  6.0

In [10]: df.astype('Int64')
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-10-b9d2763e69d8> in <module>()
----> 1 df.astype('Int64')

~/scipy/pandas/pandas/util/_decorators.py in wrapper(*args, **kwargs)
    175                 else:
    176                     kwargs[new_arg_name] = new_arg_value
--> 177             return func(*args, **kwargs)
    178         return wrapper
    179     return _deprecate_kwarg

~/scipy/pandas/pandas/core/generic.py in astype(self, dtype, copy, errors, **kwargs)
   5162             # else, only a single dtype is given
   5163             new_data = self._data.astype(dtype=dtype, copy=copy, errors=errors,
-> 5164                                          **kwargs)
   5165             return self._constructor(new_data).__finalize__(self)
   5166 

~/scipy/pandas/pandas/core/internals/managers.py in astype(self, dtype, **kwargs)
    554 
    555     def astype(self, dtype, **kwargs):
--> 556         return self.apply('astype', dtype=dtype, **kwargs)
    557 
    558     def convert(self, **kwargs):

~/scipy/pandas/pandas/core/internals/managers.py in apply(self, f, axes, filter, do_integrity_check, consolidate, **kwargs)
    421 
    422             kwargs['mgr'] = self
--> 423             applied = getattr(b, f)(**kwargs)
    424             result_blocks = _extend_blocks(applied, result_blocks)
    425 

~/scipy/pandas/pandas/core/internals/blocks.py in astype(self, dtype, copy, errors, values, **kwargs)
    562     def astype(self, dtype, copy=False, errors='raise', values=None, **kwargs):
    563         return self._astype(dtype, copy=copy, errors=errors, values=values,
--> 564                             **kwargs)
    565 
    566     def _astype(self, dtype, copy=False, errors='raise', values=None,

~/scipy/pandas/pandas/core/internals/blocks.py in _astype(self, dtype, copy, errors, values, klass, mgr, **kwargs)
    679                     "current ({newb_dtype} [{newb_size}])".format(
    680                         copy=copy, dtype=self.dtype.name,
--> 681                         itemsize=self.itemsize, newb_dtype=newb.dtype.name,
    682                         newb_size=newb.itemsize))
    683         return newb

AttributeError: 'FloatBlock' object has no attribute 'itemsize'
```

The same if the starting dataframe is of integer dtype.


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
@@ -18,7 +18,6 @@
     is_number,
     is_integer, is_bool,
     is_bool_dtype,
-    is_categorical_dtype,
     is_numeric_dtype,
     is_datetime64_any_dtype,
     is_timedelta64_dtype,
@@ -28,6 +27,7 @@
     is_re_compilable,
     is_period_arraylike,
     is_object_dtype,
+    is_extension_array_dtype,
     pandas_dtype)
 from pandas.core.dtypes.cast import maybe_promote, maybe_upcast_putmask
 from pandas.core.dtypes.inference import is_hashable
@@ -5258,8 +5258,9 @@ def astype(self, dtype, copy=True, errors='raise', **kwargs):
                 else:
                     results.append(results.append(col.copy() if copy else col))
 
-        elif is_categorical_dtype(dtype) and self.ndim > 1:
+        elif is_extension_array_dtype(dtype) and self.ndim > 1:
             # GH 18099: columnwise conversion to categorical
+            # and extension dtype
             results = (self[col].astype(dtype, copy=copy) for col in self)
 
         else:
diff --git a/pandas/core/internals/blocks.py b/pandas/core/internals/blocks.py
--- a/pandas/core/internals/blocks.py
+++ b/pandas/core/internals/blocks.py
@@ -675,11 +675,11 @@ def _astype(self, dtype, copy=False, errors='raise', values=None,
             if newb.shape != self.shape:
                 raise TypeError(
                     "cannot set astype for copy = [{copy}] for dtype "
-                    "({dtype} [{itemsize}]) with smaller itemsize than "
-                    "current ({newb_dtype} [{newb_size}])".format(
+                    "({dtype} [{shape}]) to different shape "
+                    "({newb_dtype} [{newb_shape}])".format(
                         copy=copy, dtype=self.dtype.name,
-                        itemsize=self.itemsize, newb_dtype=newb.dtype.name,
-                        newb_size=newb.itemsize))
+                        shape=self.shape, newb_dtype=newb.dtype.name,
+                        newb_shape=newb.shape))
         return newb
 
     def convert(self, copy=True, **kwargs):


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
@@ -12,6 +12,7 @@
 from pandas.compat import u
 from pandas import _np_version_under1p14
 
+from pandas.core.arrays import integer_array
 from pandas.core.dtypes.dtypes import DatetimeTZDtype, CategoricalDtype
 from pandas.tests.frame.common import TestData
 from pandas.util.testing import (assert_series_equal,
@@ -666,6 +667,48 @@ def test_astype_categoricaldtype_class_raises(self, cls):
         with tm.assert_raises_regex(TypeError, xpr):
             df['A'].astype(cls)
 
+    @pytest.mark.parametrize("dtype", ['Int64', 'Int32', 'Int16'])
+    def test_astype_extension_dtypes(self, dtype):
+        # GH 22578
+        df = pd.DataFrame([[1., 2.], [3., 4.], [5., 6.]], columns=['a', 'b'])
+
+        expected1 = pd.DataFrame({'a': integer_array([1, 3, 5],
+                                                     dtype=dtype),
+                                  'b': integer_array([2, 4, 6],
+                                                     dtype=dtype)})
+        tm.assert_frame_equal(df.astype(dtype), expected1)
+        tm.assert_frame_equal(df.astype('int64').astype(dtype), expected1)
+        tm.assert_frame_equal(df.astype(dtype).astype('float64'), df)
+
+        df = pd.DataFrame([[1., 2.], [3., 4.], [5., 6.]], columns=['a', 'b'])
+        df['b'] = df['b'].astype(dtype)
+        expected2 = pd.DataFrame({'a': [1., 3., 5.],
+                                  'b': integer_array([2, 4, 6],
+                                                     dtype=dtype)})
+        tm.assert_frame_equal(df, expected2)
+
+        tm.assert_frame_equal(df.astype(dtype), expected1)
+        tm.assert_frame_equal(df.astype('int64').astype(dtype), expected1)
+
+    @pytest.mark.parametrize("dtype", ['Int64', 'Int32', 'Int16'])
+    def test_astype_extension_dtypes_1d(self, dtype):
+        # GH 22578
+        df = pd.DataFrame({'a': [1., 2., 3.]})
+
+        expected1 = pd.DataFrame({'a': integer_array([1, 2, 3],
+                                                     dtype=dtype)})
+        tm.assert_frame_equal(df.astype(dtype), expected1)
+        tm.assert_frame_equal(df.astype('int64').astype(dtype), expected1)
+
+        df = pd.DataFrame({'a': [1., 2., 3.]})
+        df['a'] = df['a'].astype(dtype)
+        expected2 = pd.DataFrame({'a': integer_array([1, 2, 3],
+                                                     dtype=dtype)})
+        tm.assert_frame_equal(df, expected2)
+
+        tm.assert_frame_equal(df.astype(dtype), expected1)
+        tm.assert_frame_equal(df.astype('int64').astype(dtype), expected1)
+
     @pytest.mark.parametrize('dtype', [
         {100: 'float64', 200: 'uint64'}, 'category', 'float64'])
     def test_astype_column_metadata(self, dtype):