@@ -398,6 +398,79 @@ def _make_flex_doc(op_name, typ):
398
398
return doc
399
399
400
400
401
+ # -----------------------------------------------------------------------------
402
+ # Masking NA values and fallbacks for operations numpy does not support
403
+
404
+ def fill_binop (left , right , fill_value ):
405
+ """
406
+ If a non-None fill_value is given, replace null entries in left and right
407
+ with this value, but only in positions where _one_ of left/right is null,
408
+ not both.
409
+
410
+ Parameters
411
+ ----------
412
+ left : array-like
413
+ right : array-like
414
+ fill_value : object
415
+
416
+ Returns
417
+ -------
418
+ left : array-like
419
+ right : array-like
420
+
421
+ Notes
422
+ -----
423
+ Makes copies if fill_value is not None
424
+ """
425
+ # TODO: can we make a no-copy implementation?
426
+ if fill_value is not None :
427
+ left_mask = isna (left )
428
+ right_mask = isna (right )
429
+ left = left .copy ()
430
+ right = right .copy ()
431
+
432
+ # one but not both
433
+ mask = left_mask ^ right_mask
434
+ left [left_mask & mask ] = fill_value
435
+ right [right_mask & mask ] = fill_value
436
+ return left , right
437
+
438
+
439
+ def mask_cmp_op (x , y , op , allowed_types ):
440
+ """
441
+ Apply the function `op` to only non-null points in x and y.
442
+
443
+ Parameters
444
+ ----------
445
+ x : array-like
446
+ y : array-like
447
+ op : binary operation
448
+ allowed_types : class or tuple of classes
449
+
450
+ Returns
451
+ -------
452
+ result : ndarray[bool]
453
+ """
454
+ # TODO: Can we make the allowed_types arg unnecessary?
455
+ xrav = x .ravel ()
456
+ result = np .empty (x .size , dtype = bool )
457
+ if isinstance (y , allowed_types ):
458
+ yrav = y .ravel ()
459
+ mask = notna (xrav ) & notna (yrav )
460
+ result [mask ] = op (np .array (list (xrav [mask ])),
461
+ np .array (list (yrav [mask ])))
462
+ else :
463
+ mask = notna (xrav )
464
+ result [mask ] = op (np .array (list (xrav [mask ])), y )
465
+
466
+ if op == operator .ne : # pragma: no cover
467
+ np .putmask (result , ~ mask , True )
468
+ else :
469
+ np .putmask (result , ~ mask , False )
470
+ result = result .reshape (x .shape )
471
+ return result
472
+
473
+
401
474
# -----------------------------------------------------------------------------
402
475
# Functions that add arithmetic methods to objects, given arithmetic factory
403
476
# methods
@@ -1127,23 +1200,7 @@ def na_op(x, y):
1127
1200
with np .errstate (invalid = 'ignore' ):
1128
1201
result = op (x , y )
1129
1202
except TypeError :
1130
- xrav = x .ravel ()
1131
- result = np .empty (x .size , dtype = bool )
1132
- if isinstance (y , (np .ndarray , ABCSeries )):
1133
- yrav = y .ravel ()
1134
- mask = notna (xrav ) & notna (yrav )
1135
- result [mask ] = op (np .array (list (xrav [mask ])),
1136
- np .array (list (yrav [mask ])))
1137
- else :
1138
- mask = notna (xrav )
1139
- result [mask ] = op (np .array (list (xrav [mask ])), y )
1140
-
1141
- if op == operator .ne : # pragma: no cover
1142
- np .putmask (result , ~ mask , True )
1143
- else :
1144
- np .putmask (result , ~ mask , False )
1145
- result = result .reshape (x .shape )
1146
-
1203
+ result = mask_cmp_op (x , y , op , (np .ndarray , ABCSeries ))
1147
1204
return result
1148
1205
1149
1206
@Appender ('Wrapper for flexible comparison methods {name}'
@@ -1221,23 +1278,7 @@ def na_op(x, y):
1221
1278
try :
1222
1279
result = expressions .evaluate (op , str_rep , x , y )
1223
1280
except TypeError :
1224
- xrav = x .ravel ()
1225
- result = np .empty (x .size , dtype = bool )
1226
- if isinstance (y , np .ndarray ):
1227
- yrav = y .ravel ()
1228
- mask = notna (xrav ) & notna (yrav )
1229
- result [mask ] = op (np .array (list (xrav [mask ])),
1230
- np .array (list (yrav [mask ])))
1231
- else :
1232
- mask = notna (xrav )
1233
- result [mask ] = op (np .array (list (xrav [mask ])), y )
1234
-
1235
- if op == operator .ne : # pragma: no cover
1236
- np .putmask (result , ~ mask , True )
1237
- else :
1238
- np .putmask (result , ~ mask , False )
1239
- result = result .reshape (x .shape )
1240
-
1281
+ result = mask_cmp_op (x , y , op , np .ndarray )
1241
1282
return result
1242
1283
1243
1284
@Appender ('Wrapper for comparison method {name}' .format (name = name ))
0 commit comments