Blog

Linear interpolation in Python with numpy and scipy

Which function to use, what each one does at the edges of your data, and the silent default that turns bad input into a flat line.


Python gives you three good options and several ways to get a wrong number with no error message. Here is what each one does, what happens when your target falls outside the data, and when the extra dependency is worth it.

The quick answer

Use np.interp(x, xp, fp) for straight-line interpolation. It is fast, it works on arrays, and it needs nothing beyond NumPy.

Add left=np.nan, right=np.nan unless you actively want it to return the nearest endpoint value for out-of-range input, which is what it does by default and rarely what you meant.

numpy.interp

Pass your target, the known x values, and the known y values:

xp = [1, 2, 3]
fp = [10, 20, 25]
np.interp(2.5, xp, fp) → 22.5

Check it by hand: 2.5 is halfway between 2 and 3, where the values are 20 and 25, so 20 + 0.5 × 5 = 22.5. Pass an array of targets and you get an array back, which is far faster than looping in Python.

Two requirements that are documented and still catch people.

xp has to be increasing. There is no check for this. Give it unsorted input and it returns numbers that look fine and mean nothing. Sort first with order = np.argsort(xp) and index both arrays by it.

NaNs spread. One NaN in your y values poisons the two intervals either side of it. Filter them out before interpolating rather than cleaning up afterwards.

What it does past the end of your data

np.interp(5, [1, 2, 3], [10, 20, 25]) → 25.0

No error, no warning. It returns the last y value and keeps returning it forever. That is a reasonable default for signal processing and a dangerous one for analysis, because a chart of the result shows a flat line that reads as a genuine plateau in your data rather than as missing input.

Make it visible instead:

np.interp(5, xp, fp, left=np.nan, right=np.nan) → nan

A NaN in a plot leaves a gap you notice immediately. This matters for the reason set out in interpolation vs extrapolation: past the edge of your data, nothing keeps the answer honest.

scipy.interpolate.interp1d

This builds a function you call repeatedly, and it supports several kinds: 'linear', 'nearest', 'quadratic', 'cubic', plus 'previous' and 'next' for step behaviour.

f = interp1d(xp, fp, kind='cubic')
f(2.5)

Building once and calling many times is the point of it. For a single value, np.interp is simpler and quicker.

Its behaviour at the edges is the opposite of NumPy's, and better: out-of-range input raises a ValueError. You can turn that off with bounds_error=False, fill_value='extrapolate', but think before you do. Extrapolating a cubic is a bad idea, because the curve on the last segment owes your data nothing once it leaves the range.

SciPy now lists interp1d as legacy. New code should use np.interp for linear work, CubicSpline or PchipInterpolator for smooth curves, and make_interp_spline for anything else. Existing code that uses it is not broken and does not need rewriting.

CubicSpline

from scipy.interpolate import CubicSpline
cs = CubicSpline(xp, fp, bc_type='natural')
cs(2.5)    # the value
cs(2.5, 1) # the slope there
cs.integrate(1, 3) # area under the curve

You get the slope and the area for free, which is the main reason to keep a spline object around rather than a plain array of interpolated numbers.

The bc_type argument controls what the curve does at the two ends. 'not-a-knot' is the default and usually looks best on real data. 'natural' makes the curve stop bending at the ends, and 'clamped' makes it flat there. The spline tutorial works the natural case through by hand if you want to see what the object is doing internally.

Use PchipInterpolator instead when your data only goes one direction and has to stay that way. A cubic spline can dip below your data between two points, which produces a negative value in a series that is physically positive. PchipInterpolator gives up a little smoothness and promises not to overshoot.

pandas

s.interpolate(method='time', limit=3, limit_area='inside')

On a series indexed by timestamps, method='linear' ignores the timestamps and pretends the rows are evenly spaced, which is wrong for irregular data. method='time' uses the real gaps. limit stops a long outage becoming an invented ramp, and limit_area='inside' prevents it filling leading and trailing gaps, which would be extrapolation. The surrounding pitfalls, including the leakage one, are covered in interpolation in data science.

Writing it yourself

For one value with no dependencies:

def lerp(x, x1, y1, x2, y2):
    if x2 == x1:
        return y1
    t = (x - x1) / (x2 - x1)
    return (1 - t) * y1 + t * y2

The first two lines handle duplicate x values, which do happen in real tables and otherwise blow up with a ZeroDivisionError somewhere deep in a loop.

The last line uses (1 - t) * y1 + t * y2 rather than y1 + t * (y2 - y1) because it returns exactly y1 at t = 0 and exactly y2 at t = 1 in floating point. Recent Python versions also ship math.lerp if all you need is the blend.

Picking one

What you needUse
Fast straight-line interpolation on arraysnp.interp
An error when input falls outside the datainterp1d, default settings
A smooth curve, slopes, or areasCubicSpline
Data that must never go backwardsPchipInterpolator
A time series with gapsSeries.interpolate(method='time')
Values on a 2D gridRegularGridInterpolator
Scattered points in 2D or 3Dgriddata or RBFInterpolator

Whichever you pick, check one value by hand the first time you use it. The interpolation calculator is quick for that, and one verified number catches unsorted input and unit mix-ups before they end up in a report.

Try it yourself

Run these numbers through the calculator and check the working step by step.

Open the calculator