Repository: pandas-dev/pandas
Pull request: BUG: Ensure series/frame mode() keeps int index
- [x] closes #33321
- [x] tests added / passed
- [x] passes `black pandas`
- [x] passes `git diff upstream/master -u -- "*.py" | flake8 --diff`
- [x] whatsnew entry


Problem statement:
BUG: DataFrame.mode index dtype is not type stable
- [x] I have checked that this issue has not already been reported.

- [x] I have confirmed this bug exists on the latest version of pandas.

- [x] (optional) I have confirmed this bug exists on the master branch of pandas.

---



#### Code Sample, a copy-pastable example

The `.index.dtype` is not stable for DataFrame.mode. It depends on whether the DataFrame is empty and possible the dtypes

```python
In [60]: pd.DataFrame([], columns=['a', 'b']).mode().index.dtype
Out[60]: dtype('O')
```

#### Problem description

The index dtype should always be Int64, to match the non-empty case

#### Expected Output

```python
In [61]: pd.DataFrame({"A": ['a']}).mode().index.dtype
Out[61]: dtype('int64')
```

Hints from issue discussion:
@TomAugspurger 

This is happening because the empty dataframe returns an empty index and therefore the dtype is dtype('o'):
```
pd.DataFrame([], columns=['a', 'b']).index.dtype
dtype('O')
```

```
pd.DataFrame({"A": ['a']}).index.dtype
dtype('int64')
```

So there are 2 solutions I can think of.
1) We change mode when it's an empty dataframe to return a dataframe where the mode for that is actually NaN.
So go from:
```
pd.DataFrame([], columns=['a', 'b']).mode()
Empty DataFrame
Columns: [a, b]
Index: []
```

To something like:
```
pd.DataFrame([], columns=['a', 'b']).mode()
   a    b      
0  NaN  NaN
```

2) We change what empty dataframe returns when index is called.

Do you like any of those solutions?

We can construct an empty index with the right dtype (int64)

```python
In [5]: pd.DataFrame(columns=['a', 'b'], index=pd.Index([], dtype='int64')).index.dtype
Out[5]: dtype('int64')
```

Commit message: BUG: ensure series/frame mode() keeps int index

Patch to review:
diff --git a/doc/source/whatsnew/v1.3.0.rst b/doc/source/whatsnew/v1.3.0.rst
index c7573ee860744..a0b08f1afaeb7 100644
--- a/doc/source/whatsnew/v1.3.0.rst
+++ b/doc/source/whatsnew/v1.3.0.rst
@@ -202,6 +202,7 @@ Numeric
 ^^^^^^^
 - Bug in :meth:`DataFrame.quantile`, :meth:`DataFrame.sort_values` causing incorrect subsequent indexing behavior (:issue:`38351`)
 - Bug in :meth:`DataFrame.select_dtypes` with ``include=np.number`` now retains numeric ``ExtensionDtype`` columns (:issue:`35340`)
+- Bug in :meth:`DataFrame.mode` and :meth:`Series.mode` not keeping consistent integer :class:`Index` for empty input (:issue:`33321`)
 
 Conversion
 ^^^^^^^^^^
diff --git a/pandas/core/algorithms.py b/pandas/core/algorithms.py
index 2098392cf70a9..e8d650f448a91 100644
--- a/pandas/core/algorithms.py
+++ b/pandas/core/algorithms.py
@@ -927,7 +927,7 @@ def mode(values, dropna: bool = True) -> Series:
     -------
     mode : Series
     """
-    from pandas import Series
+    from pandas import Index, Series
 
     values = _ensure_arraylike(values)
     original = values
@@ -954,7 +954,9 @@ def mode(values, dropna: bool = True) -> Series:
         warn(f"Unable to sort modes: {err}")
 
     result = _reconstruct_data(result, original.dtype, original)
-    return Series(result)
+    # Ensure index is type stable (should always use int index)
+    index = None if len(result) else Index([], dtype="int64")
+    return Series(result, index=index)
 
 
 def rank(
diff --git a/pandas/core/frame.py b/pandas/core/frame.py
index 9097ac13192c9..0185a579180f7 100644
--- a/pandas/core/frame.py
+++ b/pandas/core/frame.py
@@ -9241,7 +9241,12 @@ def mode(
         def f(s):
             return s.mode(dropna=dropna)
 
-        return data.apply(f, axis=axis)
+        data = data.apply(f, axis=axis)
+        # Ensure index is type stable (should always use int index)
+        if data.empty:
+            data.index = Index([], dtype="int64")
+
+        return data
 
     def quantile(
         self,
diff --git a/pandas/tests/frame/methods/test_mode.py b/pandas/tests/frame/methods/test_mode.py
new file mode 100644
index 0000000000000..a17926b002dc6
--- /dev/null
+++ b/pandas/tests/frame/methods/test_mode.py
@@ -0,0 +1,9 @@
+from pandas import DataFrame, Index
+import pandas._testing as tm
+
+
+def test_empty_df_mode():
+    df = DataFrame([], columns=["a", "b"])
+    result = df.mode()
+    expected = DataFrame([], columns=["a", "b"], index=Index([], dtype="int64"))
+    tm.assert_frame_equal(result, expected)
diff --git a/pandas/tests/test_algos.py b/pandas/tests/test_algos.py
index 8fcc241348f27..8454b5dfefb04 100644
--- a/pandas/tests/test_algos.py
+++ b/pandas/tests/test_algos.py
@@ -2253,7 +2253,7 @@ def test_int64_add_overflow():
 
 class TestMode:
     def test_no_mode(self):
-        exp = Series([], dtype=np.float64)
+        exp = Series([], dtype=np.float64, index=Index([], dtype="int64"))
         tm.assert_series_equal(algos.mode([]), exp)
 
     def test_mode_single(self):


Code review comments:
diff_hunk: @@ -9241,7 +9241,12 @@ def mode(
         def f(s):
             return s.mode(dropna=dropna)
 
-        return data.apply(f, axis=axis)
+        data = data.apply(f, axis=axis)
+        # Ensure index is type stable (should always use int index)
+        if data.empty:
+            data.index = Index([], dtype="int64")
path: pandas/core/frame.py
text: @user1:
is this right for 32-bit machines?

@author:
Changed to `int`, thanks for catching that