@@ -819,7 +819,7 @@ def dispatch_to_index_op(op, left, right, index_class):
819
819
# avoid accidentally allowing integer add/sub. For datetime64[tz] dtypes,
820
820
# left_idx may inherit a freq from a cached DatetimeIndex.
821
821
# See discussion in GH#19147.
822
- if left_idx . freq is not None :
822
+ if getattr ( left_idx , ' freq' , None ) is not None :
823
823
left_idx = left_idx ._shallow_copy (freq = None )
824
824
try :
825
825
result = op (left_idx , right )
@@ -867,9 +867,8 @@ def na_op(x, y):
867
867
868
868
# dispatch to the categorical if we have a categorical
869
869
# in either operand
870
- if is_categorical_dtype (x ):
871
- return op (x , y )
872
- elif is_categorical_dtype (y ) and not is_scalar (y ):
870
+ if is_categorical_dtype (y ) and not is_scalar (y ):
871
+ # The `not is_scalar(y)` check excludes the string "category"
873
872
return op (y , x )
874
873
875
874
elif is_object_dtype (x .dtype ):
@@ -917,17 +916,36 @@ def wrapper(self, other, axis=None):
917
916
if axis is not None :
918
917
self ._get_axis_number (axis )
919
918
919
+ res_name = _get_series_op_result_name (self , other )
920
+
920
921
if isinstance (other , ABCDataFrame ): # pragma: no cover
921
922
# Defer to DataFrame implementation; fail early
922
923
return NotImplemented
923
924
925
+ elif isinstance (other , ABCSeries ) and not self ._indexed_same (other ):
926
+ raise ValueError ("Can only compare identically-labeled "
927
+ "Series objects" )
928
+
929
+ elif is_categorical_dtype (self ):
930
+ # Dispatch to Categorical implementation; pd.CategoricalIndex
931
+ # behavior is non-canonical GH#19513
932
+ res_values = dispatch_to_index_op (op , self , other , pd .Categorical )
933
+ return self ._constructor (res_values , index = self .index ,
934
+ name = res_name )
935
+
936
+ elif is_timedelta64_dtype (self ):
937
+ res_values = dispatch_to_index_op (op , self , other ,
938
+ pd .TimedeltaIndex )
939
+ return self ._constructor (res_values , index = self .index ,
940
+ name = res_name )
941
+
924
942
elif isinstance (other , ABCSeries ):
925
- name = com ._maybe_match_name (self , other )
926
- if not self ._indexed_same (other ):
927
- msg = 'Can only compare identically-labeled Series objects'
928
- raise ValueError (msg )
943
+ # By this point we have checked that self._indexed_same(other)
929
944
res_values = na_op (self .values , other .values )
930
- return self ._constructor (res_values , index = self .index , name = name )
945
+ # rename is needed in case res_name is None and res_values.name
946
+ # is not.
947
+ return self ._constructor (res_values , index = self .index ,
948
+ name = res_name ).rename (res_name )
931
949
932
950
elif isinstance (other , (np .ndarray , pd .Index )):
933
951
# do not check length of zerodim array
@@ -937,15 +955,17 @@ def wrapper(self, other, axis=None):
937
955
raise ValueError ('Lengths must match to compare' )
938
956
939
957
res_values = na_op (self .values , np .asarray (other ))
940
- return self ._constructor (res_values ,
941
- index = self .index ).__finalize__ (self )
942
-
943
- elif (isinstance (other , pd .Categorical ) and
944
- not is_categorical_dtype (self )):
945
- raise TypeError ("Cannot compare a Categorical for op {op} with "
946
- "Series of dtype {typ}.\n If you want to compare "
947
- "values, use 'series <op> np.asarray(other)'."
948
- .format (op = op , typ = self .dtype ))
958
+ result = self ._constructor (res_values , index = self .index )
959
+ # rename is needed in case res_name is None and self.name
960
+ # is not.
961
+ return result .__finalize__ (self ).rename (res_name )
962
+
963
+ elif isinstance (other , pd .Categorical ):
964
+ # ordering of checks matters; by this point we know
965
+ # that not is_categorical_dtype(self)
966
+ res_values = op (self .values , other )
967
+ return self ._constructor (res_values , index = self .index ,
968
+ name = res_name )
949
969
950
970
elif is_scalar (other ) and isna (other ):
951
971
# numpy does not like comparisons vs None
@@ -956,16 +976,9 @@ def wrapper(self, other, axis=None):
956
976
return self ._constructor (res_values , index = self .index ,
957
977
name = self .name , dtype = 'bool' )
958
978
959
- if is_categorical_dtype (self ):
960
- # cats are a special case as get_values() would return an ndarray,
961
- # which would then not take categories ordering into account
962
- # we can go directly to op, as the na_op would just test again and
963
- # dispatch to it.
964
- with np .errstate (all = 'ignore' ):
965
- res = op (self .values , other )
966
979
else :
967
980
values = self .get_values ()
968
- if isinstance (other , ( list , np . ndarray ) ):
981
+ if isinstance (other , list ):
969
982
other = np .asarray (other )
970
983
971
984
with np .errstate (all = 'ignore' ):
@@ -975,10 +988,9 @@ def wrapper(self, other, axis=None):
975
988
.format (typ = type (other )))
976
989
977
990
# always return a full value series here
978
- res = com ._values_from_object (res )
979
-
980
- res = pd .Series (res , index = self .index , name = self .name , dtype = 'bool' )
981
- return res
991
+ res_values = com ._values_from_object (res )
992
+ return pd .Series (res_values , index = self .index ,
993
+ name = res_name , dtype = 'bool' )
982
994
983
995
return wrapper
984
996
0 commit comments