Skip to content

Commit ea1d5f5

Browse files
nmusolinogfyoung
authored andcommittedFeb 11, 2019
BUG: Fix exceptions when Series.interpolate's order parameter is missing or invalid (#25246)
* BUG: raise accurate exception from Series.interpolate (#24014) * Actually validate `order` before use in spline * Remove unnecessary check and dead code * Clean up comparison/tests based on feedback * Include invalid order value in exception * Check for NaN order in spline validation * Add whatsnew entry for bug fix * CLN: Make unit tests assert one error at a time * CLN: break test into distinct test case * PEP8 fix in test module * CLN: Test fixture for interpolate methods
1 parent 57fb83f commit ea1d5f5

File tree

4 files changed

+78
-58
lines changed

4 files changed

+78
-58
lines changed
 

‎doc/source/whatsnew/v0.25.0.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ Indexing
141141
Missing
142142
^^^^^^^
143143

144-
-
144+
- Fixed misleading exception message in :meth:`Series.missing` if argument ``order`` is required, but omitted (:issue:`10633`, :issue:`24014`).
145145
-
146146
-
147147

‎pandas/core/internals/blocks.py

+12-18
Original file line numberDiff line numberDiff line change
@@ -1115,24 +1115,18 @@ def check_int_bool(self, inplace):
11151115
fill_value=fill_value,
11161116
coerce=coerce,
11171117
downcast=downcast)
1118-
# try an interp method
1119-
try:
1120-
m = missing.clean_interp_method(method, **kwargs)
1121-
except ValueError:
1122-
m = None
1123-
1124-
if m is not None:
1125-
r = check_int_bool(self, inplace)
1126-
if r is not None:
1127-
return r
1128-
return self._interpolate(method=m, index=index, values=values,
1129-
axis=axis, limit=limit,
1130-
limit_direction=limit_direction,
1131-
limit_area=limit_area,
1132-
fill_value=fill_value, inplace=inplace,
1133-
downcast=downcast, **kwargs)
1134-
1135-
raise ValueError("invalid method '{0}' to interpolate.".format(method))
1118+
# validate the interp method
1119+
m = missing.clean_interp_method(method, **kwargs)
1120+
1121+
r = check_int_bool(self, inplace)
1122+
if r is not None:
1123+
return r
1124+
return self._interpolate(method=m, index=index, values=values,
1125+
axis=axis, limit=limit,
1126+
limit_direction=limit_direction,
1127+
limit_area=limit_area,
1128+
fill_value=fill_value, inplace=inplace,
1129+
downcast=downcast, **kwargs)
11361130

11371131
def _interpolate_with_fill(self, method='pad', axis=0, inplace=False,
11381132
limit=None, fill_value=None, coerce=False,

‎pandas/core/missing.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -293,9 +293,10 @@ def _interpolate_scipy_wrapper(x, y, new_x, method, fill_value=None,
293293
bounds_error=bounds_error)
294294
new_y = terp(new_x)
295295
elif method == 'spline':
296-
# GH #10633
297-
if not order:
298-
raise ValueError("order needs to be specified and greater than 0")
296+
# GH #10633, #24014
297+
if isna(order) or (order <= 0):
298+
raise ValueError("order needs to be specified and greater than 0; "
299+
"got order: {}".format(order))
299300
terp = interpolate.UnivariateSpline(x, y, k=order, **kwargs)
300301
new_y = terp(new_x)
301302
else:

‎pandas/tests/series/test_missing.py

+61-36
Original file line numberDiff line numberDiff line change
@@ -854,8 +854,23 @@ def test_series_pad_backfill_limit(self):
854854
assert_series_equal(result, expected)
855855

856856

857-
class TestSeriesInterpolateData():
857+
@pytest.fixture(params=['linear', 'index', 'values', 'nearest', 'slinear',
858+
'zero', 'quadratic', 'cubic', 'barycentric', 'krogh',
859+
'polynomial', 'spline', 'piecewise_polynomial',
860+
'from_derivatives', 'pchip', 'akima', ])
861+
def nontemporal_method(request):
862+
""" Fixture that returns an (method name, required kwargs) pair.
863+
864+
This fixture does not include method 'time' as a parameterization; that
865+
method requires a Series with a DatetimeIndex, and is generally tested
866+
separately from these non-temporal methods.
867+
"""
868+
method = request.param
869+
kwargs = dict(order=1) if method in ('spline', 'polynomial') else dict()
870+
return method, kwargs
871+
858872

873+
class TestSeriesInterpolateData():
859874
def test_interpolate(self, datetime_series, string_series):
860875
ts = Series(np.arange(len(datetime_series), dtype=float),
861876
datetime_series.index)
@@ -875,12 +890,12 @@ def test_interpolate(self, datetime_series, string_series):
875890
time_interp = ord_ts_copy.interpolate(method='time')
876891
tm.assert_series_equal(time_interp, ord_ts)
877892

878-
# try time interpolation on a non-TimeSeries
879-
# Only raises ValueError if there are NaNs.
880-
non_ts = string_series.copy()
881-
non_ts[0] = np.NaN
882-
msg = ("time-weighted interpolation only works on Series or DataFrames"
883-
" with a DatetimeIndex")
893+
def test_interpolate_time_raises_for_non_timeseries(self):
894+
# When method='time' is used on a non-TimeSeries that contains a null
895+
# value, a ValueError should be raised.
896+
non_ts = Series([0, 1, 2, np.NaN])
897+
msg = ("time-weighted interpolation only works on Series.* "
898+
"with a DatetimeIndex")
884899
with pytest.raises(ValueError, match=msg):
885900
non_ts.interpolate(method='time')
886901

@@ -1061,21 +1076,35 @@ def test_interp_limit(self):
10611076
result = s.interpolate(method='linear', limit=2)
10621077
assert_series_equal(result, expected)
10631078

1064-
# GH 9217, make sure limit is an int and greater than 0
1065-
methods = ['linear', 'time', 'index', 'values', 'nearest', 'zero',
1066-
'slinear', 'quadratic', 'cubic', 'barycentric', 'krogh',
1067-
'polynomial', 'spline', 'piecewise_polynomial', None,
1068-
'from_derivatives', 'pchip', 'akima']
1069-
s = pd.Series([1, 2, np.nan, np.nan, 5])
1070-
msg = (r"Limit must be greater than 0|"
1071-
"time-weighted interpolation only works on Series or"
1072-
r" DataFrames with a DatetimeIndex|"
1073-
r"invalid method '(polynomial|spline|None)' to interpolate|"
1074-
"Limit must be an integer")
1075-
for limit in [-1, 0, 1., 2.]:
1076-
for method in methods:
1077-
with pytest.raises(ValueError, match=msg):
1078-
s.interpolate(limit=limit, method=method)
1079+
@pytest.mark.parametrize("limit", [-1, 0])
1080+
def test_interpolate_invalid_nonpositive_limit(self, nontemporal_method,
1081+
limit):
1082+
# GH 9217: make sure limit is greater than zero.
1083+
s = pd.Series([1, 2, np.nan, 4])
1084+
method, kwargs = nontemporal_method
1085+
with pytest.raises(ValueError, match="Limit must be greater than 0"):
1086+
s.interpolate(limit=limit, method=method, **kwargs)
1087+
1088+
def test_interpolate_invalid_float_limit(self, nontemporal_method):
1089+
# GH 9217: make sure limit is an integer.
1090+
s = pd.Series([1, 2, np.nan, 4])
1091+
method, kwargs = nontemporal_method
1092+
limit = 2.0
1093+
with pytest.raises(ValueError, match="Limit must be an integer"):
1094+
s.interpolate(limit=limit, method=method, **kwargs)
1095+
1096+
@pytest.mark.parametrize("invalid_method", [None, 'nonexistent_method'])
1097+
def test_interp_invalid_method(self, invalid_method):
1098+
s = Series([1, 3, np.nan, 12, np.nan, 25])
1099+
1100+
msg = "method must be one of.* Got '{}' instead".format(invalid_method)
1101+
with pytest.raises(ValueError, match=msg):
1102+
s.interpolate(method=invalid_method)
1103+
1104+
# When an invalid method and invalid limit (such as -1) are
1105+
# provided, the error message reflects the invalid method.
1106+
with pytest.raises(ValueError, match=msg):
1107+
s.interpolate(method=invalid_method, limit=-1)
10791108

10801109
def test_interp_limit_forward(self):
10811110
s = Series([1, 3, np.nan, np.nan, np.nan, 11])
@@ -1276,11 +1305,20 @@ def test_interp_limit_no_nans(self):
12761305
@td.skip_if_no_scipy
12771306
@pytest.mark.parametrize("method", ['polynomial', 'spline'])
12781307
def test_no_order(self, method):
1308+
# see GH-10633, GH-24014
12791309
s = Series([0, 1, np.nan, 3])
1280-
msg = "invalid method '{}' to interpolate".format(method)
1310+
msg = "You must specify the order of the spline or polynomial"
12811311
with pytest.raises(ValueError, match=msg):
12821312
s.interpolate(method=method)
12831313

1314+
@td.skip_if_no_scipy
1315+
@pytest.mark.parametrize('order', [-1, -1.0, 0, 0.0, np.nan])
1316+
def test_interpolate_spline_invalid_order(self, order):
1317+
s = Series([0, 1, np.nan, 3])
1318+
msg = "order needs to be specified and greater than 0"
1319+
with pytest.raises(ValueError, match=msg):
1320+
s.interpolate(method='spline', order=order)
1321+
12841322
@td.skip_if_no_scipy
12851323
def test_spline(self):
12861324
s = Series([1, 2, np.nan, 4, 5, np.nan, 7])
@@ -1313,19 +1351,6 @@ def test_spline_interpolation(self):
13131351
expected1 = s.interpolate(method='spline', order=1)
13141352
assert_series_equal(result1, expected1)
13151353

1316-
@td.skip_if_no_scipy
1317-
def test_spline_error(self):
1318-
# see gh-10633
1319-
s = pd.Series(np.arange(10) ** 2)
1320-
s[np.random.randint(0, 9, 3)] = np.nan
1321-
msg = "invalid method 'spline' to interpolate"
1322-
with pytest.raises(ValueError, match=msg):
1323-
s.interpolate(method='spline')
1324-
1325-
msg = "order needs to be specified and greater than 0"
1326-
with pytest.raises(ValueError, match=msg):
1327-
s.interpolate(method='spline', order=0)
1328-
13291354
def test_interp_timedelta64(self):
13301355
# GH 6424
13311356
df = Series([1, np.nan, 3],

0 commit comments

Comments
 (0)
Please sign in to comment.