@@ -1033,7 +1033,7 @@ def _validate_index_level(self, level):
1033
1033
verification must be done like in MultiIndex.
1034
1034
1035
1035
"""
1036
- if isinstance (level , int ):
1036
+ if com . is_integer (level ):
1037
1037
if level < 0 and level != - 1 :
1038
1038
raise IndexError ("Too many levels: Index has only 1 level,"
1039
1039
" %d is not a valid level number" % (level ,))
@@ -1045,10 +1045,44 @@ def _validate_index_level(self, level):
1045
1045
raise KeyError ('Level %s must be same as name (%s)'
1046
1046
% (level , self .name ))
1047
1047
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 ))
1049
1059
self ._validate_index_level (level )
1050
1060
return 0
1051
1061
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
+
1052
1086
@cache_readonly
1053
1087
def inferred_type (self ):
1054
1088
""" return a string of the type inferred from the values """
@@ -4294,28 +4328,38 @@ def _from_elements(values, labels=None, levels=None, names=None,
4294
4328
sortorder = None ):
4295
4329
return MultiIndex (levels , labels , names , sortorder = sortorder )
4296
4330
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 :
4299
4341
count = self .names .count (level )
4300
4342
if count > 1 :
4301
4343
raise ValueError ('The name %s occurs multiple times, use a '
4302
4344
'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 ))
4319
4363
return level
4320
4364
4321
4365
_tuples = None
@@ -4891,14 +4935,16 @@ def _drop_from_level(self, labels, level):
4891
4935
4892
4936
return self [mask ]
4893
4937
4894
- def droplevel (self , level = 0 ):
4938
+ def droplevel (self , level = 0 , ignore_names = False ):
4895
4939
"""
4896
4940
Return Index with requested level removed. If MultiIndex has only 2
4897
4941
levels, the result will be of Index type not MultiIndex.
4898
4942
4899
4943
Parameters
4900
4944
----------
4901
4945
level : int/level name or list thereof
4946
+ ignore_names : boolean, default True
4947
+ If True, level must be an int or list thereof
4902
4948
4903
4949
Notes
4904
4950
-----
@@ -4916,7 +4962,7 @@ def droplevel(self, level=0):
4916
4962
new_labels = list (self .labels )
4917
4963
new_names = list (self .names )
4918
4964
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 )
4920
4966
4921
4967
for i in levnums :
4922
4968
new_levels .pop (i )
@@ -4929,6 +4975,9 @@ def droplevel(self, level=0):
4929
4975
mask = new_labels [0 ] == - 1
4930
4976
result = new_levels [0 ].take (new_labels [0 ])
4931
4977
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 )
4932
4981
result = result .putmask (mask , np .nan )
4933
4982
4934
4983
result .name = new_names [0 ]
@@ -5539,7 +5588,7 @@ def convert_indexer(start, stop, step, indexer=indexer, labels=labels):
5539
5588
5540
5589
else :
5541
5590
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 )
5543
5592
if level > 0 or self .lexsort_depth == 0 :
5544
5593
return np .array (labels == loc ,dtype = bool )
5545
5594
else :
@@ -6050,7 +6099,7 @@ def _trim_front(strings):
6050
6099
6051
6100
6052
6101
def _sanitize_and_check (indexes ):
6053
- kinds = list (set ([ type (index ) for index in indexes ] ))
6102
+ kinds = list (set (type (index ) for index in indexes ))
6054
6103
6055
6104
if list in kinds :
6056
6105
if len (kinds ) > 1 :
@@ -6071,11 +6120,11 @@ def _get_consensus_names(indexes):
6071
6120
6072
6121
# find the non-none names, need to tupleify to make
6073
6122
# the set hashable, then reverse on return
6074
- consensus_names = set ([
6123
+ consensus_names = set (
6075
6124
tuple (i .names ) for i in indexes if all (n is not None for n in i .names )
6076
- ] )
6125
+ )
6077
6126
if len (consensus_names ) == 1 :
6078
- return list (list ( consensus_names )[ 0 ] )
6127
+ return list (consensus_names . pop () )
6079
6128
return [None ] * indexes [0 ].nlevels
6080
6129
6081
6130
0 commit comments