Repository: pandas-dev/pandas

Issue:
pd.Series.interpolate(method="quadratic) Error with non-numeric index column
I'm working with the Series.interpolate function and I noticed that a DataFrame's index column can cause some weird problems when using the quadratic method. 

First example: trying to impute data with non-numeric index column crashes:

```python
data = {'A': ["a", "b", "c", "d"], 'B': [0, 1, np.nan, 100]}
df = pd.DataFrame(data=data)
df.set_index("A", inplace=True)
s = pd.Series(df["B"])

s.interpolate(method="quadratic")
```

Raises the following error:  'TypeError: ufunc 'isnan' not supported for the input types, and the inputs could not be safely coerced to any supported types according to the casting rule ''safe'' '

I know Pandas uses Scipy's quadratic interpolate method, and while this error is raised inside Scipy, I believe it is because Pandas expects a numeric index column to interpolate data when using a quadratic method and sends it to Scipy's method.

The previous code runs without errors by not using the quadratic method.

The following code also runs without any errors: 

```python
data = {'A': [1, 2, 3, 4], 'B': [0, 1, np.nan, 100]}
df = pd.DataFrame(data=data)
df.set_index("A", inplace=True)
s = pd.Series(df["B"])

s.interpolate(method="quadratic")
```

outputs:

```python
A
0      0.0
1      1.0
2     18.0
4    100.0
Name: B, dtype: float64
```
So while it makes sense to use the index column as an indicator of how many timesteps separate two values in a series, other methods seem to simply assume 1 between each row and avoid using the index column.

Two things I can think of that could be helpful is sending a more descriptive error message when the method receives a non-numeric index column or writing this condition in the docs, as I couldn't find anything about this error and solved it by tinkering with the index column.

Let me know if any of these ideas seem appropiate to prepare a PR.

#### Output of ``pd.show_versions()``

<details>
INSTALLED VERSIONS
------------------
commit: None
python: 3.6.3.final.0
python-bits: 64
OS: Windows
OS-release: 10
machine: AMD64
processor: Intel64 Family 6 Model 60 Stepping 3, GenuineIntel
byteorder: little
LC_ALL: None
LANG: None
LOCALE: None.None

pandas: 0.20.3
pytest: 3.2.1
pip: 10.0.1
setuptools: 36.5.0.post20170921
Cython: 0.26.1
numpy: 1.13.3
scipy: 0.19.1
xarray: None
IPython: 6.1.0
sphinx: 1.6.3
patsy: 0.4.1
dateutil: 2.6.1
pytz: 2017.2
blosc: None
bottleneck: 1.2.1
tables: 3.4.2
numexpr: 2.6.2
feather: None
matplotlib: 2.1.0
openpyxl: 2.4.8
xlrd: 1.1.0
xlwt: 1.3.0
xlsxwriter: 1.0.2
lxml: 4.1.0
bs4: 4.6.0
html5lib: 0.9999999
sqlalchemy: 1.1.13
pymysql: None
psycopg2: None
jinja2: 2.9.6
s3fs: None
pandas_gbq: None
pandas_datareader: None

</details>



Discussion:
I'm not sure we should assume that the user wants us to use `range(N)` as the interpolation values for object indexes. That's assuming things about ordering and equal-spacing that may not be correct.

> other methods seem to simply assume 1 between each row and avoid using the index column.

What methods do you mean by 'other'? AFAIK, `linear` is the only one that ignores the index values.

> sending a more descriptive error message when the method receives a non-numeric index

That seems reasonable.

> What methods do you mean by 'other'? AFAIK, linear is the only one that ignores the index 

You're right, [Series.interpolate's documentation](https://pandas.pydata.org/pandas-docs/stable/generated/pandas.Series.interpolate.html)
states that all methods except linear use index values:

 _‘nearest’, ‘zero’, ‘slinear’, ‘quadratic’, ‘cubic’, ‘barycentric’, ‘polynomial’ is passed to scipy.interpolate.interp1d. Both ‘polynomial’ and ‘spline’ require that you also specify an order (int), e.g. df.interpolate(method=’polynomial’, order=4). These use the actual numerical values of the index._

I misread and thought that the last line was referring only to polynomial and spline, though now that I think about it it doesn't make sense that the other methods wouldn't.

I'll start working on this.

Resolving patch:
diff --git a/pandas/core/generic.py b/pandas/core/generic.py
--- a/pandas/core/generic.py
+++ b/pandas/core/generic.py
@@ -25,10 +25,10 @@
 from pandas.core.dtypes.cast import maybe_promote, maybe_upcast_putmask
 from pandas.core.dtypes.common import (
     ensure_int64, ensure_object, is_bool, is_bool_dtype,
-    is_datetime64_any_dtype, is_datetime64tz_dtype, is_dict_like,
-    is_extension_array_dtype, is_integer, is_list_like, is_number,
-    is_numeric_dtype, is_object_dtype, is_period_arraylike, is_re_compilable,
-    is_scalar, is_timedelta64_dtype, pandas_dtype)
+    is_datetime64_any_dtype, is_datetime64_dtype, is_datetime64tz_dtype,
+    is_dict_like, is_extension_array_dtype, is_integer, is_list_like,
+    is_number, is_numeric_dtype, is_object_dtype, is_period_arraylike,
+    is_re_compilable, is_scalar, is_timedelta64_dtype, pandas_dtype)
 from pandas.core.dtypes.generic import ABCDataFrame, ABCPanel, ABCSeries
 from pandas.core.dtypes.inference import is_hashable
 from pandas.core.dtypes.missing import isna, notna
@@ -6863,6 +6863,18 @@ def interpolate(self, method='linear', axis=0, limit=None, inplace=False,
             index = np.arange(len(_maybe_transposed_self._get_axis(alt_ax)))
         else:
             index = _maybe_transposed_self._get_axis(alt_ax)
+            methods = {"index", "values", "nearest", "time"}
+            is_numeric_or_datetime = (
+                is_numeric_dtype(index) or
+                is_datetime64_dtype(index) or
+                is_timedelta64_dtype(index)
+            )
+            if method not in methods and not is_numeric_or_datetime:
+                raise ValueError(
+                    "Index column must be numeric or datetime type when "
+                    "using {method} method other than linear. "
+                    "Try setting a numeric or datetime index column before "
+                    "interpolating.".format(method=method))
 
         if isna(index).any():
             raise NotImplementedError("Interpolation with NaNs in the index "


Test patch:
diff --git a/pandas/tests/series/test_missing.py b/pandas/tests/series/test_missing.py
--- a/pandas/tests/series/test_missing.py
+++ b/pandas/tests/series/test_missing.py
@@ -870,6 +870,22 @@ def nontemporal_method(request):
     return method, kwargs
 
 
+@pytest.fixture(params=['linear', 'slinear', 'zero', 'quadratic', 'cubic',
+                        'barycentric', 'krogh', 'polynomial', 'spline',
+                        'piecewise_polynomial', 'from_derivatives', 'pchip',
+                        'akima', ])
+def interp_methods_ind(request):
+    """ Fixture that returns a (method name, required kwargs) pair to
+    be tested for various Index types.
+
+    This fixture does not include methods - 'time', 'index', 'nearest',
+    'values' as a parameterization
+    """
+    method = request.param
+    kwargs = dict(order=1) if method in ('spline', 'polynomial') else dict()
+    return method, kwargs
+
+
 class TestSeriesInterpolateData():
     def test_interpolate(self, datetime_series, string_series):
         ts = Series(np.arange(len(datetime_series), dtype=float),
@@ -1397,3 +1413,55 @@ def test_nonzero_warning(self):
         ser = pd.Series([1, 0, 3, 4])
         with tm.assert_produces_warning(FutureWarning):
             ser.nonzero()
+
+    @pytest.mark.parametrize(
+        "ind",
+        [
+            ['a', 'b', 'c', 'd'],
+            pd.period_range(start="2019-01-01", periods=4),
+            pd.interval_range(start=0, end=4),
+        ])
+    def test_interp_non_timedelta_index(self, interp_methods_ind, ind):
+        # gh 21662
+        df = pd.DataFrame([0, 1, np.nan, 3], index=ind)
+
+        method, kwargs = interp_methods_ind
+        if method == "pchip":
+            _skip_if_no_pchip()
+
+        if method == "linear":
+            result = df[0].interpolate(**kwargs)
+            expected = pd.Series([0.0, 1.0, 2.0, 3.0], name=0, index=ind)
+            assert_series_equal(result, expected)
+        else:
+            expected_error = (
+                "Index column must be numeric or datetime type when "
+                "using {method} method other than linear. "
+                "Try setting a numeric or datetime index column before "
+                "interpolating.".format(method=method))
+            with pytest.raises(ValueError, match=expected_error):
+                df[0].interpolate(method=method, **kwargs)
+
+    def test_interpolate_timedelta_index(self, interp_methods_ind):
+        """
+        Tests for non numerical index types  - object, period, timedelta
+        Note that all methods except time, index, nearest and values
+        are tested here.
+        """
+        # gh 21662
+        ind = pd.timedelta_range(start=1, periods=4)
+        df = pd.DataFrame([0, 1, np.nan, 3], index=ind)
+
+        method, kwargs = interp_methods_ind
+        if method == "pchip":
+            _skip_if_no_pchip()
+
+        if method in {"linear", "pchip"}:
+            result = df[0].interpolate(method=method, **kwargs)
+            expected = pd.Series([0.0, 1.0, 2.0, 3.0], name=0, index=ind)
+            assert_series_equal(result, expected)
+        else:
+            pytest.skip(
+                "This interpolation method is not supported for "
+                "Timedelta Index yet."
+            )