Repository: pandas-dev/pandas
Pull request: BUG: Pandas changes dtypes of columns when no float (or other) assignments are done to this column #34573
- [ ] closes #34573
- [ ] tests added / passed
- [ ] passes `black pandas`
- [ ] passes `git diff upstream/master -u -- "*.py" | flake8 --diff`
- [ ] whatsnew entry


Problem statement:
BUG: Pandas changes dtypes of columns when no float (or other) assignments are done to this column
```python
df = pd.DataFrame()
df["a"] = int(0)

for col_name in ["b", "c"]:
    df[col_name] = 0.

print(df.dtypes)
    
for idx, b in enumerate([1,2,3]):
    df.loc[df.shape[0]] = {
        'a' : int(idx),
        'b' : float(b),
        'c' : float(b),
    }

print(df.dtypes)
```
#### Problem description

In the above example, pandas changes the dtype of the column `a` of dataframe `df` from _int64_ to _float64_, while there is no operation, that assigns non-integer value to this column. 

#### Expected Output

Currently, the output of this code is:
```
a      int64
b    float64
c    float64
dtype: object
a    float64
b    float64
c    float64
dtype: object
```

while the expected one is:
```
a      int64
b    float64
c    float64
dtype: object
a      int64
b    float64
c    float64
dtype: object
```

<details>
  <summary>pd.show_versions() output</summary>
  
```
INSTALLED VERSIONS
------------------
commit           : None
python           : 3.6.9.final.0
python-bits      : 64
OS               : Linux
OS-release       : 4.15.0-101-generic
machine          : x86_64
processor        : x86_64
byteorder        : little
LC_ALL           : None
LANG             : C.UTF-8
LOCALE           : en_US.UTF-8

pandas           : 1.0.4
numpy            : 1.18.1
pytz             : 2019.3
dateutil         : 2.8.1
pip              : 20.1.1
setuptools       : 44.0.0
Cython           : None
pytest           : None
hypothesis       : None
sphinx           : None
blosc            : None
feather          : None
xlsxwriter       : None
lxml.etree       : 4.5.0
html5lib         : None
pymysql          : None
psycopg2         : None
jinja2           : 2.10.3
IPython          : 7.11.1
pandas_datareader: None
bs4              : None
bottleneck       : None
fastparquet      : None
gcsfs            : None
lxml.etree       : 4.5.0
matplotlib       : 3.1.2
numexpr          : None
odfpy            : None
openpyxl         : None
pandas_gbq       : None
pyarrow          : None
pytables         : None
pytest           : None
pyxlsb           : None
s3fs             : None
scipy            : 1.4.1
sqlalchemy       : None
tables           : None
tabulate         : None
xarray           : None
xlrd             : 1.2.0
xlwt             : None
xlsxwriter       : None
numba            : None
```
</details>

Hints from issue discussion:
My guess is that we might convert the dictionary into a Series first (which would turn it into a float series). Just a guess though, didn't actually check.

Commit message: typo

Patch to review:
diff --git a/doc/source/whatsnew/v1.1.0.rst b/doc/source/whatsnew/v1.1.0.rst
index 2243790a663df..1ff717a743162 100644
--- a/doc/source/whatsnew/v1.1.0.rst
+++ b/doc/source/whatsnew/v1.1.0.rst
@@ -892,6 +892,7 @@ Indexing
 - Bug in :meth:`DataFrame.truncate` and :meth:`Series.truncate` where index was assumed to be monotone increasing (:issue:`33756`)
 - Indexing with a list of strings representing datetimes failed on :class:`DatetimeIndex` or :class:`PeriodIndex`(:issue:`11278`)
 - Bug in :meth:`Series.at` when used with a :class:`MultiIndex` would raise an exception on valid inputs (:issue:`26989`)
+- Bug in :meth:`DataFrame.loc` with dictionary of values changes columns with dtype of ``int`` to ``float`` (:issue:`34573`)
 - Bug in :meth:`Series.loc` when used with a :class:`MultiIndex` would raise an IndexingError when accessing a None value (:issue:`34318`)
 
 Missing
diff --git a/pandas/core/indexing.py b/pandas/core/indexing.py
index ab515cb5e606b..c94246d117233 100644
--- a/pandas/core/indexing.py
+++ b/pandas/core/indexing.py
@@ -1844,7 +1844,8 @@ def _setitem_with_indexer_missing(self, indexer, value):
                     if len(value) != len(self.obj.columns):
                         raise ValueError("cannot set a row with mismatched columns")
 
-                value = Series(value, index=self.obj.columns, name=indexer)
+                dtype = object if isinstance(value, dict) else None
+                value = Series(value, index=self.obj.columns, name=indexer, dtype=dtype)
 
             self.obj._mgr = self.obj.append(value)._mgr
             self.obj._maybe_update_cacher(clear=True)
diff --git a/pandas/tests/frame/indexing/test_setitem.py b/pandas/tests/frame/indexing/test_setitem.py
index d53665539309c..8fcdae95fbab5 100644
--- a/pandas/tests/frame/indexing/test_setitem.py
+++ b/pandas/tests/frame/indexing/test_setitem.py
@@ -126,3 +126,27 @@ def test_setitem_with_unaligned_sparse_value(self):
         df["new_column"] = sp_series
         expected = Series(SparseArray([1, 0, 0]), name="new_column")
         tm.assert_series_equal(df["new_column"], expected)
+
+    def test_setitem_dict_preserves_dtypes(self):
+        # https://github.com/pandas-dev/pandas/issues/34573
+        expected = DataFrame(
+            {
+                "a": Series([0, 1, 2], dtype="int64"),
+                "b": Series([1, 2, 3], dtype=float),
+                "c": Series([1, 2, 3], dtype=float),
+            }
+        )
+        df = DataFrame(
+            {
+                "a": Series([], dtype="int64"),
+                "b": Series([], dtype=float),
+                "c": Series([], dtype=float),
+            }
+        )
+        for idx, b in enumerate([1, 2, 3]):
+            df.loc[df.shape[0]] = {
+                "a": int(idx),
+                "b": float(b),
+                "c": float(b),
+            }
+        tm.assert_frame_equal(df, expected)


Code review comments:
diff_hunk: @@ -1844,7 +1844,8 @@ def _setitem_with_indexer_missing(self, indexer, value):
                     if len(value) != len(self.obj.columns):
                         raise ValueError("cannot set a row with mismatched columns")
 
-                value = Series(value, index=self.obj.columns, name=indexer)
+                dtype = object if isinstance(value, dict) else None
path: pandas/core/indexing.py
text: @user1:
i would integrate this with the if clause above 

@user1:
or rather the if/else on L1840

@author:
updated + green