2828 ABCDatetimeArray ,
2929 ABCExtensionArray ,
3030 ABCIndex ,
31- ABCIndexClass ,
3231 ABCSeries ,
3332 ABCTimedeltaArray ,
3433)
@@ -53,13 +52,15 @@ def comp_method_OBJECT_ARRAY(op, x, y):
5352 if isinstance (y , (ABCSeries , ABCIndex )):
5453 y = y .values
5554
56- result = libops .vec_compare (x .ravel (), y , op )
55+ if x .shape != y .shape :
56+ raise ValueError ("Shapes must match" , x .shape , y .shape )
57+ result = libops .vec_compare (x .ravel (), y .ravel (), op )
5758 else :
5859 result = libops .scalar_compare (x .ravel (), y , op )
5960 return result .reshape (x .shape )
6061
6162
62- def masked_arith_op (x , y , op ):
63+ def masked_arith_op (x : np . ndarray , y , op ):
6364 """
6465 If the given arithmetic operation fails, attempt it again on
6566 only the non-null elements of the input array(s).
@@ -78,10 +79,22 @@ def masked_arith_op(x, y, op):
7879 dtype = find_common_type ([x .dtype , y .dtype ])
7980 result = np .empty (x .size , dtype = dtype )
8081
82+ if len (x ) != len (y ):
83+ if not _can_broadcast (x , y ):
84+ raise ValueError (x .shape , y .shape )
85+
86+ # Call notna on pre-broadcasted y for performance
87+ ymask = notna (y )
88+ y = np .broadcast_to (y , x .shape )
89+ ymask = np .broadcast_to (ymask , x .shape )
90+
91+ else :
92+ ymask = notna (y )
93+
8194 # NB: ravel() is only safe since y is ndarray; for e.g. PeriodIndex
8295 # we would get int64 dtype, see GH#19956
8396 yrav = y .ravel ()
84- mask = notna (xrav ) & notna ( yrav )
97+ mask = notna (xrav ) & ymask . ravel ( )
8598
8699 if yrav .shape != mask .shape :
87100 # FIXME: GH#5284, GH#5035, GH#19448
@@ -211,6 +224,51 @@ def arithmetic_op(left: ArrayLike, right: Any, op, str_rep: str):
211224 return res_values
212225
213226
227+ def _broadcast_comparison_op (lvalues , rvalues , op ) -> np .ndarray :
228+ """
229+ Broadcast a comparison operation between two 2D arrays.
230+
231+ Parameters
232+ ----------
233+ lvalues : np.ndarray or ExtensionArray
234+ rvalues : np.ndarray or ExtensionArray
235+
236+ Returns
237+ -------
238+ np.ndarray[bool]
239+ """
240+ if isinstance (rvalues , np .ndarray ):
241+ rvalues = np .broadcast_to (rvalues , lvalues .shape )
242+ result = comparison_op (lvalues , rvalues , op )
243+ else :
244+ result = np .empty (lvalues .shape , dtype = bool )
245+ for i in range (len (lvalues )):
246+ result [i , :] = comparison_op (lvalues [i ], rvalues [:, 0 ], op )
247+ return result
248+
249+
250+ def _can_broadcast (lvalues , rvalues ) -> bool :
251+ """
252+ Check if we can broadcast rvalues to match the shape of lvalues.
253+
254+ Parameters
255+ ----------
256+ lvalues : np.ndarray or ExtensionArray
257+ rvalues : np.ndarray or ExtensionArray
258+
259+ Returns
260+ -------
261+ bool
262+ """
263+ # We assume that lengths dont match
264+ if lvalues .ndim == rvalues .ndim == 2 :
265+ # See if we can broadcast unambiguously
266+ if lvalues .shape [1 ] == rvalues .shape [- 1 ]:
267+ if rvalues .shape [0 ] == 1 :
268+ return True
269+ return False
270+
271+
214272def comparison_op (
215273 left : ArrayLike , right : Any , op , str_rep : Optional [str ] = None ,
216274) -> ArrayLike :
@@ -237,12 +295,16 @@ def comparison_op(
237295 # TODO: same for tuples?
238296 rvalues = np .asarray (rvalues )
239297
240- if isinstance (rvalues , (np .ndarray , ABCExtensionArray , ABCIndexClass )):
298+ if isinstance (rvalues , (np .ndarray , ABCExtensionArray )):
241299 # TODO: make this treatment consistent across ops and classes.
242300 # We are not catching all listlikes here (e.g. frozenset, tuple)
243301 # The ambiguous case is object-dtype. See GH#27803
244302 if len (lvalues ) != len (rvalues ):
245- raise ValueError ("Lengths must match to compare" )
303+ if _can_broadcast (lvalues , rvalues ):
304+ return _broadcast_comparison_op (lvalues , rvalues , op )
305+ raise ValueError (
306+ "Lengths must match to compare" , lvalues .shape , rvalues .shape
307+ )
246308
247309 if should_extension_dispatch (lvalues , rvalues ):
248310 res_values = dispatch_to_extension_op (op , lvalues , rvalues )
0 commit comments