Repository: pandas-dev/pandas

Issue:
COMPAT: df.to_dict('index') is not preserving dtypes
#### Code Sample

```python
import pandas as pd
from pandas.core.common import standardize_mapping

df = pd.DataFrame(dict(ints=range(5),floats=[float(i) for i in range(5)]))

into_c = standardize_mapping(dict)

print('Using iterrows:')
print(into_c((k, v.to_dict(dict)) for k, v in df.iterrows()))

```
#### Problem description

The problem appears when trying to export dataframe to dict using 'index' as param and when that dataframe conatains both float and int. Expected behaviour would be to separate ints and floats (as shown below). iAs recommended by the docs, we could use itertuples instead. I have included a code-snippet that could work. (Disclaimer: Sorry, this might be a pull request but I thought it could fit here since I'm not feeling familiar with the whole process)

#### Expected Output

```python
{0: {'ints': 0, 'floats': 0.0}, 1: {'ints': 1, 'floats': 1.0}, 2: {'ints': 2, 'floats': 2.0}, 3: {'ints': 3, 'floats': 3.0}, 4: {'ints': 4, 'floats': 4.0}}

print('\nUsing itertuples:')
print(into_c((t[0],dict(zip(df.columns,t[1:]))) for t in df.itertuples()))

```

#### Output of ``pd.show_versions()``

<details>

INSTALLED VERSIONS
------------------
commit: None
python: 3.5.2.final.0
python-bits: 64
OS: Darwin
OS-release: 14.5.0
machine: x86_64
processor: i386
byteorder: little
LC_ALL: en_US.UTF-8
LANG: en_US.UTF-8
LOCALE: en_US.UTF-8

pandas: 0.21.0
pytest: 2.9.2
pip: 9.0.1
setuptools: 27.2.0
Cython: 0.24.1
numpy: 1.13.1
scipy: 0.18.1
pyarrow: None
xarray: None
IPython: 5.1.0
sphinx: 1.4.6
patsy: 0.4.1
dateutil: 2.5.3
pytz: 2016.6.1
blosc: None
bottleneck: 1.1.0
tables: 3.2.3.1
numexpr: 2.6.1
feather: None
matplotlib: 1.5.3
openpyxl: 2.3.2
xlrd: 1.0.0
xlwt: 1.1.2
xlsxwriter: 0.9.3
lxml: 3.6.4
bs4: 4.5.1
html5lib: None
sqlalchemy: 1.0.13
pymysql: None
psycopg2: None
jinja2: 2.8
s3fs: None
fastparquet: None
pandas_gbq: None
pandas_datareader: None

</details>



Discussion:
@Anton-vBR not terribly averse to changing this if it will work in existing cases and also preserve dtypes. want to submit a PR?
@jreback Ok sure, I'll have a look at doing the necessary steps to submit a PR.

Resolving patch:
diff --git a/pandas/core/frame.py b/pandas/core/frame.py
--- a/pandas/core/frame.py
+++ b/pandas/core/frame.py
@@ -1102,7 +1102,8 @@ def to_dict(self, orient='dict', into=dict):
                            for k, v in zip(self.columns, np.atleast_1d(row)))
                     for row in self.values]
         elif orient.lower().startswith('i'):
-            return into_c((k, v.to_dict(into)) for k, v in self.iterrows())
+            return into_c((t[0], dict(zip(self.columns, t[1:])))
+                          for t in self.itertuples())
         else:
             raise ValueError("orient '%s' not understood" % orient)
 


Test patch:
diff --git a/pandas/tests/frame/test_convert_to.py b/pandas/tests/frame/test_convert_to.py
--- a/pandas/tests/frame/test_convert_to.py
+++ b/pandas/tests/frame/test_convert_to.py
@@ -5,6 +5,7 @@
 import pytest
 import pytz
 import collections
+from collections import OrderedDict, defaultdict
 import numpy as np
 
 from pandas import compat
@@ -288,3 +289,29 @@ def test_frame_to_dict_tz(self):
         ]
         tm.assert_dict_equal(result[0], expected[0])
         tm.assert_dict_equal(result[1], expected[1])
+
+    @pytest.mark.parametrize('into, expected', [
+        (dict, {0: {'int_col': 1, 'float_col': 1.0},
+                1: {'int_col': 2, 'float_col': 2.0},
+                2: {'int_col': 3, 'float_col': 3.0}}),
+        (OrderedDict, OrderedDict([(0, {'int_col': 1, 'float_col': 1.0}),
+                                   (1, {'int_col': 2, 'float_col': 2.0}),
+                                   (2, {'int_col': 3, 'float_col': 3.0})])),
+        (defaultdict(list), defaultdict(list,
+                                        {0: {'int_col': 1, 'float_col': 1.0},
+                                         1: {'int_col': 2, 'float_col': 2.0},
+                                         2: {'int_col': 3, 'float_col': 3.0}}))
+    ])
+    def test_to_dict_index_dtypes(self, into, expected):
+        # GH 18580
+        # When using to_dict(orient='index') on a dataframe with int
+        # and float columns only the int columns were cast to float
+
+        df = DataFrame({'int_col': [1, 2, 3],
+                        'float_col': [1.0, 2.0, 3.0]})
+
+        result = df.to_dict(orient='index', into=into)
+        cols = ['int_col', 'float_col']
+        result = DataFrame.from_dict(result, orient='index')[cols]
+        expected = DataFrame.from_dict(expected, orient='index')[cols]
+        tm.assert_frame_equal(result, expected)