Skip to content

Commit

Permalink
fixup! API: Have MultiIndex constructors return MI
Browse files Browse the repository at this point in the history
  • Loading branch information
TomAugspurger committed Aug 25, 2017
1 parent 7ccce35 commit b1c47aa
Show file tree
Hide file tree
Showing 8 changed files with 59 additions and 27 deletions.
8 changes: 4 additions & 4 deletions pandas/core/frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
standardize_mapping)
from pandas.core.generic import NDFrame, _shared_docs
from pandas.core.index import (Index, MultiIndex, _ensure_index,
_index_from_sequences)
_ensure_index_from_sequences)
from pandas.core.indexing import (maybe_droplevels, convert_to_index_sliceable,
check_bool_indexer)
from pandas.core.internals import (BlockManager,
Expand Down Expand Up @@ -1157,8 +1157,8 @@ def from_records(cls, data, index=None, exclude=None, columns=None,
try:
to_remove = [arr_columns.get_loc(field) for field in index]
index_data = [arrays[i] for i in to_remove]
result_index = _index_from_sequences(index_data,
names=index)
result_index = _ensure_index_from_sequences(index_data,
names=index)

exclude.update(index)
except Exception:
Expand Down Expand Up @@ -3001,7 +3001,7 @@ def set_index(self, keys, drop=True, append=False, inplace=False,
to_remove.append(col)
arrays.append(level)

index = _index_from_sequences(arrays, names)
index = _ensure_index_from_sequences(arrays, names)

if verify_integrity and not index.is_unique:
duplicates = index.get_duplicates()
Expand Down
4 changes: 2 additions & 2 deletions pandas/core/indexes/api.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from pandas.core.indexes.base import (Index,
_new_Index,
_ensure_index,
_index_from_sequences,
_ensure_index_from_sequences,
_get_na_value,
InvalidIndexError) # noqa
from pandas.core.indexes.category import CategoricalIndex # noqa
Expand All @@ -25,7 +25,7 @@
'InvalidIndexError', 'TimedeltaIndex',
'PeriodIndex', 'DatetimeIndex',
'_new_Index', 'NaT',
'_ensure_index', '_index_from_sequences', '_get_na_value',
'_ensure_index', '_ensure_index_from_sequences', '_get_na_value',
'_get_combined_index',
'_get_objs_combined_axis',
'_get_distinct_indexes', '_union_indexes',
Expand Down
46 changes: 40 additions & 6 deletions pandas/core/indexes/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -4007,19 +4007,28 @@ def invalid_op(self, other=None):
Index._add_comparison_methods()


def _index_from_sequences(sequences, names=None):
def _ensure_index_from_sequences(sequences, names=None):
"""Construct an index from sequences of data.
A single sequence returns an Index.
Many sequences returns a MultiIndex.
A single sequence returns an Index. Many sequences returns a
MultiIndex.
Parameters
----------
sequences : sequence of sequences
names : sequence of str
Returns
-------
index : Index or MultiIndex
Examples
--------
>>> _index_from_sequences([[1, 2, 3]], names=['name'])
>>> _ensure_index_from_sequences([[1, 2, 3]], names=['name'])
Int64Index([1, 2, 3], dtype='int64', name='name')
>>> _index_from_sequences([['a', 'a'], ['a', 'b']], names=['L1', 'L2'])
>>> _ensure_index_from_sequences([['a', 'a'], ['a', 'b']],
names=['L1', 'L2'])
MultiIndex(levels=[['a'], ['a', 'b']],
labels=[[0, 0], [0, 1]],
names=['L1', 'L2'])
Expand All @@ -4035,6 +4044,31 @@ def _index_from_sequences(sequences, names=None):


def _ensure_index(index_like, copy=False):
"""
Ensure that we have an index from some index-like object
Parameters
----------
index : sequence
An Index or other sequence
copy : bool
Returns
-------
index : Index or MultiIndex
Examples
--------
>>> _ensure_index(['a', 'b'])
Index(['a', 'b'], dtype='object')
>>> _ensure_index([('a', 'a'), ('b', 'c')])
Index([('a', 'a'), ('b', 'c')], dtype='object')
>>> _ensure_index([['a', 'a'], ['b', 'c']])
MultiIndex(levels=[['a'], ['b', 'c']],
labels=[[0, 0], [0, 1]])
"""
if isinstance(index_like, Index):
if copy:
index_like = index_like.copy()
Expand Down
3 changes: 3 additions & 0 deletions pandas/core/reshape/reshape.py
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,7 @@ def _unstack_multiple(data, clocs):
xnull=False)

if rlocs == []:
# Everything is in clocs, so the dummy df has a regular index
dummy_index = Index(obs_ids, name='__placeholder__')
else:
dummy_index = MultiIndex(levels=rlevels + [obs_ids],
Expand Down Expand Up @@ -450,6 +451,8 @@ def _slow_pivot(index, columns, values):
def unstack(obj, level, fill_value=None):
if isinstance(level, (tuple, list)):
if len(level) == 1:
# unstack_multiple only handles MultiIndexes,
# and isn't needed for a single level
level = level[0]
else:
return _unstack_multiple(obj, level)
Expand Down
2 changes: 2 additions & 0 deletions pandas/core/strings.py
Original file line number Diff line number Diff line change
Expand Up @@ -1454,6 +1454,8 @@ def cons_row(x):
result = list(result)
out = MultiIndex.from_tuples(result, names=name)
if out.nlevels == 1:
# We had all tuples of length-one, which are
# better represented as a regular Index.
out = out.get_level_values(0)
return out
else:
Expand Down
16 changes: 4 additions & 12 deletions pandas/io/parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from pandas.core.dtypes.missing import isna
from pandas.core.dtypes.cast import astype_nansafe
from pandas.core.index import (Index, MultiIndex, RangeIndex,
_index_from_sequences)
_ensure_index_from_sequences)
from pandas.core.series import Series
from pandas.core.frame import DataFrame
from pandas.core.categorical import Categorical
Expand Down Expand Up @@ -1446,15 +1446,7 @@ def _agg_index(self, index, try_parse_dates=True):
arrays.append(arr)

names = self.index_names
index = _index_from_sequences(arrays, names)
if len(arrays) > 1:
index = MultiIndex.from_arrays(arrays, names=self.index_names)
else:
if self.index_names is None:
name = None
else:
name = self.index_names[0]
index = Index(arrays[0], name=name)
index = _ensure_index_from_sequences(arrays, names)

return index

Expand Down Expand Up @@ -1818,7 +1810,7 @@ def read(self, nrows=None):
try_parse_dates=True)
arrays.append(values)

index = _index_from_sequences(arrays)
index = _ensure_index_from_sequences(arrays)

if self.usecols is not None:
names = self._filter_usecols(names)
Expand Down Expand Up @@ -3149,7 +3141,7 @@ def _get_empty_meta(columns, index_col, index_names, dtype=None):
index = Index([])
else:
data = [Series([], dtype=dtype[name]) for name in index_names]
index = _index_from_sequences(data, names=index_names)
index = _ensure_index_from_sequences(data, names=index_names)
index_col.sort()
for i, n in enumerate(index_col):
columns.pop(n - i)
Expand Down
6 changes: 3 additions & 3 deletions pandas/tests/indexes/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
DataFrame, Float64Index, Int64Index,
CategoricalIndex, DatetimeIndex, TimedeltaIndex,
PeriodIndex, isna)
from pandas.core.index import _get_combined_index, _index_from_sequences
from pandas.core.index import _get_combined_index, _ensure_index_from_sequences
from pandas.util.testing import assert_almost_equal
from pandas.compat.numpy import np_datetime64_compat

Expand Down Expand Up @@ -2116,6 +2116,6 @@ class TestIndexUtils(object):
MultiIndex([['a'], ['c', 'd']], [[0, 0], [0, 1]],
names=['L1', 'L2'])),
])
def test_index_from_sequences(self, data, names, expected):
result = _index_from_sequences(data, names)
def test_ensure_index_from_sequences(self, data, names, expected):
result = _ensure_index_from_sequences(data, names)
tm.assert_index_equal(result, expected)
1 change: 1 addition & 0 deletions pandas/util/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -1909,6 +1909,7 @@ def keyfunc(x):

# convert tuples to index
if nentries == 1:
# we have a single level of tuples, i.e. a regular Index
index = Index(tuples[0], name=names[0])
elif nlevels == 1:
name = None if names is None else names[0]
Expand Down

0 comments on commit b1c47aa

Please sign in to comment.