@@ -3891,6 +3891,60 @@ def equal_levels(self, other: MultiIndex) -> bool:
38913891 # --------------------------------------------------------------------
38923892 # Set Methods
38933893
3894+ def difference (self , other , sort = None ):
3895+ """
3896+ Return a new MultiIndex with elements from the index not in `other`.
3897+
3898+ Parameters
3899+ ----------
3900+ other : MultiIndex or array-like
3901+ sort : bool or None, default None
3902+ Whether to sort the resulting index.
3903+
3904+ Returns
3905+ -------
3906+ MultiIndex
3907+ """
3908+ if not isinstance (other , MultiIndex ):
3909+ other = MultiIndex .from_tuples (other , names = self .names )
3910+
3911+ # Convert 'other' to codes using self's levels
3912+ other_codes = []
3913+ for i , (lev , name ) in enumerate (zip (self .levels , self .names )):
3914+ level_vals = other .get_level_values (i )
3915+ other_code = lev .get_indexer (level_vals )
3916+ other_codes .append (other_code )
3917+
3918+ # Create mask for elements not in 'other'
3919+ n = len (self )
3920+ mask = np .ones (n , dtype = bool )
3921+ engine = self ._engine
3922+ for codes in zip (* other_codes ):
3923+ try :
3924+ loc = engine .get_loc (tuple (codes ))
3925+ if isinstance (loc , slice ):
3926+ mask [loc ] = False
3927+ elif isinstance (loc , np .ndarray ):
3928+ mask &= ~ loc
3929+ else :
3930+ mask [loc ] = False
3931+ except KeyError :
3932+ pass
3933+
3934+ new_codes = [code [mask ] for code in self .codes ]
3935+ result = MultiIndex (
3936+ levels = self .levels ,
3937+ codes = new_codes ,
3938+ names = self .names ,
3939+ verify_integrity = False ,
3940+ )
3941+ if sort is None or sort is True :
3942+ try :
3943+ return result .sort_values ()
3944+ except TypeError :
3945+ pass
3946+ return result
3947+
38943948 def _union (self , other , sort ) -> MultiIndex :
38953949 other , result_names = self ._convert_can_do_setop (other )
38963950 if other .has_duplicates :
0 commit comments