Skip to content

BUG: Index/Series.to_frame not respecting explicit name=None #44212

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Oct 30, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/source/whatsnew/v1.4.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -642,6 +642,7 @@ Other
- Bug in :meth:`CustomBusinessMonthBegin.__add__` (:meth:`CustomBusinessMonthEnd.__add__`) not applying the extra ``offset`` parameter when beginning (end) of the target month is already a business day (:issue:`41356`)
- Bug in :meth:`RangeIndex.union` with another ``RangeIndex`` with matching (even) ``step`` and starts differing by strictly less than ``step / 2`` (:issue:`44019`)
- Bug in :meth:`RangeIndex.difference` with ``sort=None`` and ``step<0`` failing to sort (:issue:`44085`)
- Bug in :meth:`Series.to_frame` and :meth:`Index.to_frame` ignoring the ``name`` argument when ``name=None`` is explicitly passed (:issue:`44212`)

.. ***DO NOT USE THIS SECTION***

Expand Down
6 changes: 4 additions & 2 deletions pandas/core/indexes/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1510,7 +1510,9 @@ def to_series(self, index=None, name: Hashable = None) -> Series:

return Series(self._values.copy(), index=index, name=name)

def to_frame(self, index: bool = True, name: Hashable = None) -> DataFrame:
def to_frame(
self, index: bool = True, name: Hashable = lib.no_default
) -> DataFrame:
"""
Create a DataFrame with a column containing the Index.

Expand Down Expand Up @@ -1561,7 +1563,7 @@ def to_frame(self, index: bool = True, name: Hashable = None) -> DataFrame:
"""
from pandas import DataFrame

if name is None:
if name is lib.no_default:
name = self.name or 0
result = DataFrame({name: self._values.copy()})

Expand Down
4 changes: 2 additions & 2 deletions pandas/core/indexes/multi.py
Original file line number Diff line number Diff line change
Expand Up @@ -1684,7 +1684,7 @@ def unique(self, level=None):
level = self._get_level_number(level)
return self._get_level_values(level=level, unique=True)

def to_frame(self, index: bool = True, name=None) -> DataFrame:
def to_frame(self, index: bool = True, name=lib.no_default) -> DataFrame:
"""
Create a DataFrame with the levels of the MultiIndex as columns.

Expand Down Expand Up @@ -1736,7 +1736,7 @@ def to_frame(self, index: bool = True, name=None) -> DataFrame:
"""
from pandas import DataFrame

if name is not None:
if name is not lib.no_default:
if not is_list_like(name):
raise TypeError("'name' must be a list / sequence of column names.")

Expand Down
17 changes: 14 additions & 3 deletions pandas/core/series.py
Original file line number Diff line number Diff line change
Expand Up @@ -1317,7 +1317,7 @@ def repeat(self, repeats, axis=None) -> Series:
)

@deprecate_nonkeyword_arguments(version=None, allowed_args=["self", "level"])
def reset_index(self, level=None, drop=False, name=None, inplace=False):
def reset_index(self, level=None, drop=False, name=lib.no_default, inplace=False):
"""
Generate a new DataFrame or Series with the index reset.

Expand Down Expand Up @@ -1427,6 +1427,9 @@ def reset_index(self, level=None, drop=False, name=None, inplace=False):
"""
inplace = validate_bool_kwarg(inplace, "inplace")
if drop:
if name is lib.no_default:
name = self.name

new_index = default_index(len(self))
if level is not None:
if not isinstance(level, (tuple, list)):
Expand All @@ -1448,6 +1451,14 @@ def reset_index(self, level=None, drop=False, name=None, inplace=False):
"Cannot reset_index inplace on a Series to create a DataFrame"
)
else:
if name is lib.no_default:
# For backwards compatibility, keep columns as [0] instead of
# [None] when self.name is None
if self.name is None:
name = 0
else:
name = self.name

df = self.to_frame(name)
return df.reset_index(level=level, drop=drop)

Expand Down Expand Up @@ -1697,7 +1708,7 @@ def to_dict(self, into=dict):
into_c = com.standardize_mapping(into)
return into_c((k, maybe_box_native(v)) for k, v in self.items())

def to_frame(self, name=None) -> DataFrame:
def to_frame(self, name: Hashable = lib.no_default) -> DataFrame:
"""
Convert Series to DataFrame.

Expand All @@ -1723,7 +1734,7 @@ def to_frame(self, name=None) -> DataFrame:
2 c
"""
columns: Index
if name is None:
if name is lib.no_default:
name = self.name
if name is None:
# default to [0], same as we would get with DataFrame(self)
Expand Down
6 changes: 5 additions & 1 deletion pandas/plotting/_matplotlib/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,11 @@ def _compute_plot_data(self):
label = self.label
if label is None and data.name is None:
label = "None"
data = data.to_frame(name=label)
if label is None:
# We'll end up with columns of [0] instead of [None]
data = data.to_frame()
else:
data = data.to_frame(name=label)
elif self._kind in ("hist", "box"):
cols = self.columns if self.by is None else self.columns + self.by
data = data.loc[:, cols]
Expand Down
12 changes: 12 additions & 0 deletions pandas/tests/indexes/datetimes/methods/test_to_frame.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from pandas import (
DataFrame,
Index,
date_range,
)
import pandas._testing as tm
Expand All @@ -12,3 +13,14 @@ def test_to_frame_datetime_tz(self):
result = idx.to_frame()
expected = DataFrame(idx, index=idx)
tm.assert_frame_equal(result, expected)

def test_to_frame_respects_none_name(self):
# GH#44212 if we explicitly pass name=None, then that should be respected,
# not changed to 0
idx = date_range(start="2019-01-01", end="2019-01-30", freq="D", tz="UTC")
result = idx.to_frame(name=None)
exp_idx = Index([None], dtype=object)
tm.assert_index_equal(exp_idx, result.columns)

result = idx.rename("foo").to_frame(name=None)
tm.assert_index_equal(exp_idx, result.columns)
13 changes: 13 additions & 0 deletions pandas/tests/series/methods/test_to_frame.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,24 @@
from pandas import (
DataFrame,
Index,
Series,
)
import pandas._testing as tm


class TestToFrame:
def test_to_frame_respects_name_none(self):
# GH#44212 if we explicitly pass name=None, then that should be respected,
# not changed to 0
ser = Series(range(3))
result = ser.to_frame(None)

exp_index = Index([None], dtype=object)
tm.assert_index_equal(result.columns, exp_index)

result = ser.rename("foo").to_frame(None)
tm.assert_index_equal(result.columns, exp_index)

def test_to_frame(self, datetime_series):
datetime_series.name = None
rs = datetime_series.to_frame()
Expand Down