@@ -141,7 +141,7 @@ def _copyto(a, val, mask):
141
141
return a
142
142
143
143
144
- def _remove_nan_1d (arr1d , overwrite_input = False ):
144
+ def _remove_nan_1d (arr1d , second_arr1d = None , overwrite_input = False ):
145
145
"""
146
146
Equivalent to arr1d[~arr1d.isnan()], but in a different order
147
147
@@ -151,13 +151,17 @@ def _remove_nan_1d(arr1d, overwrite_input=False):
151
151
----------
152
152
arr1d : ndarray
153
153
Array to remove nans from
154
+ second_arr1d : ndarray or None
155
+ A second array which will have the same positions removed as arr1d.
154
156
overwrite_input : bool
155
157
True if `arr1d` can be modified in place
156
158
157
159
Returns
158
160
-------
159
161
res : ndarray
160
162
Array with nan elements removed
163
+ second_res : ndarray or None
164
+ Second array with nan element positions of first array removed.
161
165
overwrite_input : bool
162
166
True if `res` can be modified in place, given the constraint on the
163
167
input
@@ -172,9 +176,12 @@ def _remove_nan_1d(arr1d, overwrite_input=False):
172
176
if s .size == arr1d .size :
173
177
warnings .warn ("All-NaN slice encountered" , RuntimeWarning ,
174
178
stacklevel = 6 )
175
- return arr1d [:0 ], True
179
+ if second_arr1d is None :
180
+ return arr1d [:0 ], None , True
181
+ else :
182
+ return arr1d [:0 ], second_arr1d [:0 ], True
176
183
elif s .size == 0 :
177
- return arr1d , overwrite_input
184
+ return arr1d , second_arr1d , overwrite_input
178
185
else :
179
186
if not overwrite_input :
180
187
arr1d = arr1d .copy ()
@@ -183,7 +190,15 @@ def _remove_nan_1d(arr1d, overwrite_input=False):
183
190
# fill nans in beginning of array with non-nans of end
184
191
arr1d [s [:enonan .size ]] = enonan
185
192
186
- return arr1d [:- s .size ], True
193
+ if second_arr1d is None :
194
+ return arr1d [:- s .size ], None , True
195
+ else :
196
+ if not overwrite_input :
197
+ second_arr1d = second_arr1d .copy ()
198
+ enonan = second_arr1d [- s .size :][~ c [- s .size :]]
199
+ second_arr1d [s [:enonan .size ]] = enonan
200
+
201
+ return arr1d [:- s .size ], second_arr1d [:- s .size ], True
187
202
188
203
189
204
def _divide_by_count (a , b , out = None ):
@@ -1061,7 +1076,7 @@ def _nanmedian1d(arr1d, overwrite_input=False):
1061
1076
Private function for rank 1 arrays. Compute the median ignoring NaNs.
1062
1077
See nanmedian for parameter usage
1063
1078
"""
1064
- arr1d_parsed , overwrite_input = _remove_nan_1d (
1079
+ arr1d_parsed , _ , overwrite_input = _remove_nan_1d (
1065
1080
arr1d , overwrite_input = overwrite_input ,
1066
1081
)
1067
1082
@@ -1650,13 +1665,36 @@ def _nanquantile_ureduce_func(
1650
1665
wgt = None if weights is None else weights .ravel ()
1651
1666
result = _nanquantile_1d (part , q , overwrite_input , method , weights = wgt )
1652
1667
else :
1653
- result = np .apply_along_axis (_nanquantile_1d , axis , a , q ,
1654
- overwrite_input , method , weights )
1655
- # apply_along_axis fills in collapsed axis with results.
1656
- # Move that axis to the beginning to match percentile's
1657
- # convention.
1658
- if q .ndim != 0 :
1659
- result = np .moveaxis (result , axis , 0 )
1668
+ # Note that this code could try to fill in `out` right away
1669
+ if weights is None :
1670
+ result = np .apply_along_axis (_nanquantile_1d , axis , a , q ,
1671
+ overwrite_input , method , weights )
1672
+ # apply_along_axis fills in collapsed axis with results.
1673
+ # Move those axes to the beginning to match percentile's
1674
+ # convention.
1675
+ if q .ndim != 0 :
1676
+ from_ax = [axis + i for i in range (q .ndim )]
1677
+ result = np .moveaxis (result , from_ax , list (range (q .ndim )))
1678
+ else :
1679
+ # We need to apply along axis over 2 arrays, a and weights.
1680
+ # move operation axes to end for simplicity:
1681
+ a = np .moveaxis (a , axis , - 1 )
1682
+ if weights is not None :
1683
+ weights = np .moveaxis (weights , axis , - 1 )
1684
+ if out is not None :
1685
+ result = out
1686
+ else :
1687
+ # weights are limited to `inverted_cdf` so the result dtype
1688
+ # is known to be identical to that of `a` here:
1689
+ result = np .empty_like (a , shape = q .shape + a .shape [:- 1 ])
1690
+
1691
+ for ii in np .ndindex (a .shape [:- 1 ]):
1692
+ result [(...,) + ii ] = _nanquantile_1d (
1693
+ a [ii ], q , weights = weights [ii ],
1694
+ overwrite_input = overwrite_input , method = method ,
1695
+ )
1696
+ # This path dealt with `out` already...
1697
+ return result
1660
1698
1661
1699
if out is not None :
1662
1700
out [...] = result
@@ -1670,8 +1708,9 @@ def _nanquantile_1d(
1670
1708
Private function for rank 1 arrays. Compute quantile ignoring NaNs.
1671
1709
See nanpercentile for parameter usage
1672
1710
"""
1673
- arr1d , overwrite_input = _remove_nan_1d (arr1d ,
1674
- overwrite_input = overwrite_input )
1711
+ # TODO: What to do when arr1d = [1, np.nan] and weights = [0, 1]?
1712
+ arr1d , weights , overwrite_input = _remove_nan_1d (arr1d ,
1713
+ second_arr1d = weights , overwrite_input = overwrite_input )
1675
1714
if arr1d .size == 0 :
1676
1715
# convert to scalar
1677
1716
return np .full (q .shape , np .nan , dtype = arr1d .dtype )[()]
0 commit comments