Skip to content
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

Add to_flat_index method to MultiIndex #22866

Merged
merged 16 commits into from
Nov 13, 2018
Merged
Show file tree
Hide file tree
Changes from 15 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/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1724,6 +1724,7 @@ MultiIndex Components
MultiIndex.set_levels
MultiIndex.set_labels
MultiIndex.to_hierarchical
MultiIndex.to_flat_index
MultiIndex.to_frame
MultiIndex.is_lexsorted
MultiIndex.sortlevel
Expand Down
1 change: 1 addition & 0 deletions doc/source/whatsnew/v0.24.0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ Other Enhancements
- :func:`~DataFrame.to_parquet` now supports writing a ``DataFrame`` as a directory of parquet files partitioned by a subset of the columns when ``engine = 'pyarrow'`` (:issue:`23283`)
- :meth:`Timestamp.tz_localize`, :meth:`DatetimeIndex.tz_localize`, and :meth:`Series.tz_localize` have gained the ``nonexistent`` argument for alternative handling of nonexistent times. See :ref:`timeseries.timezone_nonexistent` (:issue:`8917`)
- :meth:`read_excel()` now accepts ``usecols`` as a list of column names or callable (:issue:`18273`)
- :meth:`MultiIndex.to_flat_index` has been added to flatten multiple levels into a single-level :class:`Index` object.

.. _whatsnew_0240.api_breaking:

Expand Down
33 changes: 33 additions & 0 deletions pandas/core/indexes/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1113,6 +1113,39 @@ def _format_attrs(self):
"""
return format_object_attrs(self)

def to_flat_index(self):
"""
Convert a MultiIndex to an Index of Tuples containing the level values.

.. versionadded:: 0.24.0

Returns
-------
pd.Index
Index with the MultiIndex data represented in Tuples.

Notes
-----
This method will simply return the caller if called by anything other
than a MultiIndex.

Examples
--------
>>> index = pd.MultiIndex.from_product(
... [['foo', 'bar'], ['baz', 'qux']],
... names=['a', 'b'])
>>> index.to_flat_index()
Index([('foo', 'baz'), ('foo', 'qux'),
('bar', 'baz'), ('bar', 'qux')],
dtype='object')
"""
if not isinstance(self, ABCMultiIndex):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idle style question (no need to change here): what are people's preferences on

  1. Defining a method for both Index and MultiIndex here, but using an isnstance to figure out which one we're working with
  2. Defining a default implementation here that's return self, and overriding in MultiIndex, so no if isintance?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Preference for 2, and in general, for leaving the base Index class unaware (every time it is possible/reasonable) of the existence and specificities of Index subclasses.

return self

idx = Index(self.values, tupleize_cols=False)
TomAugspurger marked this conversation as resolved.
Show resolved Hide resolved

return idx

def to_series(self, index=None, name=None):
"""
Create a Series with both index and values equal to the index keys
Expand Down
1 change: 1 addition & 0 deletions pandas/core/indexes/multi.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ class MultiIndex(Index):
set_levels
set_labels
to_frame
to_flat_index
is_lexsorted
sortlevel
droplevel
Expand Down
8 changes: 8 additions & 0 deletions pandas/tests/indexes/multi/test_conversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,11 @@ def test_to_series_with_arguments(idx):
assert s.values is not idx.values
assert s.index is not idx
assert s.name != idx.name


def test_to_flat_index(idx):
expected = pd.Index((('foo', 'one'), ('foo', 'two'), ('bar', 'one'),
('baz', 'two'), ('qux', 'one'), ('qux', 'two')),
tupleize_cols=False)
result = idx.to_flat_index()
tm.assert_index_equal(result, expected)
8 changes: 8 additions & 0 deletions pandas/tests/indexes/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2266,6 +2266,14 @@ def test_tab_complete_warning(self, ip):
with provisionalcompleter('ignore'):
list(ip.Completer.completions('idx.', 4))

def test_to_flat_index(self, indices):
# 22866
if isinstance(indices, MultiIndex):
pytest.skip("Separate expectation for MultiIndex")

result = indices.to_flat_index()
tm.assert_index_equal(result, indices)


class TestMixedIntIndex(Base):
# Mostly the tests from common.py for which the results differ
Expand Down