Làm cách nào để scipy.interpolate đưa ra kết quả ngoại suy vượt ra ngoài phạm vi đầu vào?


82

Tôi đang cố gắng chuyển một chương trình sử dụng bộ nội suy cuộn tay (do một trường toán học phát triển) sang sử dụng bộ nội suy do scipy cung cấp. Tôi muốn sử dụng hoặc bọc bộ nội suy scipy để nó có hành vi gần nhất có thể với bộ nội suy cũ.

Sự khác biệt chính giữa hai chức năng là trong bộ nội suy ban đầu của chúng tôi - nếu giá trị đầu vào cao hơn hoặc thấp hơn phạm vi đầu vào, bộ nội suy ban đầu của chúng tôi sẽ ngoại suy kết quả. Nếu bạn thử điều này với bộ nội suy scipy, nó sẽ tăng a ValueError. Hãy coi chương trình này như một ví dụ:

import numpy as np
from scipy import interpolate

x = np.arange(0,10)
y = np.exp(-x/3.0)
f = interpolate.interp1d(x, y)

print f(9)
print f(11) # Causes ValueError, because it's greater than max(x)

Có cách nào hợp lý để làm cho nó thay vì sụp đổ, dòng cuối cùng sẽ chỉ đơn giản thực hiện phép ngoại suy tuyến tính, tiếp tục các gradient được xác định bởi hai điểm đầu tiên và cuối cùng đến vô cùng.

Lưu ý rằng trong phần mềm thực, tôi không thực sự sử dụng hàm exp - ở đây chỉ để minh họa!


2
scipy.interpolate.UnivariateSplinedường như ngoại suy mà không có vấn đề.
heltonbiker 20/12/12

Câu trả lời:


37

1. Phép ngoại suy không đổi

Bạn có thể sử dụng interphàm từ scipy, nó ngoại suy các giá trị bên trái và bên phải dưới dạng hằng số ngoài phạm vi:

>>> from scipy import interp, arange, exp
>>> x = arange(0,10)
>>> y = exp(-x/3.0)
>>> interp([9,10], x, y)
array([ 0.04978707,  0.04978707])

2. Ngoại suy tuyến tính (hoặc tùy chỉnh khác)

Bạn có thể viết một trình bao bọc xung quanh một hàm nội suy đảm nhiệm việc ngoại suy tuyến tính. Ví dụ:

from scipy.interpolate import interp1d
from scipy import arange, array, exp

def extrap1d(interpolator):
    xs = interpolator.x
    ys = interpolator.y

    def pointwise(x):
        if x < xs[0]:
            return ys[0]+(x-xs[0])*(ys[1]-ys[0])/(xs[1]-xs[0])
        elif x > xs[-1]:
            return ys[-1]+(x-xs[-1])*(ys[-1]-ys[-2])/(xs[-1]-xs[-2])
        else:
            return interpolator(x)

    def ufunclike(xs):
        return array(list(map(pointwise, array(xs))))

    return ufunclike

extrap1dnhận một hàm nội suy và trả về một hàm cũng có thể ngoại suy. Và bạn có thể sử dụng nó như thế này:

x = arange(0,10)
y = exp(-x/3.0)
f_i = interp1d(x, y)
f_x = extrap1d(f_i)

print f_x([9,10])

Đầu ra:

[ 0.04978707  0.03009069]

1
Trong Python 3.6, tôi phải thêm listvào return: return array(list(map(pointwise, array(xs)))) để giải quyết trình lặp.
user21387

Giải pháp này linh hoạt hơn tùy chọn điền_value = "ngoại suy". Tôi đã có thể điều chỉnh chức năng bên trong 'theo chiều hướng' phù hợp với nhu cầu của mình, tôi giữ lại bình luận ở trên và chèn danh sách khi cần. Phải nói rằng, đôi khi bạn có thể chỉ muốn có một máy phát điện.
Wilmer E. Henao

1
Lưu ý rằng giải pháp đầu tiên dựa trên scipy.interpkhông còn được yêu cầu vì nó không được dùng nữa và sẽ biến mất trong SciPy 2.0.0. Họ gợi ý bằng cách sử dụng numpy.interpthay thế nhưng như đã nêu trong câu hỏi, nó sẽ không hoạt động ở đây
Yosko

86

Bạn có thể xem qua InterpolatedUnivariateSpline

Dưới đây là một ví dụ sử dụng nó:

import matplotlib.pyplot as plt
import numpy as np
from scipy.interpolate import InterpolatedUnivariateSpline

# given values
xi = np.array([0.2, 0.5, 0.7, 0.9])
yi = np.array([0.3, -0.1, 0.2, 0.1])
# positions to inter/extrapolate
x = np.linspace(0, 1, 50)
# spline order: 1 linear, 2 quadratic, 3 cubic ... 
order = 1
# do inter/extrapolation
s = InterpolatedUnivariateSpline(xi, yi, k=order)
y = s(x)

# example showing the interpolation for linear, quadratic and cubic interpolation
plt.figure()
plt.plot(xi, yi)
for order in range(1, 4):
    s = InterpolatedUnivariateSpline(xi, yi, k=order)
    y = s(x)
    plt.plot(x, y)
plt.show()

2
đây là câu trả lời tốt nhất. Đó là những gì tôi đã làm. I used k=1 (order), Vì vậy nó trở thành một suy tuyến tính, vàI used bbox=[xmin-w, xmax+w] where w is my tolerance
eusoubrasileiro

76

Kể từ phiên bản SciPy 0.17.0, có một tùy chọn mới cho scipy.interpolate.interp1d cho phép ngoại suy. Đơn giản chỉ cần đặt fill_value = 'ngoại suy' trong cuộc gọi. Sửa đổi mã của bạn theo cách này mang lại:

import numpy as np
from scipy import interpolate

x = np.arange(0,10)
y = np.exp(-x/3.0)
f = interpolate.interp1d(x, y, fill_value='extrapolate')

print f(9)
print f(11)

và đầu ra là:

0.0497870683679
0.010394302658

Loại ngoại suy có giống với loại nội suy không? Ví dụ, chúng ta có thể nội suy tuyến tính với phép ngoại suy điểm gần nhất không?
a.s sáng

Nếu loại = 'khối', fill_value = 'ngoại suy' không hoạt động.
vlmercado

@ a.sam: Tôi không chắc ý của bạn ... Có lẽ, nếu bạn sử dụng loại = 'tuyến tính' với fill_value = 'nội suy' thì bạn sẽ nhận được nội suy tuyến tính và nếu bạn sử dụng nó với fill_value = 'ngoại suy' sau đó bạn nhận được một phép ngoại suy tuyến tính, không?
Moot

@vlmercado: bạn có thể giải thích theo cách nào mà nó không hoạt động được không? Tôi đã thử chạy ví dụ trên với việc bổ sung loại = 'khối' và nó hoạt động tốt đối với tôi.
Moot

@Moot, sử dụng scipy 0.18.1, tôi nhận được như sau: ValueError: Phép ngoại suy không làm việc với các loại = spline
vlmercado

8

Điều gì về scipy.interpolate.splrep (với mức độ 1 và không làm mịn):

>> tck = scipy.interpolate.splrep([1, 2, 3, 4, 5], [1, 4, 9, 16, 25], k=1, s=0)
>> scipy.interpolate.splev(6, tck)
34.0

Nó dường như làm những gì bạn muốn, vì 34 = 25 + (25 - 16).


7

Đây là một phương pháp thay thế chỉ sử dụng gói numpy. Nó tận dụng các hàm mảng của numpy, do đó có thể nhanh hơn khi nội suy / ngoại suy các mảng lớn:

import numpy as np

def extrap(x, xp, yp):
    """np.interp function with linear extrapolation"""
    y = np.interp(x, xp, yp)
    y = np.where(x<xp[0], yp[0]+(x-xp[0])*(yp[0]-yp[1])/(xp[0]-xp[1]), y)
    y = np.where(x>xp[-1], yp[-1]+(x-xp[-1])*(yp[-1]-yp[-2])/(xp[-1]-xp[-2]), y)
    return y

x = np.arange(0,10)
y = np.exp(-x/3.0)
xtest = np.array((8.5,9.5))

print np.exp(-xtest/3.0)
print np.interp(xtest, x, y)
print extrap(xtest, x, y)

Chỉnh sửa: Sửa đổi đề xuất của Mark Mikofski đối với hàm "ngoại suy":

def extrap(x, xp, yp):
    """np.interp function with linear extrapolation"""
    y = np.interp(x, xp, yp)
    y[x < xp[0]] = yp[0] + (x[x<xp[0]]-xp[0]) * (yp[0]-yp[1]) / (xp[0]-xp[1])
    y[x > xp[-1]]= yp[-1] + (x[x>xp[-1]]-xp[-1])*(yp[-1]-yp[-2])/(xp[-1]-xp[-2])
    return y

2
+1 cho một ví dụ thực tế, nhưng bạn cũng có thể sử dụng lập chỉ mục booleanở đây y[x < xp[0]] = fp[0] + (x[x < xp[0]] - xp[0]) / (xp[1] - xp[0]) * (fp[1] - fp[0])y[x > xp[-1]] = fp[-1] + (x[x > xp[-1]] - xp[-1]) / (xp[-2] - xp[-1]) * (fp[-2] - fp[-1])thay vì np.where, vì Falsetùy chọn, ykhông thay đổi.
Mark Mikofski

6

Có thể nhanh hơn khi sử dụng lập chỉ mục boolean với các tập dữ liệu lớn , vì thuật toán kiểm tra xem mọi điểm có nằm ngoài khoảng thời gian hay không, trong khi lập chỉ mục boolean cho phép so sánh dễ dàng và nhanh hơn.

Ví dụ:

# Necessary modules
import numpy as np
from scipy.interpolate import interp1d

# Original data
x = np.arange(0,10)
y = np.exp(-x/3.0)

# Interpolator class
f = interp1d(x, y)

# Output range (quite large)
xo = np.arange(0, 10, 0.001)

# Boolean indexing approach

# Generate an empty output array for "y" values
yo = np.empty_like(xo)

# Values lower than the minimum "x" are extrapolated at the same time
low = xo < f.x[0]
yo[low] =  f.y[0] + (xo[low]-f.x[0])*(f.y[1]-f.y[0])/(f.x[1]-f.x[0])

# Values higher than the maximum "x" are extrapolated at same time
high = xo > f.x[-1]
yo[high] = f.y[-1] + (xo[high]-f.x[-1])*(f.y[-1]-f.y[-2])/(f.x[-1]-f.x[-2])

# Values inside the interpolation range are interpolated directly
inside = np.logical_and(xo >= f.x[0], xo <= f.x[-1])
yo[inside] = f(xo[inside])

Trong trường hợp của tôi, với tập dữ liệu 300000 điểm, điều này có nghĩa là tốc độ tăng từ 25,8 lên 0,094 giây, nhanh hơn 250 lần .


Điều này rất hay, nhưng nó không hoạt động nếu x0 là một float, nếu y [0] là np.nan, hoặc nếu y [-1] là np.nan.
Kéo dài

2

Tôi đã làm điều đó bằng cách thêm một điểm vào các mảng ban đầu của mình. Bằng cách này, tôi tránh xác định các hàm tự tạo và phép ngoại suy tuyến tính (trong ví dụ bên dưới: phép ngoại suy bên phải) có vẻ ổn.

import numpy as np  
from scipy import interp as itp  

xnew = np.linspace(0,1,51)  
x1=xold[-2]  
x2=xold[-1]  
y1=yold[-2]  
y2=yold[-1]  
right_val=y1+(xnew[-1]-x1)*(y2-y1)/(x2-x1)  
x=np.append(xold,xnew[-1])  
y=np.append(yold,right_val)  
f = itp(xnew,x,y)  

1

Tôi e rằng không dễ làm được điều này ở Scipy theo hiểu biết của tôi. Bạn có thể, như tôi khá chắc rằng bạn đã biết, tắt các lỗi giới hạn và điền vào tất cả các giá trị hàm ngoài phạm vi bằng một hằng số, nhưng điều đó không thực sự hữu ích. Xem câu hỏi này trong danh sách gửi thư để biết thêm một số ý tưởng. Có thể bạn có thể sử dụng một số loại chức năng mảnh, nhưng đó có vẻ là một vấn đề lớn.


Đó là kết luận mà tôi đi đến, ít nhất là với scipy 0.7, tuy nhiên hướng dẫn này được viết cách đây 21 tháng gợi ý rằng hàm interp1d có thuộc tính high và low có thể được đặt thành "tuyến tính", hướng dẫn này không rõ phiên bản scipy này áp dụng đối với: projects.scipy.org/scipy/browser/branches/Interpolate1D/docs/...
Salim Fadhley

Có vẻ như đây là một phần của nhánh chưa được tích hợp vào phiên bản chính nên có thể vẫn còn một số vấn đề với nó. Mã hiện tại cho điều này là tại websites.scipy.org/scipy/browser/bragets/interpolate/… mặc dù bạn có thể muốn cuộn xuống cuối trang và nhấp để tải xuống dưới dạng văn bản thuần túy. Tôi nghĩ rằng điều này có vẻ hứa hẹn mặc dù bản thân tôi chưa thử.
Justin Peel

1

Đoạn mã dưới đây cung cấp cho bạn mô-đun ngoại suy đơn giản. k là giá trị mà tập dữ liệu y phải được ngoại suy dựa trên tập dữ liệu x. Các numpymô-đun là bắt buộc.

 def extrapol(k,x,y):
        xm=np.mean(x);
        ym=np.mean(y);
        sumnr=0;
        sumdr=0;
        length=len(x);
        for i in range(0,length):
            sumnr=sumnr+((x[i]-xm)*(y[i]-ym));
            sumdr=sumdr+((x[i]-xm)*(x[i]-xm));

        m=sumnr/sumdr;
        c=ym-(m*xm);
        return((m*k)+c)

0

Nội suy tiêu chuẩn + ngoại suy tuyến tính:

    def interpola(v, x, y):
        if v <= x[0]:
            return y[0]+(y[1]-y[0])/(x[1]-x[0])*(v-x[0])
        elif v >= x[-1]:
            return y[-2]+(y[-1]-y[-2])/(x[-1]-x[-2])*(v-x[-2])
        else:
            f = interp1d(x, y, kind='cubic') 
            return f(v)

1
Này Federico! Nếu bạn thắc mắc tại sao bạn lại bị phản đối, hãy lưu ý rằng khi trả lời câu hỏi, bạn cần thực sự giải thích cách nó giải quyết vấn đề. Câu trả lời này, vì nó là, chỉ là một kết xuất mã và phải có ít nhất một vài câu giải thích tại sao và / hoặc nó hữu ích như thế nào. Cảm ơn!
Félix Gagnon-Grenier
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.