@@ -1033,7 +1033,7 @@ def _validate_index_level(self, level):
10331033 verification must be done like in MultiIndex.
10341034
10351035 """
1036- if isinstance (level , int ):
1036+ if com . is_integer (level ):
10371037 if level < 0 and level != - 1 :
10381038 raise IndexError ("Too many levels: Index has only 1 level,"
10391039 " %d is not a valid level number" % (level ,))
@@ -1045,10 +1045,44 @@ def _validate_index_level(self, level):
10451045 raise KeyError ('Level %s must be same as name (%s)'
10461046 % (level , self .name ))
10471047
1048- def _get_level_number (self , level ):
1048+ def _get_level_number (self , level , ignore_names = False ):
1049+ """
1050+ Returns level number corresponding to level.
1051+ If level is a level name and ignore_names is False,
1052+ the level number corresponding to such level name is returned.
1053+ Otherwise level must be a number.
1054+ If level is a positive number, it is returned.
1055+ If level is a negative number, its sum with self.nlevels is returned.
1056+ """
1057+ if ignore_names and (not com .is_integer (level )):
1058+ raise KeyError ('Level %s not found' % str (level ))
10491059 self ._validate_index_level (level )
10501060 return 0
10511061
1062+ def _get_level_numbers (self , levels , allow_mixed_names_and_numbers = False ):
1063+ """
1064+ Returns level numbers corresponding to levels.
1065+ If levels is None, a list of all level numbers is returned.
1066+ If levels is a single number or level name,
1067+ then a single number is returned (using _get_level_number()).
1068+ If levels is a list of numbers or level names,
1069+ then a list of numbers is returned (each using _get_level_number()).
1070+ If allow_mixed_names_and_numbers is False, then levels must be
1071+ either all level numbers or all level names.
1072+ """
1073+ if levels is None :
1074+ return list (range (self .nlevels ))
1075+ elif isinstance (levels , (list , tuple , set )):
1076+ if (not allow_mixed_names_and_numbers ) and (not all (lev in self .names for lev in levels )):
1077+ if all (isinstance (lev , int ) for lev in levels ):
1078+ return type (levels )(self ._get_level_number (level , ignore_names = True ) for level in levels )
1079+ else :
1080+ raise ValueError ("level should contain all level names or all level numbers, "
1081+ "not a mixture of the two." )
1082+ return type (levels )(self ._get_level_number (level ) for level in levels )
1083+ else :
1084+ return self ._get_level_number (levels )
1085+
10521086 @cache_readonly
10531087 def inferred_type (self ):
10541088 """ return a string of the type inferred from the values """
@@ -4294,28 +4328,38 @@ def _from_elements(values, labels=None, levels=None, names=None,
42944328 sortorder = None ):
42954329 return MultiIndex (levels , labels , names , sortorder = sortorder )
42964330
4297- def _get_level_number (self , level ):
4298- try :
4331+ def _get_level_number (self , level , ignore_names = False ):
4332+ """
4333+ Returns level number corresponding to level.
4334+ If level is a level name and ignore_names is False,
4335+ the level number corresponding to such level name is returned.
4336+ Otherwise level must be a number.
4337+ If level is a positive number, it is returned.
4338+ If level is a negative number, its sum with self.nlevels is returned.
4339+ """
4340+ if not ignore_names :
42994341 count = self .names .count (level )
43004342 if count > 1 :
43014343 raise ValueError ('The name %s occurs multiple times, use a '
43024344 'level number' % level )
4303- level = self .names .index (level )
4304- except ValueError :
4305- if not isinstance (level , int ):
4306- raise KeyError ('Level %s not found' % str (level ))
4307- elif level < 0 :
4308- level += self .nlevels
4309- if level < 0 :
4310- orig_level = level - self .nlevels
4311- raise IndexError (
4312- 'Too many levels: Index has only %d levels, '
4313- '%d is not a valid level number' % (self .nlevels , orig_level )
4314- )
4315- # Note: levels are zero-based
4316- elif level >= self .nlevels :
4317- raise IndexError ('Too many levels: Index has only %d levels, '
4318- 'not %d' % (self .nlevels , level + 1 ))
4345+ try :
4346+ return self .names .index (level )
4347+ except ValueError :
4348+ pass
4349+ if not com .is_integer (level ):
4350+ raise KeyError ('Level %s not found' % str (level ))
4351+ elif level < 0 :
4352+ level += self .nlevels
4353+ if level < 0 :
4354+ orig_level = level - self .nlevels
4355+ raise IndexError (
4356+ 'Too many levels: Index has only %d levels, '
4357+ '%d is not a valid level number' % (self .nlevels , orig_level )
4358+ )
4359+ # Note: levels are zero-based
4360+ elif level >= self .nlevels :
4361+ raise IndexError ('Too many levels: Index has only %d levels, '
4362+ 'not %d' % (self .nlevels , level + 1 ))
43194363 return level
43204364
43214365 _tuples = None
@@ -4891,14 +4935,16 @@ def _drop_from_level(self, labels, level):
48914935
48924936 return self [mask ]
48934937
4894- def droplevel (self , level = 0 ):
4938+ def droplevel (self , level = 0 , ignore_names = False ):
48954939 """
48964940 Return Index with requested level removed. If MultiIndex has only 2
48974941 levels, the result will be of Index type not MultiIndex.
48984942
48994943 Parameters
49004944 ----------
49014945 level : int/level name or list thereof
4946+ ignore_names : boolean, default True
4947+ If True, level must be an int or list thereof
49024948
49034949 Notes
49044950 -----
@@ -4916,7 +4962,7 @@ def droplevel(self, level=0):
49164962 new_labels = list (self .labels )
49174963 new_names = list (self .names )
49184964
4919- levnums = sorted (self ._get_level_number (lev ) for lev in levels )[:: - 1 ]
4965+ levnums = sorted (( self ._get_level_number (lev , ignore_names ) for lev in levels ), reverse = True )
49204966
49214967 for i in levnums :
49224968 new_levels .pop (i )
@@ -4929,6 +4975,9 @@ def droplevel(self, level=0):
49294975 mask = new_labels [0 ] == - 1
49304976 result = new_levels [0 ].take (new_labels [0 ])
49314977 if mask .any ():
4978+ if result .is_integer ():
4979+ # cannot store NaNs in an integer index, so promote to Float64Index
4980+ result = Float64Index (result .values , name = result .name )
49324981 result = result .putmask (mask , np .nan )
49334982
49344983 result .name = new_names [0 ]
@@ -5539,7 +5588,7 @@ def convert_indexer(start, stop, step, indexer=indexer, labels=labels):
55395588
55405589 else :
55415590
5542- loc = level_index .get_loc (key )
5591+ loc = - 1 if com . is_float ( key ) and np . isnan ( key ) else level_index .get_loc (key )
55435592 if level > 0 or self .lexsort_depth == 0 :
55445593 return np .array (labels == loc ,dtype = bool )
55455594 else :
@@ -6050,7 +6099,7 @@ def _trim_front(strings):
60506099
60516100
60526101def _sanitize_and_check (indexes ):
6053- kinds = list (set ([ type (index ) for index in indexes ] ))
6102+ kinds = list (set (type (index ) for index in indexes ))
60546103
60556104 if list in kinds :
60566105 if len (kinds ) > 1 :
@@ -6071,11 +6120,11 @@ def _get_consensus_names(indexes):
60716120
60726121 # find the non-none names, need to tupleify to make
60736122 # the set hashable, then reverse on return
6074- consensus_names = set ([
6123+ consensus_names = set (
60756124 tuple (i .names ) for i in indexes if all (n is not None for n in i .names )
6076- ] )
6125+ )
60776126 if len (consensus_names ) == 1 :
6078- return list (list ( consensus_names )[ 0 ] )
6127+ return list (consensus_names . pop () )
60796128 return [None ] * indexes [0 ].nlevels
60806129
60816130
0 commit comments