Skip to content

Commit 08bd3e3

Browse files
henriqueribeirogfyoung
authored andcommitted
API: Add 'name' as argument for index 'to_frame' method (pandas-dev#22580)
1 parent b7e5704 commit 08bd3e3

File tree

5 files changed

+79
-15
lines changed

5 files changed

+79
-15
lines changed

doc/source/whatsnew/v0.24.0.txt

+1
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ Other Enhancements
184184
- :class:`DatetimeIndex` gained :attr:`DatetimeIndex.timetz` attribute. Returns local time with timezone information. (:issue:`21358`)
185185
- :class:`Resampler` now is iterable like :class:`GroupBy` (:issue:`15314`).
186186
- :meth:`Series.resample` and :meth:`DataFrame.resample` have gained the :meth:`Resampler.quantile` (:issue:`15023`).
187+
- :meth:`Index.to_frame` now supports overriding column name(s) (:issue:`22580`).
187188

188189
.. _whatsnew_0240.api_breaking:
189190

pandas/core/indexes/base.py

+16-3
Original file line numberDiff line numberDiff line change
@@ -1115,17 +1115,21 @@ def to_series(self, index=None, name=None):
11151115

11161116
return Series(self._to_embed(), index=index, name=name)
11171117

1118-
def to_frame(self, index=True):
1118+
def to_frame(self, index=True, name=None):
11191119
"""
11201120
Create a DataFrame with a column containing the Index.
11211121
1122-
.. versionadded:: 0.21.0
1122+
.. versionadded:: 0.24.0
11231123
11241124
Parameters
11251125
----------
11261126
index : boolean, default True
11271127
Set the index of the returned DataFrame as the original Index.
11281128
1129+
name : object, default None
1130+
The passed name should substitute for the index name (if it has
1131+
one).
1132+
11291133
Returns
11301134
-------
11311135
DataFrame
@@ -1153,10 +1157,19 @@ def to_frame(self, index=True):
11531157
0 Ant
11541158
1 Bear
11551159
2 Cow
1160+
1161+
To override the name of the resulting column, specify `name`:
1162+
1163+
>>> idx.to_frame(index=False, name='zoo')
1164+
zoo
1165+
0 Ant
1166+
1 Bear
1167+
2 Cow
11561168
"""
11571169

11581170
from pandas import DataFrame
1159-
name = self.name or 0
1171+
if name is None:
1172+
name = self.name or 0
11601173
result = DataFrame({name: self.values.copy()})
11611174

11621175
if index:

pandas/core/indexes/multi.py

+18-3
Original file line numberDiff line numberDiff line change
@@ -1126,20 +1126,23 @@ def _to_safe_for_reshape(self):
11261126
""" convert to object if we are a categorical """
11271127
return self.set_levels([i._to_safe_for_reshape() for i in self.levels])
11281128

1129-
def to_frame(self, index=True):
1129+
def to_frame(self, index=True, name=None):
11301130
"""
11311131
Create a DataFrame with the levels of the MultiIndex as columns.
11321132
11331133
Column ordering is determined by the DataFrame constructor with data as
11341134
a dict.
11351135
1136-
.. versionadded:: 0.20.0
1136+
.. versionadded:: 0.24.0
11371137
11381138
Parameters
11391139
----------
11401140
index : boolean, default True
11411141
Set the index of the returned DataFrame as the original MultiIndex.
11421142
1143+
name : list / sequence of strings, optional
1144+
The passed names should substitute index level names.
1145+
11431146
Returns
11441147
-------
11451148
DataFrame : a DataFrame containing the original MultiIndex data.
@@ -1150,10 +1153,22 @@ def to_frame(self, index=True):
11501153
"""
11511154

11521155
from pandas import DataFrame
1156+
if name is not None:
1157+
if not is_list_like(name):
1158+
raise TypeError("'name' must be a list / sequence "
1159+
"of column names.")
1160+
1161+
if len(name) != len(self.levels):
1162+
raise ValueError("'name' should have same length as "
1163+
"number of levels on index.")
1164+
idx_names = name
1165+
else:
1166+
idx_names = self.names
1167+
11531168
result = DataFrame({(name or level):
11541169
self._get_level_values(level)
11551170
for name, level in
1156-
zip(self.names, range(len(self.levels)))},
1171+
zip(idx_names, range(len(self.levels)))},
11571172
copy=False)
11581173
if index:
11591174
result.index = self

pandas/tests/indexes/common.py

+12-7
Original file line numberDiff line numberDiff line change
@@ -66,19 +66,24 @@ def test_to_series_with_arguments(self):
6666
assert s.index is not idx
6767
assert s.name != idx.name
6868

69-
def test_to_frame(self):
70-
# see gh-15230
69+
@pytest.mark.parametrize("name", [None, "new_name"])
70+
def test_to_frame(self, name):
71+
# see GH-15230, GH-22580
7172
idx = self.create_index()
72-
name = idx.name or 0
7373

74-
df = idx.to_frame()
74+
if name:
75+
idx_name = name
76+
else:
77+
idx_name = idx.name or 0
78+
79+
df = idx.to_frame(name=idx_name)
7580

7681
assert df.index is idx
7782
assert len(df.columns) == 1
78-
assert df.columns[0] == name
79-
assert df[name].values is not idx.values
83+
assert df.columns[0] == idx_name
84+
assert df[idx_name].values is not idx.values
8085

81-
df = idx.to_frame(index=False)
86+
df = idx.to_frame(index=False, name=idx_name)
8287
assert df.index is not idx
8388

8489
def test_shift(self):

pandas/tests/indexes/multi/test_conversion.py

+32-2
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,27 @@ def test_to_frame():
3737
expected.index = index
3838
tm.assert_frame_equal(result, expected)
3939

40+
# See GH-22580
41+
index = MultiIndex.from_tuples(tuples)
42+
result = index.to_frame(index=False, name=['first', 'second'])
43+
expected = DataFrame(tuples)
44+
expected.columns = ['first', 'second']
45+
tm.assert_frame_equal(result, expected)
46+
47+
result = index.to_frame(name=['first', 'second'])
48+
expected.index = index
49+
expected.columns = ['first', 'second']
50+
tm.assert_frame_equal(result, expected)
51+
52+
msg = "'name' must be a list / sequence of column names."
53+
with tm.assert_raises_regex(TypeError, msg):
54+
index.to_frame(name='first')
55+
56+
msg = "'name' should have same length as number of levels on index."
57+
with tm.assert_raises_regex(ValueError, msg):
58+
index.to_frame(name=['first'])
59+
60+
# Tests for datetime index
4061
index = MultiIndex.from_product([range(5),
4162
pd.date_range('20130101', periods=3)])
4263
result = index.to_frame(index=False)
@@ -45,12 +66,21 @@ def test_to_frame():
4566
1: np.tile(pd.date_range('20130101', periods=3), 5)})
4667
tm.assert_frame_equal(result, expected)
4768

48-
index = MultiIndex.from_product([range(5),
49-
pd.date_range('20130101', periods=3)])
5069
result = index.to_frame()
5170
expected.index = index
5271
tm.assert_frame_equal(result, expected)
5372

73+
# See GH-22580
74+
result = index.to_frame(index=False, name=['first', 'second'])
75+
expected = DataFrame(
76+
{'first': np.repeat(np.arange(5, dtype='int64'), 3),
77+
'second': np.tile(pd.date_range('20130101', periods=3), 5)})
78+
tm.assert_frame_equal(result, expected)
79+
80+
result = index.to_frame(name=['first', 'second'])
81+
expected.index = index
82+
tm.assert_frame_equal(result, expected)
83+
5484

5585
def test_to_hierarchical():
5686
index = MultiIndex.from_tuples([(1, 'one'), (1, 'two'), (2, 'one'), (

0 commit comments

Comments
 (0)