Skip to content

Commit 1c8816f

Browse files
gliptakjreback
authored andcommitted
Implement Akima1DInterpolator
closes #7588 Author: Gábor Lipták <[email protected]> Closes #12833 from gliptak/akima and squashes the following commits: a63dc31 [Gábor Lipták] Implement Akima1DInterpolator
1 parent 1c49caf commit 1c8816f

File tree

7 files changed

+103
-14
lines changed

7 files changed

+103
-14
lines changed

ci/requirements-3.5_OSX.run

-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ openpyxl
44
xlsxwriter
55
xlrd
66
xlwt
7-
scipy
87
numexpr
98
pytables
109
html5lib

doc/source/conf.py

+1
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,7 @@
292292
'matplotlib': ('http://matplotlib.org/', None),
293293
'python': ('http://docs.python.org/3', None),
294294
'numpy': ('http://docs.scipy.org/doc/numpy', None),
295+
'scipy': ('http://docs.scipy.org/doc/scipy', None),
295296
'py': ('http://pylib.readthedocs.org/en/latest/', None)
296297
}
297298
import glob

doc/source/missing_data.rst

+8-3
Original file line numberDiff line numberDiff line change
@@ -392,9 +392,12 @@ The ``method`` argument gives access to fancier interpolation methods.
392392
If you have scipy_ installed, you can set pass the name of a 1-d interpolation routine to ``method``.
393393
You'll want to consult the full scipy interpolation documentation_ and reference guide_ for details.
394394
The appropriate interpolation method will depend on the type of data you are working with.
395-
For example, if you are dealing with a time series that is growing at an increasing rate,
396-
``method='quadratic'`` may be appropriate. If you have values approximating a cumulative
397-
distribution function, then ``method='pchip'`` should work well.
395+
396+
* If you are dealing with a time series that is growing at an increasing rate,
397+
``method='quadratic'`` may be appropriate.
398+
* If you have values approximating a cumulative distribution function,
399+
then ``method='pchip'`` should work well.
400+
* To fill missing values with goal of smooth plotting, use ``method='akima'``.
398401

399402
.. warning::
400403

@@ -406,6 +409,8 @@ distribution function, then ``method='pchip'`` should work well.
406409
407410
df.interpolate(method='pchip')
408411
412+
df.interpolate(method='akima')
413+
409414
When interpolating via a polynomial or spline approximation, you must also specify
410415
the degree or order of the approximation:
411416

doc/source/whatsnew/v0.18.1.txt

+1
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ Other Enhancements
7777
- ``pd.read_csv()`` now supports opening ZIP files that contains a single CSV, via extension inference or explict ``compression='zip'`` (:issue:`12175`)
7878
- ``pd.read_csv()`` now supports opening files using xz compression, via extension inference or explicit ``compression='xz'`` is specified; ``xz`` compressions is also supported by ``DataFrame.to_csv`` in the same way (:issue:`11852`)
7979
- ``pd.read_msgpack()`` now always gives writeable ndarrays even when compression is used (:issue:`12359`).
80+
- ``interpolate()`` now supports ``method='akima'`` (:issue:`7588`).
8081
- ``Index.take`` now handles ``allow_fill`` and ``fill_value`` consistently (:issue:`12631`)
8182

8283
.. ipython:: python

pandas/core/generic.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -3451,7 +3451,8 @@ def interpolate(self, method='linear', axis=0, limit=None, inplace=False,
34513451
----------
34523452
method : {'linear', 'time', 'index', 'values', 'nearest', 'zero',
34533453
'slinear', 'quadratic', 'cubic', 'barycentric', 'krogh',
3454-
'polynomial', 'spline' 'piecewise_polynomial', 'pchip'}
3454+
'polynomial', 'spline' 'piecewise_polynomial', 'pchip',
3455+
'akima'}
34553456
34563457
* 'linear': ignore the index and treat the values as equally
34573458
spaced. This is the only method supported on MultiIndexes.
@@ -3465,13 +3466,16 @@ def interpolate(self, method='linear', axis=0, limit=None, inplace=False,
34653466
require that you also specify an `order` (int),
34663467
e.g. df.interpolate(method='polynomial', order=4).
34673468
These use the actual numerical values of the index.
3468-
* 'krogh', 'piecewise_polynomial', 'spline', and 'pchip' are all
3469+
* 'krogh', 'piecewise_polynomial', 'spline', 'pchip' and 'akima' are all
34693470
wrappers around the scipy interpolation methods of similar
34703471
names. These use the actual numerical values of the index. See
34713472
the scipy documentation for more on their behavior
34723473
`here <http://docs.scipy.org/doc/scipy/reference/interpolate.html#univariate-interpolation>`__ # noqa
34733474
`and here <http://docs.scipy.org/doc/scipy/reference/tutorial/interpolate.html>`__ # noqa
34743475
3476+
.. versionadded:: 0.18.1
3477+
Added support for the 'akima' method
3478+
34753479
axis : {0, 1}, default 0
34763480
* 0: fill column-by-column
34773481
* 1: fill row-by-row

pandas/core/missing.py

+64-7
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ def clean_interp_method(method, **kwargs):
8282
order = kwargs.get('order')
8383
valid = ['linear', 'time', 'index', 'values', 'nearest', 'zero', 'slinear',
8484
'quadratic', 'cubic', 'barycentric', 'polynomial', 'krogh',
85-
'piecewise_polynomial', 'pchip', 'spline']
85+
'piecewise_polynomial', 'pchip', 'akima', 'spline']
8686
if method in ('spline', 'polynomial') and order is None:
8787
raise ValueError("You must specify the order of the spline or "
8888
"polynomial.")
@@ -188,7 +188,7 @@ def _interp_limit(invalid, fw_limit, bw_limit):
188188

189189
sp_methods = ['nearest', 'zero', 'slinear', 'quadratic', 'cubic',
190190
'barycentric', 'krogh', 'spline', 'polynomial',
191-
'piecewise_polynomial', 'pchip']
191+
'piecewise_polynomial', 'pchip', 'akima']
192192
if method in sp_methods:
193193
inds = np.asarray(xvalues)
194194
# hack for DatetimeIndex, #1646
@@ -232,12 +232,19 @@ def _interpolate_scipy_wrapper(x, y, new_x, method, fill_value=None,
232232
# GH 5975, scipy.interp1d can't hande datetime64s
233233
x, new_x = x._values.astype('i8'), new_x.astype('i8')
234234

235-
try:
236-
alt_methods['pchip'] = interpolate.pchip_interpolate
237-
except AttributeError:
238-
if method == 'pchip':
239-
raise ImportError("Your version of scipy does not support "
235+
if method == 'pchip':
236+
try:
237+
alt_methods['pchip'] = interpolate.pchip_interpolate
238+
except AttributeError:
239+
raise ImportError("Your version of Scipy does not support "
240240
"PCHIP interpolation.")
241+
elif method == 'akima':
242+
try:
243+
from scipy.interpolate import Akima1DInterpolator # noqa
244+
alt_methods['akima'] = _akima_interpolate
245+
except ImportError:
246+
raise ImportError("Your version of Scipy does not support "
247+
"Akima interpolation.")
241248

242249
interp1d_methods = ['nearest', 'zero', 'slinear', 'quadratic', 'cubic',
243250
'polynomial']
@@ -267,6 +274,56 @@ def _interpolate_scipy_wrapper(x, y, new_x, method, fill_value=None,
267274
return new_y
268275

269276

277+
def _akima_interpolate(xi, yi, x, der=0, axis=0):
278+
"""
279+
Convenience function for akima interpolation.
280+
xi and yi are arrays of values used to approximate some function f,
281+
with ``yi = f(xi)``.
282+
283+
See `Akima1DInterpolator` for details.
284+
285+
Parameters
286+
----------
287+
xi : array_like
288+
A sorted list of x-coordinates, of length N.
289+
yi : array_like
290+
A 1-D array of real values. `yi`'s length along the interpolation
291+
axis must be equal to the length of `xi`. If N-D array, use axis
292+
parameter to select correct axis.
293+
x : scalar or array_like
294+
Of length M.
295+
der : int or list, optional
296+
How many derivatives to extract; None for all potentially
297+
nonzero derivatives (that is a number equal to the number
298+
of points), or a list of derivatives to extract. This number
299+
includes the function value as 0th derivative.
300+
axis : int, optional
301+
Axis in the yi array corresponding to the x-coordinate values.
302+
303+
See Also
304+
--------
305+
scipy.interpolate.Akima1DInterpolator
306+
307+
Returns
308+
-------
309+
y : scalar or array_like
310+
The result, of length R or length M or M by R,
311+
312+
"""
313+
from scipy import interpolate
314+
try:
315+
P = interpolate.Akima1DInterpolator(xi, yi, axis=axis)
316+
except TypeError:
317+
# Scipy earlier than 0.17.0 missing axis
318+
P = interpolate.Akima1DInterpolator(xi, yi)
319+
if der == 0:
320+
return P(x)
321+
elif interpolate._isscalar(der):
322+
return P(x, der=der)
323+
else:
324+
return [P(x, nu) for nu in der]
325+
326+
270327
def interpolate_2d(values, method='pad', axis=0, limit=None, fill_value=None,
271328
dtype=None):
272329
""" perform an actual interpolation of values, values will be make 2-d if

pandas/tests/test_generic.py

+23-1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@ def _skip_if_no_pchip():
3434
except ImportError:
3535
raise nose.SkipTest('scipy.interpolate.pchip missing')
3636

37+
38+
def _skip_if_no_akima():
39+
try:
40+
from scipy.interpolate import Akima1DInterpolator # noqa
41+
except ImportError:
42+
raise nose.SkipTest('scipy.interpolate.Akima1DInterpolator missing')
43+
3744
# ----------------------------------------------------------------------
3845
# Generic types test cases
3946

@@ -734,7 +741,7 @@ def test_interpolate(self):
734741
non_ts[0] = np.NaN
735742
self.assertRaises(ValueError, non_ts.interpolate, method='time')
736743

737-
def test_interp_regression(self):
744+
def test_interpolate_pchip(self):
738745
tm._skip_if_no_scipy()
739746
_skip_if_no_pchip()
740747

@@ -747,6 +754,21 @@ def test_interp_regression(self):
747754
# does not blow up, GH5977
748755
interp_s[49:51]
749756

757+
def test_interpolate_akima(self):
758+
tm._skip_if_no_scipy()
759+
_skip_if_no_akima()
760+
761+
ser = Series([10, 11, 12, 13])
762+
763+
expected = Series([11.00, 11.25, 11.50, 11.75,
764+
12.00, 12.25, 12.50, 12.75, 13.00],
765+
index=Index([1.0, 1.25, 1.5, 1.75,
766+
2.0, 2.25, 2.5, 2.75, 3.0]))
767+
# interpolate at new_index
768+
new_index = ser.index.union(Index([1.25, 1.5, 1.75, 2.25, 2.5, 2.75]))
769+
interp_s = ser.reindex(new_index).interpolate(method='akima')
770+
assert_series_equal(interp_s[1:3], expected)
771+
750772
def test_interpolate_corners(self):
751773
s = Series([np.nan, np.nan])
752774
assert_series_equal(s.interpolate(), s)

0 commit comments

Comments
 (0)