diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst index ef561d50066d1..731195f0b1268 100644 --- a/doc/source/whatsnew/v3.0.0.rst +++ b/doc/source/whatsnew/v3.0.0.rst @@ -264,6 +264,7 @@ Removal of prior version deprecations/changes Performance improvements ~~~~~~~~~~~~~~~~~~~~~~~~ - :attr:`Categorical.categories` returns a :class:`RangeIndex` columns instead of an :class:`Index` if the constructed ``values`` was a ``range``. (:issue:`57787`) +- :class:`DataFrame` returns a :class:`RangeIndex` columns when possible when ``data`` is a ``dict`` (:issue:`57943`) - :func:`concat` returns a :class:`RangeIndex` level in the :class:`MultiIndex` result when ``keys`` is a ``range`` or :class:`RangeIndex` (:issue:`57542`) - :meth:`RangeIndex.append` returns a :class:`RangeIndex` instead of a :class:`Index` when appending values that could continue the :class:`RangeIndex` (:issue:`57467`) - :meth:`Series.str.extract` returns a :class:`RangeIndex` columns instead of an :class:`Index` column when possible (:issue:`57542`) diff --git a/pandas/core/indexes/api.py b/pandas/core/indexes/api.py index a8887a21afa34..9b05eb42c6d6e 100644 --- a/pandas/core/indexes/api.py +++ b/pandas/core/indexes/api.py @@ -1,6 +1,5 @@ from __future__ import annotations -import textwrap from typing import ( TYPE_CHECKING, cast, @@ -23,6 +22,7 @@ ensure_index, ensure_index_from_sequences, get_unanimous_names, + maybe_sequence_to_range, ) from pandas.core.indexes.category import CategoricalIndex from pandas.core.indexes.datetimes import DatetimeIndex @@ -34,16 +34,6 @@ if TYPE_CHECKING: from pandas._typing import Axis -_sort_msg = textwrap.dedent( - """\ -Sorting because non-concatenation axis is not aligned. A future version -of pandas will change to not sort by default. - -To accept the future behavior, pass 'sort=False'. - -To retain the current behavior and silence the warning, pass 'sort=True'. -""" -) __all__ = [ @@ -66,6 +56,7 @@ "all_indexes_same", "default_index", "safe_sort_index", + "maybe_sequence_to_range", ] diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 9a537c71f3cd0..e59c0542ee6da 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -7169,18 +7169,17 @@ def maybe_sequence_to_range(sequence) -> Any | range: ------- Any : input or range """ - if isinstance(sequence, (ABCSeries, Index, range)): + if isinstance(sequence, (ABCSeries, Index, range, ExtensionArray)): return sequence - np_sequence = np.asarray(sequence) - if np_sequence.dtype.kind != "i" or len(np_sequence) == 1: + elif len(sequence) == 1 or lib.infer_dtype(sequence, skipna=False) != "integer": return sequence - elif len(np_sequence) == 0: + elif len(sequence) == 0: return range(0) - diff = np_sequence[1] - np_sequence[0] + diff = sequence[1] - sequence[0] if diff == 0: return sequence - elif len(np_sequence) == 2 or lib.is_sequence_range(np_sequence, diff): - return range(np_sequence[0], np_sequence[-1] + diff, diff) + elif len(sequence) == 2 or lib.is_sequence_range(np.asarray(sequence), diff): + return range(sequence[0], sequence[-1] + diff, diff) else: return sequence diff --git a/pandas/core/internals/construction.py b/pandas/core/internals/construction.py index 93f1674fbd328..73b93110c9018 100644 --- a/pandas/core/internals/construction.py +++ b/pandas/core/internals/construction.py @@ -60,6 +60,7 @@ default_index, ensure_index, get_objs_combined_axis, + maybe_sequence_to_range, union_indexes, ) from pandas.core.internals.blocks import ( @@ -403,7 +404,7 @@ def dict_to_mgr( arrays[i] = arr else: - keys = list(data.keys()) + keys = maybe_sequence_to_range(list(data.keys())) columns = Index(keys) if keys else default_index(0) arrays = [com.maybe_iterable_to_list(data[k]) for k in keys] diff --git a/pandas/tests/frame/test_constructors.py b/pandas/tests/frame/test_constructors.py index 7d1a5b4492740..12d8269b640fc 100644 --- a/pandas/tests/frame/test_constructors.py +++ b/pandas/tests/frame/test_constructors.py @@ -2709,6 +2709,11 @@ def test_inference_on_pandas_objects(self): result = DataFrame({"a": ser}) assert result.dtypes.iloc[0] == np.object_ + def test_dict_keys_returns_rangeindex(self): + result = DataFrame({0: [1], 1: [2]}).columns + expected = RangeIndex(2) + tm.assert_index_equal(result, expected, exact=True) + class TestDataFrameConstructorIndexInference: def test_frame_from_dict_of_series_overlapping_monthly_period_indexes(self): diff --git a/pandas/tests/reshape/test_pivot.py b/pandas/tests/reshape/test_pivot.py index 99250dc929997..f750d5e7fa919 100644 --- a/pandas/tests/reshape/test_pivot.py +++ b/pandas/tests/reshape/test_pivot.py @@ -1738,6 +1738,7 @@ def test_daily(self): mask = ts.index.year == y expected[y] = Series(ts.values[mask], index=doy[mask]) expected = DataFrame(expected, dtype=float).T + expected.index = expected.index.astype(np.int32) tm.assert_frame_equal(result, expected) def test_monthly(self): @@ -1753,6 +1754,7 @@ def test_monthly(self): mask = ts.index.year == y expected[y] = Series(ts.values[mask], index=month[mask]) expected = DataFrame(expected, dtype=float).T + expected.index = expected.index.astype(np.int32) tm.assert_frame_equal(result, expected) def test_pivot_table_with_iterator_values(self, data):