Repository: pandas-dev/pandas

Issue:
Using Series in DataFrame.astype() does not work correctly
#### Code Sample

```python
import pandas as pd
import numpy as np

df = pd.DataFrame([[1,2],[3,4]], columns=["A", "B"])


df

 	A 	B
0 	1 	2
1 	3 	4


df.astype({"A": np.float64, "B": np.float64})

 	A 	B
0 	1.0 	2.0
1 	3.0 	4.0

df.astype(pd.Series({"A": np.float64, "B": np.float64})) # using a series does not react at all (error or right behaviour)
 	A 	B
0 	1 	2
1 	3 	4

df.astype(list(pd.Series({"A": np.float64, "B": np.float64}))) # using a type that is not allowed raises error
[...] TypeError: data type not understood

```
#### Problem description
I wanted to parse the columns of a DF with a Series (instead of a dict as stated in the docs). I realised that the types did not change at all nor an error was raised. Casting to dict helped (see in code). 

#### Expected Output
I expect to give the same error as when a wrong data type (e.g. list) is used, or alternatively that it works the same way as a dict. At the moment nothing happens. 
 
<details>
pandas: 0.20.2
pytest: None
pip: 9.0.1
setuptools: 28.8.0
Cython: None
numpy: 1.13.0
scipy: None
xarray: None
IPython: 6.1.0
sphinx: None
patsy: None
dateutil: 2.6.0
pytz: 2017.2
blosc: None
bottleneck: None
tables: None
numexpr: None
feather: None
matplotlib: 2.0.2
openpyxl: None
xlrd: None
xlwt: None
xlsxwriter: None
lxml: None
bs4: None
html5lib: 0.9999999
sqlalchemy: None
pymysql: None
psycopg2: None
jinja2: 2.9.6
s3fs: None
pandas_gbq: None
pandas_datareader: None
</details>



Discussion:
I think `.astype(series[name, dtype])` should work just like a dictionary.

We try `isinstance(dtype, collections.Mapping)`, which is `False` for series (it should *maybe* be true, see https://github.com/pandas-dev/pandas/issues/12056. We aren't 100% compatible with the interface though, since `.values` isn't callable). Alternatively we could replace that `isinstance` with `is_dict_like` and it'll work. Interested in submitting a PR?
> Interested in submitting a PR?

At the moment and near future I am too busy. But, if it remains open and I have some time I will look at it.

Resolving patch:
diff --git a/pandas/core/generic.py b/pandas/core/generic.py
--- a/pandas/core/generic.py
+++ b/pandas/core/generic.py
@@ -3507,12 +3507,12 @@ def astype(self, dtype, copy=True, errors='raise', **kwargs):
         -------
         casted : type of caller
         """
-        if isinstance(dtype, collections.Mapping):
+        if is_dict_like(dtype):
             if self.ndim == 1:  # i.e. Series
-                if len(dtype) > 1 or list(dtype.keys())[0] != self.name:
+                if len(dtype) > 1 or self.name not in dtype:
                     raise KeyError('Only the Series name can be used for '
                                    'the key in Series dtype mappings.')
-                new_type = list(dtype.values())[0]
+                new_type = dtype[self.name]
                 return self.astype(new_type, copy, errors, **kwargs)
             elif self.ndim > 2:
                 raise NotImplementedError(


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
@@ -442,8 +442,9 @@ def test_astype_str(self):
             expected = DataFrame(['1.12345678901'])
             assert_frame_equal(result, expected)
 
-    def test_astype_dict(self):
-        # GH7271
+    @pytest.mark.parametrize("dtype_class", [dict, Series])
+    def test_astype_dict_like(self, dtype_class):
+        # GH7271 & GH16717
         a = Series(date_range('2010-01-04', periods=5))
         b = Series(range(5))
         c = Series([0.0, 0.2, 0.4, 0.6, 0.8])
@@ -452,7 +453,8 @@ def test_astype_dict(self):
         original = df.copy(deep=True)
 
         # change type of a subset of columns
-        result = df.astype({'b': 'str', 'd': 'float32'})
+        dt1 = dtype_class({'b': 'str', 'd': 'float32'})
+        result = df.astype(dt1)
         expected = DataFrame({
             'a': a,
             'b': Series(['0', '1', '2', '3', '4']),
@@ -461,7 +463,8 @@ def test_astype_dict(self):
         assert_frame_equal(result, expected)
         assert_frame_equal(df, original)
 
-        result = df.astype({'b': np.float32, 'c': 'float32', 'd': np.float64})
+        dt2 = dtype_class({'b': np.float32, 'c': 'float32', 'd': np.float64})
+        result = df.astype(dt2)
         expected = DataFrame({
             'a': a,
             'b': Series([0.0, 1.0, 2.0, 3.0, 4.0], dtype='float32'),
@@ -471,19 +474,31 @@ def test_astype_dict(self):
         assert_frame_equal(df, original)
 
         # change all columns
-        assert_frame_equal(df.astype({'a': str, 'b': str, 'c': str, 'd': str}),
+        dt3 = dtype_class({'a': str, 'b': str, 'c': str, 'd': str})
+        assert_frame_equal(df.astype(dt3),
                            df.astype(str))
         assert_frame_equal(df, original)
 
         # error should be raised when using something other than column labels
         # in the keys of the dtype dict
-        pytest.raises(KeyError, df.astype, {'b': str, 2: str})
-        pytest.raises(KeyError, df.astype, {'e': str})
+        dt4 = dtype_class({'b': str, 2: str})
+        dt5 = dtype_class({'e': str})
+        pytest.raises(KeyError, df.astype, dt4)
+        pytest.raises(KeyError, df.astype, dt5)
         assert_frame_equal(df, original)
 
         # if the dtypes provided are the same as the original dtypes, the
         # resulting DataFrame should be the same as the original DataFrame
-        equiv = df.astype({col: df[col].dtype for col in df.columns})
+        dt6 = dtype_class({col: df[col].dtype for col in df.columns})
+        equiv = df.astype(dt6)
+        assert_frame_equal(df, equiv)
+        assert_frame_equal(df, original)
+
+        # GH 16717
+        # if dtypes provided is empty, the resulting DataFrame
+        # should be the same as the original DataFrame
+        dt7 = dtype_class({})
+        result = df.astype(dt7)
         assert_frame_equal(df, equiv)
         assert_frame_equal(df, original)
 
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
@@ -152,24 +152,35 @@ def test_astype_unicode(self):
             reload(sys)  # noqa
             sys.setdefaultencoding(former_encoding)
 
-    def test_astype_dict(self):
+    @pytest.mark.parametrize("dtype_class", [dict, Series])
+    def test_astype_dict_like(self, dtype_class):
         # see gh-7271
         s = Series(range(0, 10, 2), name='abc')
 
-        result = s.astype({'abc': str})
+        dt1 = dtype_class({'abc': str})
+        result = s.astype(dt1)
         expected = Series(['0', '2', '4', '6', '8'], name='abc')
         tm.assert_series_equal(result, expected)
 
-        result = s.astype({'abc': 'float64'})
+        dt2 = dtype_class({'abc': 'float64'})
+        result = s.astype(dt2)
         expected = Series([0.0, 2.0, 4.0, 6.0, 8.0], dtype='float64',
                           name='abc')
         tm.assert_series_equal(result, expected)
 
+        dt3 = dtype_class({'abc': str, 'def': str})
         with pytest.raises(KeyError):
-            s.astype({'abc': str, 'def': str})
+            s.astype(dt3)
 
+        dt4 = dtype_class({0: str})
         with pytest.raises(KeyError):
-            s.astype({0: str})
+            s.astype(dt4)
+
+        # GH16717
+        # if dtypes provided is empty, it should error
+        dt5 = dtype_class({})
+        with pytest.raises(KeyError):
+            s.astype(dt5)
 
     def test_astype_generic_timestamp_deprecated(self):
         # see gh-15524