Як я можу виконати двовимірну інтерполяцію за допомогою scipy?


105

Ця запитання розроблена як канонічна (-іш) щодо двовимірної (і багатовимірної) інтерполяції з використанням scipy. Часто виникають питання, що стосуються основного синтаксису різних багатовимірних методів інтерполяції, я сподіваюсь, що їх також можна зрозуміти.

У мене є набір розсіяних двовимірних точок даних, і я хотів би побудувати їх як приємну поверхню, бажано використовувати щось на зразок contourfабо plot_surfaceв matplotlib.pyplot. Як я можу інтерполювати свої двовимірні або багатовимірні дані в сітку за допомогою scipy?

Я знайшов scipy.interpolateсуб-пакет, але я отримую помилки при використанні interp2dабо bisplrepабо griddataабо rbf. Який власний синтаксис цих методів?

Відповіді:


163

Відмова: Я переважно пишу цей пост з синтаксичними міркуваннями та загальною поведінкою. Я не знайомий із аспектом пам’яті та процесора описаних методів, і я націлюю цю відповідь на тих, хто має досить малі набори даних, так що якість інтерполяції може бути головним аспектом, який слід враховувати. Я усвідомлюю, що при роботі з дуже великими наборами даних, ефективніші методи (а саме griddataі Rbf) можуть бути неможливими.

Я збираюся порівняти три види методів багатовимірної інтерполяції ( interp2d/ сплайни griddataта Rbf). Я підпорядкую їм два види завдань інтерполяції та два види базових функцій (пункти, з яких потрібно інтерполювати). Конкретні приклади демонструють двовимірну інтерполяцію, але життєздатні методи застосовні у довільних розмірах. Кожен метод забезпечує різні види інтерполяції; у всіх випадках я буду використовувати кубічну інтерполяцію (або щось близьке 1 ). Важливо зауважити, що щоразу, коли ви використовуєте інтерполяцію, ви вводите упередженість у порівнянні з вашими необробленими даними, а конкретні використовувані методи впливають на артефакти, якими ви в кінцевому підсумку. Завжди пам’ятайте про це та відповідально інтерполюйте.

Дві завдання інтерполяції будуть

  1. помірний вибір (вхідні дані є прямокутною сіткою, вихідні дані - щільнішою сіткою)
  2. інтерполяція розпорошених даних на звичайну сітку

Дві функції (над доменом [x,y] in [-1,1]x[-1,1]) будуть

  1. плавна і доброзичлива функція cos(pi*x)*sin(pi*y):; діапазон в[-1, 1]
  2. зла (і, зокрема, неперервна) функція: x*y/(x^2+y^2)зі значенням 0,5 біля початку; діапазон в[-0.5, 0.5]

Ось як вони виглядають:

fig1: тестові функції

Спочатку я продемонструю, як три методи поводяться під цими чотирма тестами, потім детально опишу синтаксис усіх трьох. Якщо ви знаєте, чого слід очікувати від методу, можливо, ви не хочете витрачати свій час на вивчення його синтаксису (дивлячись на вас, interp2d).

Дані тесту

Для наочності ось код, за допомогою якого я створив вхідні дані. Хоча в цьому конкретному випадку я, очевидно, знаю функцію, що лежить в основі даних, я буду використовувати це лише для отримання вводу для методів інтерполяції. Я використовую numpy для зручності (і в основному для генерації даних), але й науки однієї теж вистачить.

import numpy as np
import scipy.interpolate as interp

# auxiliary function for mesh generation
def gimme_mesh(n):
    minval = -1
    maxval =  1
    # produce an asymmetric shape in order to catch issues with transpositions
    return np.meshgrid(np.linspace(minval,maxval,n), np.linspace(minval,maxval,n+1))

# set up underlying test functions, vectorized
def fun_smooth(x, y):
    return np.cos(np.pi*x)*np.sin(np.pi*y)

def fun_evil(x, y):
    # watch out for singular origin; function has no unique limit there
    return np.where(x**2+y**2>1e-10, x*y/(x**2+y**2), 0.5)

# sparse input mesh, 6x7 in shape
N_sparse = 6
x_sparse,y_sparse = gimme_mesh(N_sparse)
z_sparse_smooth = fun_smooth(x_sparse, y_sparse)
z_sparse_evil = fun_evil(x_sparse, y_sparse)

# scattered input points, 10^2 altogether (shape (100,))
N_scattered = 10
x_scattered,y_scattered = np.random.rand(2,N_scattered**2)*2 - 1
z_scattered_smooth = fun_smooth(x_scattered, y_scattered)
z_scattered_evil = fun_evil(x_scattered, y_scattered)

# dense output mesh, 20x21 in shape
N_dense = 20
x_dense,y_dense = gimme_mesh(N_dense)

Плавне функціонування та покращення рівня

Почнемо з найпростішого завдання. Ось як розгортається перегляд від сітки форми [6,7]до однієї з [20,21]функцій плавного тестування:

fig2: плавне перетворення

Незважаючи на те, що це просте завдання, між результатами вже є тонкі відмінності. На перший погляд усі три виходи розумні. Зазначимо дві особливості, засновані на наших попередніх знаннях основної функції: середній випадок найбільше griddataспотворює дані. Зверніть увагу на y==-1межу ділянки (найближчий до xмітки): функція повинна бути суворо нульовою (оскільки y==-1це вузлова лінія для гладкої функції), але це не так griddata. Також зверніть увагу на x==-1межу ділянок (позаду, зліва): основна функція має локальний максимум (маючи на увазі нульовий градієнт поблизу кордону) при [-1, -0.5], але griddataвихід показує явно ненульовий градієнт у цій області. Ефект тонкий, але все-таки упередження. (ВірністьRbfще краще, якщо за замовчуванням вибір радіальних функцій, прозваний multiquadratic.)

Зла функція та перемогу

Трохи складніше завдання полягає в тому, щоб здійснити перемогу над нашою злою функцією:

fig3: злий перебіг

Ясні відмінності починають проявлятися серед трьох методів. Дивлячись на поверхневі ділянки, є чіткі хибні екстремуми, що з'являються на виході з interp2d(зверніть увагу на два горбики з правого боку ділянки поверхні). Хоча griddataі, Rbfздається, дає подібні результати на перший погляд, останній, здається, дає глибший мінімум поблизу [0.4, -0.4], відсутній у основної функції.

Однак є один важливий аспект, в якому Rbfнабагато вищий: він поважає симетрію основної функції (що, звичайно, також стає можливим завдяки симетрії вибіркової сітки). Вихід з griddataпорушення порушує симетрію точок вибірки, що вже слабко видно в гладкому випадку.

Плавна функція та розсіяні дані

Найчастіше хочеться виконати інтерполяцію на розсіяних даних. З цієї причини я очікую, що ці тести будуть важливішими. Як показано вище, вибіркові бали були обрані псевдо-рівномірно в області, що цікавить. У реалістичних сценаріях у вас може виникнути додатковий шум при кожному вимірюванні, і ви повинні врахувати, чи є сенс інтерполювати ваші вихідні дані для початку.

Вихід для плавної функції:

fig4: гладка розсіяна інтерполяція

Зараз вже триває шоу жахів. Я вирізав вихід interp2dміж ними [-1, 1]виключно для побудови графіків, щоб зберегти хоча б мінімальну кількість інформації. Зрозуміло, що хоча деякі основні форми присутні, є величезні галасливі регіони, де метод повністю руйнується. Другий випадок griddataвідтворює форму досить гарно, але відзначте білі області на межі контуру ділянки. Це пов’язано з тим, що griddataпрацює тільки всередині опуклого корпусу точок вхідних даних (іншими словами, він не виконує жодної екстраполяції ). Я зберігав значення NaN за замовчуванням для вихідних точок, що лежать поза опуклим корпусом. 2 Враховуючи ці особливості, Rbfздається, найкраще.

Зла функція і розпорошені дані

І момент, якого ми всі чекали:

fig5: зла розсіяна інтерполяція

Це не величезний сюрприз, який interp2dздається. Насправді, під час дзвінка до interp2dвас слід очікувати дружнього RuntimeWarningпоскарження на неможливість побудови сплайну. Щодо інших двох методів, Rbfсхоже, це дає найкращий результат навіть біля меж домену, де результат екстраполюється.


Тож дозвольте мені сказати кілька слів про три методи у зменшенні порядку уподобань (так що найгірший є найменшим, який може прочитати хтось).

scipy.interpolate.Rbf

RbfКласу означає «радіальні базисні функції». Якщо чесно, я ніколи не розглядав цей підхід, поки не почав досліджувати цю посаду, але я впевнений, що буду використовувати це в майбутньому.

Як і методи, засновані на сплайні (див. Далі), використання відбувається у два етапи: спочатку створюється Rbfекземпляр класу, що викликається на основі вхідних даних, а потім викликає цей об'єкт для заданої вихідної сітки для отримання інтерпольованого результату. Приклад з тесту на плавне перетворення:

import scipy.interpolate as interp
zfun_smooth_rbf = interp.Rbf(x_sparse, y_sparse, z_sparse_smooth, function='cubic', smooth=0)  # default smooth=0 for interpolation
z_dense_smooth_rbf = zfun_smooth_rbf(x_dense, y_dense)  # not really a function, but a callable class instance

Зауважте, що і вхідні, і вихідні точки в цьому випадку були 2d масивами, а вихід z_dense_smooth_rbfмає таку ж форму, що x_denseі y_denseбез будь-яких зусиль. Також зверніть увагу, що Rbfпідтримує довільні розміри для інтерполяції.

Так, scipy.interpolate.Rbf

  • дає хороший результат навіть для шалених вхідних даних
  • підтримує інтерполяцію у більш високих розмірах
  • екстраполяти поза опуклим корпусом вхідних точок (звичайно, екстраполяція - це завжди азартна гра, і взагалі на неї взагалі не варто покладатися)
  • створює інтерполятор як перший крок, тому оцінювати його в різних вихідних точках - менше додаткових зусиль
  • можуть мати точки виведення довільної форми (на відміну від обмежених прямокутними сітками, див. далі)
  • схильний до збереження симетрії вхідних даних
  • підтримує декілька видів радіальних функцій для ключового слова function: multiquadric, inverse, gaussian, linear, cubic, quintic, thin_plateі визначені користувачем довільно

scipy.interpolate.griddata

Мій колишній фаворит, griddata- це загальна робоча коня для інтерполяції у довільних вимірах. Він не виконує екстраполяцію, крім встановлення єдиного заданого значення для точок поза опуклим корпусом вузлових точок, але оскільки екстраполяція є дуже непостійною і небезпечною річчю, це не обов'язково є кон. Приклад використання:

z_dense_smooth_griddata = interp.griddata(np.array([x_sparse.ravel(),y_sparse.ravel()]).T,
                                          z_sparse_smooth.ravel(),
                                          (x_dense,y_dense), method='cubic')   # default method is linear

Зверніть увагу на злегка хиткий синтаксис. Точки введення повинні бути вказані у масиві форм [N, D]за Dрозмірами. Для цього ми спочатку повинні згладити наші 2d масиви координат (використовуючи ravel), потім об'єднати масиви і перенести результат. Існує кілька способів зробити це, але всі вони здаються громіздкими. Вхідні zдані також повинні бути вирівняними. У нас є трохи більше свободи, коли йдеться про вихідні точки: їх чомусь можна також вказати як кордон багатовимірних масивів. Слід зазначити , що helpз griddataвводить в оману, оскільки це передбачає , що те ж саме справедливо і для вхідних точок ( по крайней мере , для версії 0.17.0):

griddata(points, values, xi, method='linear', fill_value=nan, rescale=False)
    Interpolate unstructured D-dimensional data.

    Parameters
    ----------
    points : ndarray of floats, shape (n, D)
        Data point coordinates. Can either be an array of
        shape (n, D), or a tuple of `ndim` arrays.
    values : ndarray of float or complex, shape (n,)
        Data values.
    xi : ndarray of float, shape (M, D)
        Points at which to interpolate data.

Коротко, scipy.interpolate.griddata

  • дає хороший результат навіть для шалених вхідних даних
  • підтримує інтерполяцію у більш високих розмірах
  • не виконує екстраполяцію, для виходу за межі опуклого корпусу вхідних точок можна встановити одне значення (див. fill_value)
  • обчислює інтерпольовані значення в одному виклику, тому зондування декількох наборів точок виводу починається з нуля
  • можуть мати точки виведення довільної форми
  • підтримує найближчу сусідню і лінійну інтерполяцію в довільних розмірах, кубічних в 1d і 2d. Використання найближчої сусідки та лінійної інтерполяції NearestNDInterpolatorта LinearNDInterpolatorпід кришкою відповідно. 1d кубічна інтерполяція використовує сплайн, 2d кубічна інтерполяція використовує CloughTocher2DInterpolatorдля побудови безперервно диференційованого кусочно-кубічного інтерполятора.
  • може порушити симетрію вхідних даних

scipy.interpolate.interp2d/scipy.interpolate.bisplrep

Єдина причина, яку я обговорюю interp2dта її родичів, - це те, що вона має оманливе ім’я, і люди, ймовірно, намагаються його використовувати. Попередження спойлера: не використовуйте його (станом на scipy версії 0.17.0). Це вже більш особливе, ніж попередні теми, оскільки воно спеціально використовується для двовимірної інтерполяції, але я підозрюю, що це, безумовно, найпоширеніший випадок багатоваріантної інтерполяції.

Що стосується синтаксису, interp2dвін схожий на Rbfте, що спочатку потрібно побудувати екземпляр інтерполяції, який можна викликати для надання фактичних інтерпольованих значень. Однак є привід: точки виведення повинні розташовуватися на прямокутній сітці, тому входи, що надходять у виклик інтерполятора, повинні бути 1d векторами, що охоплюють вихідну сітку, як би від numpy.meshgrid:

# reminder: x_sparse and y_sparse are of shape [6, 7] from numpy.meshgrid
zfun_smooth_interp2d = interp.interp2d(x_sparse, y_sparse, z_sparse_smooth, kind='cubic')   # default kind is 'linear'
# reminder: x_dense and y_dense are of shape [20, 21] from numpy.meshgrid
xvec = x_dense[0,:] # 1d array of unique x values, 20 elements
yvec = y_dense[:,0] # 1d array of unique y values, 21 elements
z_dense_smooth_interp2d = zfun_smooth_interp2d(xvec,yvec)   # output is [20, 21]-shaped array

Однією з найпоширеніших помилок при використанні interp2dє введення повних 2d-сіток в інтерполяційний дзвінок, що призводить до вибухонебезпечного споживання пам’яті та, сподіваємось, до поспішності MemoryError.

Зараз найбільшою проблемою interp2dє те, що вона часто не працює. Щоб зрозуміти це, нам треба заглянути під капот. Виявляється, interp2dце обгортка для функцій нижчого рівня bisplrep+ bisplev, які в свою чергу є обгортками для підпрограм FITPACK (написано у Fortran). Еквівалентний дзвінок до попереднього прикладу був би

kind = 'cubic'
if kind=='linear':
    kx=ky=1
elif kind=='cubic':
    kx=ky=3
elif kind=='quintic':
    kx=ky=5
# bisplrep constructs a spline representation, bisplev evaluates the spline at given points
bisp_smooth = interp.bisplrep(x_sparse.ravel(),y_sparse.ravel(),z_sparse_smooth.ravel(),kx=kx,ky=ky,s=0)
z_dense_smooth_bisplrep = interp.bisplev(xvec,yvec,bisp_smooth).T  # note the transpose

Тепер ось про що interp2d: (у scipy версії 0.17.0) є приємний коментарinterpolate/interpolate.py для interp2d:

if not rectangular_grid:
    # TODO: surfit is really not meant for interpolation!
    self.tck = fitpack.bisplrep(x, y, z, kx=kx, ky=ky, s=0.0)

і справді в interpolate/fitpack.py, bisplrepє деяка настройка і в кінцевому рахунку

tx, ty, c, o = _fitpack._surfit(x, y, z, w, xb, xe, yb, ye, kx, ky,
                                task, s, eps, tx, ty, nxest, nyest,
                                wrk, lwrk1, lwrk2)                 

І це все. Процедури, що лежать interp2dв основі , насправді не призначені для здійснення інтерполяції. Їх може вистачити для достатньо добре сприйнятих даних, але за реальних обставин ви, ймовірно, захочете скористатися чимось іншим.

Для завершення, interpolate.interp2d

  • може призвести до артефактів навіть при добре загартованих даних
  • спеціально для двовимірних проблем (хоча interpnдля вхідних точок, визначених у сітці, обмежено )
  • виконує екстраполяцію
  • створює інтерполятор як перший крок, тому оцінювати його в різних вихідних точках - менше додаткових зусиль
  • може створювати вихід лише через прямокутну сітку, для розсіяного виходу вам доведеться викликати інтерполятор у циклі
  • підтримує лінійну, кубічну та квінтичну інтерполяцію
  • може порушити симетрію вхідних даних

1 Я абсолютно впевнений , що cubicі linearвид базисних функцій , Rbfнеточно відповідають до інших інтерполятора одного і того ж імені.
2 Ці NaN також є причиною того, що ділянка поверхні виглядає настільки дивним: matplotlib історично має труднощі з побудовою складних 3d-об'єктів з належною інформацією про глибину. Значення NaN у даних плутають рендерінг, тому частини поверхні, які повинні знаходитись іззаду, намічаються так, щоб вони були спереду. Це питання візуалізації, а не інтерполяції.


2
Rbf може споживати більше пам'яті, ніж griddata, залежно від кількості точок даних та кількості вимірів. Також griddata має базовий об'єкт LinearNDInterpolator, який можна використовувати як Rbf у 2 етапи.
denfromufa

1
Кубічна інтерполяція Гріддата обмежена 2 (?) Розмірами. Для більш високих розмірів варто розглянути смоляні розріджені сітки на основі шабфуну.
denfromufa

1
дозвольте мені закінчити свої коментарі за цим посиланням, де я досліджував усі розсіяні варіанти інтерполяції: scicomp.stackexchange.com/questions/19137/…
denfromufa

4
лінійна інтерполяція griddata локальна, кубічна інтерполяція griddata - глобальна. Екстраполяція не підтримується, тому що я не встиг зрозуміти, як зберегти наступність / диференційованість. Rbf добре для невеликих наборів даних, але для інтерполяції n точок даних потрібно інвертувати матрицю nxn, що з часом стає неможливим після n> 5000. Rbf також може бути чутливим до розподілу даних, і вам може знадобитися точно налаштувати його параметри вручну. Можна робити Rbf для великих наборів даних, але це не реалізовано в scipy.
пв.

1
Ось rbf для великих наборів даних: github.com/scipy/scipy/isissue/5180
denfromufa
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.