@@ -449,6 +449,35 @@ def _na_for_min_count(values: np.ndarray, axis: int | None) -> Scalar | np.ndarr
449
449
return np .full (result_shape , fill_value , dtype = values .dtype )
450
450
451
451
452
+ def maybe_operate_rowwise (func ):
453
+ """
454
+ NumPy operations on C-contiguous ndarrays with axis=1 can be
455
+ very slow. Operate row-by-row and concatenate the results.
456
+ """
457
+
458
+ @functools .wraps (func )
459
+ def newfunc (values : np .ndarray , * , axis : int | None = None , ** kwargs ):
460
+ if (
461
+ axis == 1
462
+ and values .ndim == 2
463
+ and values .flags ["C_CONTIGUOUS" ]
464
+ and values .dtype != object
465
+ ):
466
+ arrs = list (values )
467
+ if kwargs .get ("mask" ) is not None :
468
+ mask = kwargs .pop ("mask" )
469
+ results = [
470
+ func (arrs [i ], mask = mask [i ], ** kwargs ) for i in range (len (arrs ))
471
+ ]
472
+ else :
473
+ results = [func (x , ** kwargs ) for x in arrs ]
474
+ return np .array (results )
475
+
476
+ return func (values , axis = axis , ** kwargs )
477
+
478
+ return newfunc
479
+
480
+
452
481
def nanany (
453
482
values : np .ndarray ,
454
483
* ,
@@ -543,6 +572,7 @@ def nanall(
543
572
544
573
@disallow ("M8" )
545
574
@_datetimelike_compat
575
+ @maybe_operate_rowwise
546
576
def nansum (
547
577
values : np .ndarray ,
548
578
* ,
@@ -1111,6 +1141,7 @@ def nanargmin(
1111
1141
1112
1142
1113
1143
@disallow ("M8" , "m8" )
1144
+ @maybe_operate_rowwise
1114
1145
def nanskew (
1115
1146
values : np .ndarray ,
1116
1147
* ,
@@ -1198,6 +1229,7 @@ def nanskew(
1198
1229
1199
1230
1200
1231
@disallow ("M8" , "m8" )
1232
+ @maybe_operate_rowwise
1201
1233
def nankurt (
1202
1234
values : np .ndarray ,
1203
1235
* ,
@@ -1294,6 +1326,7 @@ def nankurt(
1294
1326
1295
1327
1296
1328
@disallow ("M8" , "m8" )
1329
+ @maybe_operate_rowwise
1297
1330
def nanprod (
1298
1331
values : np .ndarray ,
1299
1332
* ,
0 commit comments