From c41b85f1c0f655c39c146b8022f032aea2f612a3 Mon Sep 17 00:00:00 2001 From: Louis Dumont Date: Fri, 12 Jul 2019 18:12:08 +0200 Subject: [PATCH 01/12] BUG: Wrong dtype when resetting a multiindex with missing values. (#19602 and #24206) - Fixed the bad path in reset_index where the dtype was ignored if there are only missing values. - Simplified the structure of _maybe_casted_values in reset_index by using as much as possible the take method of ExtensionArray's --- pandas/core/frame.py | 40 +++++++++------------------ pandas/tests/frame/test_alter_axes.py | 31 +++++++++++++++++++++ 2 files changed, 44 insertions(+), 27 deletions(-) diff --git a/pandas/core/frame.py b/pandas/core/frame.py index 53cb0cedc208b..41419694b733f 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -4610,33 +4610,19 @@ def _maybe_casted_values(index, labels=None): # if we have the labels, extract the values with a mask if labels is not None: - mask = labels == -1 - - # we can have situations where the whole mask is -1, - # meaning there is nothing found in labels, so make all nan's - if mask.all(): - values = np.empty(len(mask)) - values.fill(np.nan) + if isinstance(values, np.ndarray): + mask = labels == -1 + # we can have situations where the whole mask is -1, + # meaning there is nothing found in labels, so make all nan's + if mask.all(): + values = np.empty(len(mask), dtype=values.dtype) + values.fill(np.nan) + else: + values = values.take(labels) + if mask.any(): + values, _ = maybe_upcast_putmask(values, mask, np.nan) else: - values = values.take(labels) - - # TODO(https://github.com/pandas-dev/pandas/issues/24206) - # Push this into maybe_upcast_putmask? - # We can't pass EAs there right now. Looks a bit - # complicated. - # So we unbox the ndarray_values, op, re-box. - values_type = type(values) - values_dtype = values.dtype - - if issubclass(values_type, DatetimeLikeArray): - values = values._data - - if mask.any(): - values, changed = maybe_upcast_putmask(values, mask, np.nan) - - if issubclass(values_type, DatetimeLikeArray): - values = values_type(values, dtype=values_dtype) - + values = values.take(labels, allow_fill=True) return values new_index = ibase.default_index(len(new_obj)) @@ -4680,7 +4666,7 @@ def _maybe_casted_values(index, labels=None): missing = self.columns.nlevels - len(name_lst) name_lst += [col_fill] * missing name = tuple(name_lst) - # to ndarray and maybe infer different dtype + # to array-like and maybe infer different dtype level_values = _maybe_casted_values(lev, lab) new_obj.insert(0, name, level_values) diff --git a/pandas/tests/frame/test_alter_axes.py b/pandas/tests/frame/test_alter_axes.py index c57b2a6964f39..64532129128d2 100644 --- a/pandas/tests/frame/test_alter_axes.py +++ b/pandas/tests/frame/test_alter_axes.py @@ -12,6 +12,7 @@ from pandas import ( Categorical, + CategoricalIndex, DataFrame, DatetimeIndex, Index, @@ -1186,6 +1187,36 @@ def test_reset_index_multiindex_nan(self): rs = df.set_index(["A", "B"]).reset_index() tm.assert_frame_equal(rs, df) + # GH 19602 + df = DataFrame({0: DatetimeIndex([]), 1: []}) + rs = df.set_index([0, 1]).reset_index() + tm.assert_frame_equal(rs, df) + + idx = MultiIndex( + levels=[DatetimeIndex([]), + DatetimeIndex(['2015-01-01 11:00:00'])], + codes=[[-1, -1], [0, -1]], + names=[0, 1] + ) + df = DataFrame(index=idx).reset_index() + + xp = DataFrame({ + 0: DatetimeIndex([np.nan, np.nan]), + 1: DatetimeIndex(['2015-01-01 11:00:00', np.nan]) + }) + tm.assert_frame_equal(df, xp) + + # GH 24206 + idx = MultiIndex([CategoricalIndex(['A', 'B']), CategoricalIndex(['a', 'b'])], + [[0, 0, 1, 1], [0, 1, 0, -1]]) + df = DataFrame({'col': range(len(idx))}, index=idx).reset_index() + xp = DataFrame({ + 'level_0': CategoricalIndex(['A', 'A', 'B', 'B']), + 'level_1': CategoricalIndex(['a', 'b', 'a', np.nan]), + 'col': [0, 1, 2, 3] + }) + tm.assert_frame_equal(df, xp) + def test_reset_index_with_datetimeindex_cols(self): # GH5818 # From 4d2b2e8a60591c47fd65e2ac45c9cbcfbb9fef51 Mon Sep 17 00:00:00 2001 From: Louis Dumont Date: Sat, 13 Jul 2019 00:12:08 +0200 Subject: [PATCH 02/12] Extracted tests into new functions. --- pandas/tests/frame/test_alter_axes.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pandas/tests/frame/test_alter_axes.py b/pandas/tests/frame/test_alter_axes.py index 64532129128d2..c5a98a21a5e63 100644 --- a/pandas/tests/frame/test_alter_axes.py +++ b/pandas/tests/frame/test_alter_axes.py @@ -1187,6 +1187,7 @@ def test_reset_index_multiindex_nan(self): rs = df.set_index(["A", "B"]).reset_index() tm.assert_frame_equal(rs, df) + def test_reset_index_multiindex_datetime_all_nan(self): # GH 19602 df = DataFrame({0: DatetimeIndex([]), 1: []}) rs = df.set_index([0, 1]).reset_index() @@ -1206,6 +1207,7 @@ def test_reset_index_multiindex_nan(self): }) tm.assert_frame_equal(df, xp) + def test_reset_index_multiindex_categorical_with_nan(self): # GH 24206 idx = MultiIndex([CategoricalIndex(['A', 'B']), CategoricalIndex(['a', 'b'])], [[0, 0, 1, 1], [0, 1, 0, -1]]) From cbf7499398bbceeab63882fa9ecd003a9f34e100 Mon Sep 17 00:00:00 2001 From: Louis Dumont Date: Sat, 13 Jul 2019 10:18:52 +0200 Subject: [PATCH 03/12] Fixed linting and compatibility problems. - Removed unused import - Enforced column order in tests for compat with python 3.5 --- pandas/core/frame.py | 1 - pandas/tests/frame/test_alter_axes.py | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/pandas/core/frame.py b/pandas/core/frame.py index 41419694b733f..80e7e500fa975 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -83,7 +83,6 @@ from pandas.core import algorithms, common as com, nanops, ops from pandas.core.accessor import CachedAccessor from pandas.core.arrays import Categorical, ExtensionArray -from pandas.core.arrays.datetimelike import DatetimeLikeArrayMixin as DatetimeLikeArray from pandas.core.arrays.sparse import SparseFrameAccessor from pandas.core.generic import NDFrame, _shared_docs from pandas.core.index import ( diff --git a/pandas/tests/frame/test_alter_axes.py b/pandas/tests/frame/test_alter_axes.py index c5a98a21a5e63..cccc91a81a7f8 100644 --- a/pandas/tests/frame/test_alter_axes.py +++ b/pandas/tests/frame/test_alter_axes.py @@ -1189,7 +1189,7 @@ def test_reset_index_multiindex_nan(self): def test_reset_index_multiindex_datetime_all_nan(self): # GH 19602 - df = DataFrame({0: DatetimeIndex([]), 1: []}) + df = DataFrame({0: DatetimeIndex([]), 1: []}, columns=[0, 1]) rs = df.set_index([0, 1]).reset_index() tm.assert_frame_equal(rs, df) @@ -1204,7 +1204,7 @@ def test_reset_index_multiindex_datetime_all_nan(self): xp = DataFrame({ 0: DatetimeIndex([np.nan, np.nan]), 1: DatetimeIndex(['2015-01-01 11:00:00', np.nan]) - }) + }, columns=[0, 1]) tm.assert_frame_equal(df, xp) def test_reset_index_multiindex_categorical_with_nan(self): @@ -1216,7 +1216,7 @@ def test_reset_index_multiindex_categorical_with_nan(self): 'level_0': CategoricalIndex(['A', 'A', 'B', 'B']), 'level_1': CategoricalIndex(['a', 'b', 'a', np.nan]), 'col': [0, 1, 2, 3] - }) + }, columns=['level_0', 'level_1', 'col']) tm.assert_frame_equal(df, xp) def test_reset_index_with_datetimeindex_cols(self): From 07f8161e9f5b12fe8fa71ba485a8acef7677b0a3 Mon Sep 17 00:00:00 2001 From: Louis Dumont Date: Mon, 15 Jul 2019 02:35:28 +0200 Subject: [PATCH 04/12] Reformatted to pass black pandas. --- pandas/tests/frame/test_alter_axes.py | 37 ++++++++++++++++----------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/pandas/tests/frame/test_alter_axes.py b/pandas/tests/frame/test_alter_axes.py index cccc91a81a7f8..5bb6a70dbb2b3 100644 --- a/pandas/tests/frame/test_alter_axes.py +++ b/pandas/tests/frame/test_alter_axes.py @@ -1194,29 +1194,36 @@ def test_reset_index_multiindex_datetime_all_nan(self): tm.assert_frame_equal(rs, df) idx = MultiIndex( - levels=[DatetimeIndex([]), - DatetimeIndex(['2015-01-01 11:00:00'])], + levels=[DatetimeIndex([]), DatetimeIndex(["2015-01-01 11:00:00"])], codes=[[-1, -1], [0, -1]], - names=[0, 1] + names=[0, 1], ) df = DataFrame(index=idx).reset_index() - xp = DataFrame({ - 0: DatetimeIndex([np.nan, np.nan]), - 1: DatetimeIndex(['2015-01-01 11:00:00', np.nan]) - }, columns=[0, 1]) + xp = DataFrame( + { + 0: DatetimeIndex([np.nan, np.nan]), + 1: DatetimeIndex(["2015-01-01 11:00:00", np.nan]), + }, + columns=[0, 1], + ) tm.assert_frame_equal(df, xp) def test_reset_index_multiindex_categorical_with_nan(self): # GH 24206 - idx = MultiIndex([CategoricalIndex(['A', 'B']), CategoricalIndex(['a', 'b'])], - [[0, 0, 1, 1], [0, 1, 0, -1]]) - df = DataFrame({'col': range(len(idx))}, index=idx).reset_index() - xp = DataFrame({ - 'level_0': CategoricalIndex(['A', 'A', 'B', 'B']), - 'level_1': CategoricalIndex(['a', 'b', 'a', np.nan]), - 'col': [0, 1, 2, 3] - }, columns=['level_0', 'level_1', 'col']) + idx = MultiIndex( + [CategoricalIndex(["A", "B"]), CategoricalIndex(["a", "b"])], + [[0, 0, 1, 1], [0, 1, 0, -1]], + ) + df = DataFrame({"col": range(len(idx))}, index=idx).reset_index() + xp = DataFrame( + { + "level_0": CategoricalIndex(["A", "A", "B", "B"]), + "level_1": CategoricalIndex(["a", "b", "a", np.nan]), + "col": [0, 1, 2, 3], + }, + columns=["level_0", "level_1", "col"], + ) tm.assert_frame_equal(df, xp) def test_reset_index_with_datetimeindex_cols(self): From 865a6eb3c7350ba43d68d3c2341b5314d3b81cf9 Mon Sep 17 00:00:00 2001 From: Louis Dumont Date: Mon, 15 Jul 2019 03:14:29 +0200 Subject: [PATCH 05/12] Added whatsnew entries. --- doc/source/whatsnew/v0.25.0.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/source/whatsnew/v0.25.0.rst b/doc/source/whatsnew/v0.25.0.rst index eeaafd7ad7d51..14f6bc48b8595 100644 --- a/doc/source/whatsnew/v0.25.0.rst +++ b/doc/source/whatsnew/v0.25.0.rst @@ -1068,6 +1068,8 @@ MultiIndex ^^^^^^^^^^ - Bug in which incorrect exception raised by :class:`Timedelta` when testing the membership of :class:`MultiIndex` (:issue:`24570`) +- Bug in :meth:`DataFrame.reset_index` where dtype was sometimes not preserved for :class:`MultiIndex` that is empty or with missing values (:issue:`19602`) +- Bug in which :meth:`DataFrame.reset_index` did not work for :class:`MultiIndex` with :class:`CategoricalIndex` levels with missing values (:issue:`24206`) - I/O From 94f3a28d9430f07b70c28fa2c4f342247b86f604 Mon Sep 17 00:00:00 2001 From: Louis Dumont Date: Tue, 16 Jul 2019 05:46:39 +0200 Subject: [PATCH 06/12] Refactored tests with parametrization. --- pandas/tests/frame/test_alter_axes.py | 95 ++++++++------------------- 1 file changed, 28 insertions(+), 67 deletions(-) diff --git a/pandas/tests/frame/test_alter_axes.py b/pandas/tests/frame/test_alter_axes.py index 5bb6a70dbb2b3..2a5b4059368b0 100644 --- a/pandas/tests/frame/test_alter_axes.py +++ b/pandas/tests/frame/test_alter_axes.py @@ -1158,73 +1158,34 @@ def test_reset_index_multiindex_col(self): ) tm.assert_frame_equal(rs, xp) - def test_reset_index_multiindex_nan(self): - # GH6322, testing reset_index on MultiIndexes - # when we have a nan or all nan - df = DataFrame( - {"A": ["a", "b", "c"], "B": [0, 1, np.nan], "C": np.random.rand(3)} - ) - rs = df.set_index(["A", "B"]).reset_index() - tm.assert_frame_equal(rs, df) - - df = DataFrame( - {"A": [np.nan, "b", "c"], "B": [0, 1, 2], "C": np.random.rand(3)} - ) - rs = df.set_index(["A", "B"]).reset_index() - tm.assert_frame_equal(rs, df) - - df = DataFrame({"A": ["a", "b", "c"], "B": [0, 1, 2], "C": [np.nan, 1.1, 2.2]}) - rs = df.set_index(["A", "B"]).reset_index() - tm.assert_frame_equal(rs, df) - - df = DataFrame( - { - "A": ["a", "b", "c"], - "B": [np.nan, np.nan, np.nan], - "C": np.random.rand(3), - } - ) - rs = df.set_index(["A", "B"]).reset_index() - tm.assert_frame_equal(rs, df) - - def test_reset_index_multiindex_datetime_all_nan(self): - # GH 19602 - df = DataFrame({0: DatetimeIndex([]), 1: []}, columns=[0, 1]) - rs = df.set_index([0, 1]).reset_index() - tm.assert_frame_equal(rs, df) - - idx = MultiIndex( - levels=[DatetimeIndex([]), DatetimeIndex(["2015-01-01 11:00:00"])], - codes=[[-1, -1], [0, -1]], - names=[0, 1], - ) - df = DataFrame(index=idx).reset_index() - - xp = DataFrame( - { - 0: DatetimeIndex([np.nan, np.nan]), - 1: DatetimeIndex(["2015-01-01 11:00:00", np.nan]), - }, - columns=[0, 1], - ) - tm.assert_frame_equal(df, xp) - - def test_reset_index_multiindex_categorical_with_nan(self): - # GH 24206 - idx = MultiIndex( - [CategoricalIndex(["A", "B"]), CategoricalIndex(["a", "b"])], - [[0, 0, 1, 1], [0, 1, 0, -1]], - ) - df = DataFrame({"col": range(len(idx))}, index=idx).reset_index() - xp = DataFrame( - { - "level_0": CategoricalIndex(["A", "A", "B", "B"]), - "level_1": CategoricalIndex(["a", "b", "a", np.nan]), - "col": [0, 1, 2, 3], - }, - columns=["level_0", "level_1", "col"], - ) - tm.assert_frame_equal(df, xp) + @pytest.mark.parametrize( + "columns", + [ + [["a", "b", "c"], [0, 1, np.nan], np.random.rand(3)], + [[np.nan, "b", "c"], [0, 1, 2], np.random.rand(3)], + [["a", "b", "c"], [0, 1, 2], [np.nan, 1.1, 2.2]], + [["a", "b", "c"], [np.nan, np.nan, np.nan], np.random.rand(3)], + [ + DatetimeIndex([np.nan, np.nan]), + DatetimeIndex(["2015-01-01 11:00:00", np.nan]), + np.random.rand(2), + ], + [ + CategoricalIndex(["A", "A", "B", "B"]), + CategoricalIndex(["a", "b", "a", np.nan]), + np.random.rand(4), + ], + [DatetimeIndex([]), [], []], + ], + ) + def test_reset_index_multiindex_nan(self, columns): + # GH6322, GH19602, GH24206: testing reset_index on MultiIndex + # with some nans or all nans + column_names = ["A", "B", "C"] + columns = dict(zip(column_names, columns)) + df = DataFrame(columns, columns=column_names) + result = df.set_index(column_names[:2]).reset_index() + tm.assert_frame_equal(df, result) def test_reset_index_with_datetimeindex_cols(self): # GH5818 From 2af935ab57fec3e2592c43dd68486a24de95af23 Mon Sep 17 00:00:00 2001 From: Louis Dumont Date: Tue, 16 Jul 2019 06:06:59 +0200 Subject: [PATCH 07/12] Moved _maybe_convert_values to pandas.core.dtypes.cast --- pandas/core/dtypes/cast.py | 43 ++++++++++++++++++++++++++++++++++++++ pandas/core/frame.py | 31 ++------------------------- 2 files changed, 45 insertions(+), 29 deletions(-) diff --git a/pandas/core/dtypes/cast.py b/pandas/core/dtypes/cast.py index f483cf520754b..1555b7d499075 100644 --- a/pandas/core/dtypes/cast.py +++ b/pandas/core/dtypes/cast.py @@ -1384,3 +1384,46 @@ def maybe_cast_to_integer_array(arr, dtype, copy=False): if is_integer_dtype(dtype) and (is_float_dtype(arr) or is_object_dtype(arr)): raise ValueError("Trying to coerce float values to integers") + + +def maybe_casted_values(index, codes=None): + """ + Convert an index, given directly or as a pair (level, codes), to a 1D array + containing its values. + + Parameters + ---------- + index : Index + codes : sequence of integers (optional) + + Returns + ------- + ExtensionArray or ndarray + If codes is `None`, the values of `index`. + If codes is passed, an array obtained by taking from `index` the indices + contained in `codes`. + """ + from pandas.core.indexes.period import PeriodIndex + from pandas import DatetimeIndex + + values = index._values + if not isinstance(index, (PeriodIndex, DatetimeIndex)): + if values.dtype == np.object_: + values = lib.maybe_convert_objects(values) + + # if we have the labels, extract the values with a mask + if codes is not None: + if isinstance(values, np.ndarray): + mask = codes == -1 + # we can have situations where the whole mask is -1, + # meaning there is nothing found in labels, so make all nan's + if mask.all(): + values = np.empty(len(mask), dtype=values.dtype) + values.fill(np.nan) + else: + values = values.take(codes) + if mask.any(): + values, _ = maybe_upcast_putmask(values, mask, np.nan) + else: + values = values.take(codes, allow_fill=True) + return values diff --git a/pandas/core/frame.py b/pandas/core/frame.py index 80e7e500fa975..fc6641e27cb2d 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -45,8 +45,7 @@ maybe_downcast_to_dtype, maybe_infer_to_datetimelike, maybe_upcast, - maybe_upcast_putmask, -) + maybe_casted_values) from pandas.core.dtypes.common import ( ensure_float64, ensure_int64, @@ -92,8 +91,6 @@ ensure_index_from_sequences, ) from pandas.core.indexes import base as ibase -from pandas.core.indexes.datetimes import DatetimeIndex -from pandas.core.indexes.period import PeriodIndex from pandas.core.indexing import ( check_bool_indexer, convert_to_index_sliceable, @@ -4601,29 +4598,6 @@ class max type else: new_obj = self.copy() - def _maybe_casted_values(index, labels=None): - values = index._values - if not isinstance(index, (PeriodIndex, DatetimeIndex)): - if values.dtype == np.object_: - values = lib.maybe_convert_objects(values) - - # if we have the labels, extract the values with a mask - if labels is not None: - if isinstance(values, np.ndarray): - mask = labels == -1 - # we can have situations where the whole mask is -1, - # meaning there is nothing found in labels, so make all nan's - if mask.all(): - values = np.empty(len(mask), dtype=values.dtype) - values.fill(np.nan) - else: - values = values.take(labels) - if mask.any(): - values, _ = maybe_upcast_putmask(values, mask, np.nan) - else: - values = values.take(labels, allow_fill=True) - return values - new_index = ibase.default_index(len(new_obj)) if level is not None: if not isinstance(level, (tuple, list)): @@ -4665,8 +4639,7 @@ def _maybe_casted_values(index, labels=None): missing = self.columns.nlevels - len(name_lst) name_lst += [col_fill] * missing name = tuple(name_lst) - # to array-like and maybe infer different dtype - level_values = _maybe_casted_values(lev, lab) + level_values = maybe_casted_values(lev, lab) new_obj.insert(0, name, level_values) new_obj.index = new_index From 425acee2d1dea1b0a790321ac9a7eb6665227fe4 Mon Sep 17 00:00:00 2001 From: Louis Dumont Date: Wed, 14 Aug 2019 15:06:22 +0200 Subject: [PATCH 08/12] Removed weird type check in _maybe_convert_values. --- pandas/core/dtypes/cast.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/pandas/core/dtypes/cast.py b/pandas/core/dtypes/cast.py index 1555b7d499075..78779faa77096 100644 --- a/pandas/core/dtypes/cast.py +++ b/pandas/core/dtypes/cast.py @@ -1403,13 +1403,9 @@ def maybe_casted_values(index, codes=None): If codes is passed, an array obtained by taking from `index` the indices contained in `codes`. """ - from pandas.core.indexes.period import PeriodIndex - from pandas import DatetimeIndex - values = index._values - if not isinstance(index, (PeriodIndex, DatetimeIndex)): - if values.dtype == np.object_: - values = lib.maybe_convert_objects(values) + if values.dtype == np.object_: + values = lib.maybe_convert_objects(values) # if we have the labels, extract the values with a mask if codes is not None: From 7569ba8438f3d3187bd2bdd9f8dfa1d46c7b03ff Mon Sep 17 00:00:00 2001 From: Louis Dumont Date: Wed, 14 Aug 2019 17:50:16 +0200 Subject: [PATCH 09/12] Fixed import sorting. --- pandas/core/frame.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/core/frame.py b/pandas/core/frame.py index 3ce30c7da8407..cce0def99febb 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -41,11 +41,11 @@ infer_dtype_from_scalar, invalidate_string_dtypes, maybe_cast_to_datetime, + maybe_casted_values, maybe_convert_platform, maybe_downcast_to_dtype, maybe_infer_to_datetimelike, maybe_upcast, - maybe_casted_values, ) from pandas.core.dtypes.common import ( ensure_float64, From 0cf50d0814f179f8a166f2701dd7733f32018960 Mon Sep 17 00:00:00 2001 From: Louis Dumont Date: Mon, 26 Aug 2019 19:41:05 +0200 Subject: [PATCH 10/12] Shortened summary for maybe_casted_values. --- pandas/core/dtypes/cast.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pandas/core/dtypes/cast.py b/pandas/core/dtypes/cast.py index 4e88a46f1012b..9abbed07aeb68 100644 --- a/pandas/core/dtypes/cast.py +++ b/pandas/core/dtypes/cast.py @@ -1395,8 +1395,7 @@ def maybe_cast_to_integer_array(arr, dtype, copy=False): def maybe_casted_values(index, codes=None): """ - Convert an index, given directly or as a pair (level, codes), to a 1D array - containing its values. + Convert an index, given directly or as a pair (level, codes), to a 1D array. Parameters ---------- From e2288a175951e4d54192379c5a11b90f13aa6de0 Mon Sep 17 00:00:00 2001 From: Louis Dumont Date: Thu, 29 Aug 2019 00:40:29 +0200 Subject: [PATCH 11/12] Added basic test for GH19602. --- pandas/tests/frame/test_alter_axes.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pandas/tests/frame/test_alter_axes.py b/pandas/tests/frame/test_alter_axes.py index 74c10516122f9..7d2be2357feca 100644 --- a/pandas/tests/frame/test_alter_axes.py +++ b/pandas/tests/frame/test_alter_axes.py @@ -1187,6 +1187,15 @@ def test_reset_index_multiindex_nan(self, columns): result = df.set_index(column_names[:2]).reset_index() tm.assert_frame_equal(df, result) + def test_reset_index_multiindex_empty(self): + # GH19602: preserve dtypes when resetting multiindex of + # empty dataframe + idx = MultiIndex.from_product([[0, 1], [1, 2]]) + empty_df = DataFrame(index=idx)[:0] + types = empty_df.reset_index().dtypes + assert types[0] == np.int64 + assert types[1] == np.int64 + def test_reset_index_with_datetimeindex_cols(self): # GH5818 # From f1d110a0a7e8d13ec570b021938aacbc5c823284 Mon Sep 17 00:00:00 2001 From: Louis Dumont Date: Thu, 29 Aug 2019 00:42:41 +0200 Subject: [PATCH 12/12] Moved whatsnew entries to v1.0.0 --- doc/source/whatsnew/v0.25.1.rst | 4 ++-- doc/source/whatsnew/v1.0.0.rst | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/doc/source/whatsnew/v0.25.1.rst b/doc/source/whatsnew/v0.25.1.rst index 8de7ae26f6d7d..dfa216b1db56e 100644 --- a/doc/source/whatsnew/v0.25.1.rst +++ b/doc/source/whatsnew/v0.25.1.rst @@ -97,8 +97,8 @@ Missing MultiIndex ^^^^^^^^^^ -- Bug in :meth:`DataFrame.reset_index` where dtype was sometimes not preserved for :class:`MultiIndex` that is empty or with missing values (:issue:`19602`) -- Bug in which :meth:`DataFrame.reset_index` did not work for :class:`MultiIndex` with :class:`CategoricalIndex` levels with missing values (:issue:`24206`) +- +- - I/O diff --git a/doc/source/whatsnew/v1.0.0.rst b/doc/source/whatsnew/v1.0.0.rst index b35f230100f8d..1df31da95c87d 100644 --- a/doc/source/whatsnew/v1.0.0.rst +++ b/doc/source/whatsnew/v1.0.0.rst @@ -151,7 +151,8 @@ Missing MultiIndex ^^^^^^^^^^ -- +- Bug in :meth:`DataFrame.reset_index` where dtype was sometimes not preserved for :class:`MultiIndex` that is empty or with missing values (:issue:`19602`) +- Bug in which :meth:`DataFrame.reset_index` did not work for :class:`MultiIndex` with :class:`CategoricalIndex` levels with missing values (:issue:`24206`) - I/O