Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 9facf36

Browse filesBrowse files
committed
ENH/API: DataFrame.stack() support for level=ALL_LEVELS and sequentially=True/False.
1 parent 484f668 commit 9facf36
Copy full SHA for 9facf36

File tree

4 files changed

+374
-115
lines changed
Filter options

4 files changed

+374
-115
lines changed

‎pandas/core/frame.py

Copy file name to clipboardExpand all lines: pandas/core/frame.py
+11-6
Original file line numberDiff line numberDiff line change
@@ -3392,7 +3392,7 @@ def pivot(self, index=None, columns=None, values=None):
33923392
from pandas.core.reshape import pivot
33933393
return pivot(self, index=index, columns=columns, values=values)
33943394

3395-
def stack(self, level=-1, dropna=True):
3395+
def stack(self, level=-1, dropna=True, sequentially=True):
33963396
"""
33973397
Pivot a level of the (possibly hierarchical) column labels, returning a
33983398
DataFrame (or Series in the case of an object with a single level of
@@ -3403,10 +3403,14 @@ def stack(self, level=-1, dropna=True):
34033403
Parameters
34043404
----------
34053405
level : int, string, or list of these, default last level
3406-
Level(s) to stack, can pass level name
3406+
Level(s) to stack, can pass level name(s).
3407+
May pass Index.ALL_LEVELS to specify list(range(columns.nlevels)).
34073408
dropna : boolean, default True
34083409
Whether to drop rows in the resulting Frame/Series with no valid
34093410
values
3411+
sequentially : boolean, default True
3412+
When level is a list (or ALL_LEVELS), whether the multiple column levels
3413+
should be stacked sequentially (if True) or simultaneously (if False).
34103414
34113415
Examples
34123416
----------
@@ -3425,12 +3429,13 @@ def stack(self, level=-1, dropna=True):
34253429
-------
34263430
stacked : DataFrame or Series
34273431
"""
3428-
from pandas.core.reshape import stack, stack_multiple
3432+
from pandas.core.reshape import stack_levels_sequentially, stack_multi_levels_simultaneously
34293433

3430-
if isinstance(level, (tuple, list)):
3431-
return stack_multiple(self, level, dropna=dropna)
3434+
level_num = self.columns._get_level_numbers(level, allow_mixed_names_and_numbers=False)
3435+
if isinstance(level_num, (tuple, list, set)) and isinstance(self.columns, MultiIndex) and (not sequentially):
3436+
return stack_multi_levels_simultaneously(self, level_num, dropna=dropna)
34323437
else:
3433-
return stack(self, level, dropna=dropna)
3438+
return stack_levels_sequentially(self, level_num, dropna=dropna)
34343439

34353440
def unstack(self, level=-1):
34363441
"""

‎pandas/core/index.py

Copy file name to clipboardExpand all lines: pandas/core/index.py
+43-23
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,8 @@ class Index(IndexOpsMixin, PandasObject):
123123

124124
_engine_type = _index.ObjectEngine
125125

126+
ALL_LEVELS = -1000
127+
126128
def __new__(cls, data=None, dtype=None, copy=False, name=None, fastpath=False,
127129
tupleize_cols=True, **kwargs):
128130

@@ -824,10 +826,26 @@ def _validate_index_level(self, level):
824826
raise KeyError('Level %s must be same as name (%s)'
825827
% (level, self.name))
826828

827-
def _get_level_number(self, level):
829+
def _get_level_number(self, level, ignore_names=False):
830+
if ignore_names and (not isinstance(level, int)):
831+
raise KeyError('Level %s not found' % str(level))
828832
self._validate_index_level(level)
829833
return 0
830834

835+
def _get_level_numbers(self, levels, allow_mixed_names_and_numbers=False):
836+
if levels == self.ALL_LEVELS:
837+
return list(range(self.nlevels))
838+
elif isinstance(levels, (list, tuple, set)):
839+
if (not allow_mixed_names_and_numbers) and (not all(lev in self.names for lev in levels)):
840+
if all(isinstance(lev, int) for lev in levels):
841+
return type(levels)(self._get_level_number(level, ignore_names=True) for level in levels)
842+
else:
843+
raise ValueError("level should contain all level names or all level numbers, "
844+
"not a mixture of the two.")
845+
return type(levels)(self._get_level_number(level) for level in levels)
846+
else:
847+
return self._get_level_number(levels)
848+
831849
@cache_readonly
832850
def inferred_type(self):
833851
""" return a string of the type inferred from the values """
@@ -3161,28 +3179,30 @@ def _from_elements(values, labels=None, levels=None, names=None,
31613179
sortorder=None):
31623180
return MultiIndex(levels, labels, names, sortorder=sortorder)
31633181

3164-
def _get_level_number(self, level):
3165-
try:
3182+
def _get_level_number(self, level, ignore_names=False):
3183+
if not ignore_names:
31663184
count = self.names.count(level)
31673185
if count > 1:
31683186
raise ValueError('The name %s occurs multiple times, use a '
31693187
'level number' % level)
3170-
level = self.names.index(level)
3171-
except ValueError:
3172-
if not isinstance(level, int):
3173-
raise KeyError('Level %s not found' % str(level))
3174-
elif level < 0:
3175-
level += self.nlevels
3176-
if level < 0:
3177-
orig_level = level - self.nlevels
3178-
raise IndexError(
3179-
'Too many levels: Index has only %d levels, '
3180-
'%d is not a valid level number' % (self.nlevels, orig_level)
3181-
)
3182-
# Note: levels are zero-based
3183-
elif level >= self.nlevels:
3184-
raise IndexError('Too many levels: Index has only %d levels, '
3185-
'not %d' % (self.nlevels, level + 1))
3188+
try:
3189+
return self.names.index(level)
3190+
except ValueError:
3191+
pass
3192+
if not isinstance(level, int):
3193+
raise KeyError('Level %s not found' % str(level))
3194+
elif level < 0:
3195+
level += self.nlevels
3196+
if level < 0:
3197+
orig_level = level - self.nlevels
3198+
raise IndexError(
3199+
'Too many levels: Index has only %d levels, '
3200+
'%d is not a valid level number' % (self.nlevels, orig_level)
3201+
)
3202+
# Note: levels are zero-based
3203+
elif level >= self.nlevels:
3204+
raise IndexError('Too many levels: Index has only %d levels, '
3205+
'not %d' % (self.nlevels, level + 1))
31863206
return level
31873207

31883208
_tuples = None
@@ -4852,7 +4872,7 @@ def _trim_front(strings):
48524872

48534873

48544874
def _sanitize_and_check(indexes):
4855-
kinds = list(set([type(index) for index in indexes]))
4875+
kinds = list(set(type(index) for index in indexes))
48564876

48574877
if list in kinds:
48584878
if len(kinds) > 1:
@@ -4873,11 +4893,11 @@ def _get_consensus_names(indexes):
48734893

48744894
# find the non-none names, need to tupleify to make
48754895
# the set hashable, then reverse on return
4876-
consensus_names = set([
4896+
consensus_names = set(
48774897
tuple(i.names) for i in indexes if all(n is not None for n in i.names)
4878-
])
4898+
)
48794899
if len(consensus_names) == 1:
4880-
return list(list(consensus_names)[0])
4900+
return list(consensus_names.pop())
48814901
return [None] * indexes[0].nlevels
48824902

48834903

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.