@@ -111,7 +111,7 @@ def clean_interp_method(method, **kwargs):
111
111
112
112
113
113
def interpolate_1d (xvalues , yvalues , method = 'linear' , limit = None ,
114
- limit_direction = 'forward' , fill_value = None ,
114
+ limit_direction = 'forward' , limit_area = None , fill_value = None ,
115
115
bounds_error = False , order = None , ** kwargs ):
116
116
"""
117
117
Logic for the 1-d interpolation. The result should be 1-d, inputs
@@ -151,28 +151,12 @@ def interpolate_1d(xvalues, yvalues, method='linear', limit=None,
151
151
raise ValueError (msg .format (valid = valid_limit_directions ,
152
152
invalid = limit_direction ))
153
153
154
- from pandas import Series
155
- ys = Series (yvalues )
156
- start_nans = set (range (ys .first_valid_index ()))
157
- end_nans = set (range (1 + ys .last_valid_index (), len (valid )))
158
-
159
- # violate_limit is a list of the indexes in the series whose yvalue is
160
- # currently NaN, and should still be NaN after the interpolation.
161
- # Specifically:
162
- #
163
- # If limit_direction='forward' or None then the list will contain NaNs at
164
- # the beginning of the series, and NaNs that are more than 'limit' away
165
- # from the prior non-NaN.
166
- #
167
- # If limit_direction='backward' then the list will contain NaNs at
168
- # the end of the series, and NaNs that are more than 'limit' away
169
- # from the subsequent non-NaN.
170
- #
171
- # If limit_direction='both' then the list will contain NaNs that
172
- # are more than 'limit' away from any non-NaN.
173
- #
174
- # If limit=None, then use default behavior of filling an unlimited number
175
- # of NaNs in the direction specified by limit_direction
154
+ if limit_area is not None :
155
+ valid_limit_areas = ['inside' , 'outside' ]
156
+ limit_area = limit_area .lower ()
157
+ if limit_area not in valid_limit_areas :
158
+ raise ValueError ('Invalid limit_area: expecting one of {}, got '
159
+ '{}.' .format (valid_limit_areas , limit_area ))
176
160
177
161
# default limit is unlimited GH #16282
178
162
if limit is None :
@@ -183,22 +167,43 @@ def interpolate_1d(xvalues, yvalues, method='linear', limit=None,
183
167
elif limit < 1 :
184
168
raise ValueError ('Limit must be greater than 0' )
185
169
186
- # each possible limit_direction
187
- # TODO: do we need sorted?
188
- if limit_direction == 'forward' and limit is not None :
189
- violate_limit = sorted (start_nans |
190
- set (_interp_limit (invalid , limit , 0 )))
191
- elif limit_direction == 'forward' :
192
- violate_limit = sorted (start_nans )
193
- elif limit_direction == 'backward' and limit is not None :
194
- violate_limit = sorted (end_nans |
195
- set (_interp_limit (invalid , 0 , limit )))
170
+ from pandas import Series
171
+ ys = Series (yvalues )
172
+
173
+ # These are sets of index pointers to invalid values... i.e. {0, 1, etc...
174
+ all_nans = set (np .flatnonzero (invalid ))
175
+ start_nans = set (range (ys .first_valid_index ()))
176
+ end_nans = set (range (1 + ys .last_valid_index (), len (valid )))
177
+ mid_nans = all_nans - start_nans - end_nans
178
+
179
+ # Like the sets above, preserve_nans contains indices of invalid values,
180
+ # but in this case, it is the final set of indices that need to be
181
+ # preserved as NaN after the interpolation.
182
+
183
+ # For example if limit_direction='forward' then preserve_nans will
184
+ # contain indices of NaNs at the beginning of the series, and NaNs that
185
+ # are more than'limit' away from the prior non-NaN.
186
+
187
+ # set preserve_nans based on direction using _interp_limit
188
+ if limit_direction == 'forward' :
189
+ preserve_nans = start_nans | set (_interp_limit (invalid , limit , 0 ))
196
190
elif limit_direction == 'backward' :
197
- violate_limit = sorted (end_nans )
198
- elif limit_direction == 'both' and limit is not None :
199
- violate_limit = sorted (_interp_limit (invalid , limit , limit ))
191
+ preserve_nans = end_nans | set (_interp_limit (invalid , 0 , limit ))
200
192
else :
201
- violate_limit = []
193
+ # both directions... just use _interp_limit
194
+ preserve_nans = set (_interp_limit (invalid , limit , limit ))
195
+
196
+ # if limit_area is set, add either mid or outside indices
197
+ # to preserve_nans GH #16284
198
+ if limit_area == 'inside' :
199
+ # preserve NaNs on the outside
200
+ preserve_nans |= start_nans | end_nans
201
+ elif limit_area == 'outside' :
202
+ # preserve NaNs on the inside
203
+ preserve_nans |= mid_nans
204
+
205
+ # sort preserve_nans and covert to list
206
+ preserve_nans = sorted (preserve_nans )
202
207
203
208
xvalues = getattr (xvalues , 'values' , xvalues )
204
209
yvalues = getattr (yvalues , 'values' , yvalues )
@@ -215,7 +220,7 @@ def interpolate_1d(xvalues, yvalues, method='linear', limit=None,
215
220
else :
216
221
inds = xvalues
217
222
result [invalid ] = np .interp (inds [invalid ], inds [valid ], yvalues [valid ])
218
- result [violate_limit ] = np .nan
223
+ result [preserve_nans ] = np .nan
219
224
return result
220
225
221
226
sp_methods = ['nearest' , 'zero' , 'slinear' , 'quadratic' , 'cubic' ,
@@ -234,7 +239,7 @@ def interpolate_1d(xvalues, yvalues, method='linear', limit=None,
234
239
fill_value = fill_value ,
235
240
bounds_error = bounds_error ,
236
241
order = order , ** kwargs )
237
- result [violate_limit ] = np .nan
242
+ result [preserve_nans ] = np .nan
238
243
return result
239
244
240
245
@@ -646,8 +651,24 @@ def fill_zeros(result, x, y, name, fill):
646
651
647
652
648
653
def _interp_limit (invalid , fw_limit , bw_limit ):
649
- """Get idx of values that won't be filled b/c they exceed the limits.
654
+ """
655
+ Get indexers of values that won't be filled
656
+ because they exceed the limits.
657
+
658
+ Parameters
659
+ ----------
660
+ invalid : boolean ndarray
661
+ fw_limit : int or None
662
+ forward limit to index
663
+ bw_limit : int or None
664
+ backward limit to index
665
+
666
+ Returns
667
+ -------
668
+ set of indexers
650
669
670
+ Notes
671
+ -----
651
672
This is equivalent to the more readable, but slower
652
673
653
674
.. code-block:: python
@@ -660,6 +681,8 @@ def _interp_limit(invalid, fw_limit, bw_limit):
660
681
# 1. operate on the reversed array
661
682
# 2. subtract the returned indicies from N - 1
662
683
N = len (invalid )
684
+ f_idx = set ()
685
+ b_idx = set ()
663
686
664
687
def inner (invalid , limit ):
665
688
limit = min (limit , N )
@@ -668,18 +691,25 @@ def inner(invalid, limit):
668
691
set (np .where ((~ invalid [:limit + 1 ]).cumsum () == 0 )[0 ]))
669
692
return idx
670
693
671
- if fw_limit == 0 :
672
- f_idx = set (np .where (invalid )[0 ])
673
- else :
674
- f_idx = inner (invalid , fw_limit )
694
+ if fw_limit is not None :
675
695
676
- if bw_limit == 0 :
677
- # then we don't even need to care about backwards, just use forwards
678
- return f_idx
679
- else :
680
- b_idx = set (N - 1 - np .asarray (list (inner (invalid [::- 1 ], bw_limit ))))
681
696
if fw_limit == 0 :
682
- return b_idx
697
+ f_idx = set (np .where (invalid )[0 ])
698
+ else :
699
+ f_idx = inner (invalid , fw_limit )
700
+
701
+ if bw_limit is not None :
702
+
703
+ if bw_limit == 0 :
704
+ # then we don't even need to care about backwards
705
+ # just use forwards
706
+ return f_idx
707
+ else :
708
+ b_idx = list (inner (invalid [::- 1 ], bw_limit ))
709
+ b_idx = set (N - 1 - np .asarray (b_idx ))
710
+ if fw_limit == 0 :
711
+ return b_idx
712
+
683
713
return f_idx & b_idx
684
714
685
715
0 commit comments