Обратно взвешенная по расстоянию (IDW) интерполяция с помощью Python

Вопрос: Как лучше всего рассчитать в Python интерполяцию с обратным взвешиванием по расстоянию (IDW) для местоположений точек?

Немного предыстории. В настоящее время я использую RPy2 для взаимодействия с R и его модулем gstat. К сожалению, модуль gstat конфликтует с arcgisscripting, который я получил, запустив анализ на основе RPy2 в отдельном процессе. Даже если эта проблема будет решена в недавнем / будущем выпуске и эффективность может быть улучшена, я все равно хотел бы удалить свою зависимость от установки R.

Веб-сайт gstat предоставляет автономный исполняемый файл, который легче упаковать с моим скриптом python, но я все еще надеюсь на решение Python, которое не требует многократной записи на диск и запуска внешних процессов. Количество вызовов функции интерполяции отдельных наборов точек и значений может приближаться к 20 000 при обработке, которую я выполняю.

Мне особенно нужно интерполировать точки, поэтому использование функции IDW в ArcGIS для генерации растровых звуков даже хуже, чем использование R, с точки зрения производительности ... если нет способа эффективно замаскировать только те точки, которые мне нужны. Даже с этой модификацией я бы не ожидал, что производительность будет такой большой. Я рассмотрю этот вариант как еще одну альтернативу. ОБНОВЛЕНИЕ: проблема в том, что вы привязаны к размеру используемой ячейки. Если вы уменьшите размер ячейки для повышения точности, обработка займет много времени. Вам также необходимо продолжить извлечение по точкам ... Уродливый метод, если вам нужны значения для определенных точек.

Я просмотрел документацию scipy, но похоже, что там нет прямой способ рассчитать IDW.

Я думаю о том, чтобы свернуть свою собственную реализацию, возможно, используя некоторые из функций scipy для определения ближайших точек и расчета расстояний.

Я упускаю что-то очевидное? Есть ли модуль Python, который я не видел, который делает именно то, что я хочу? Является ли создание моей собственной реализации с помощью scipy мудрым выбором?


person Michael Allan Jackson    schedule 23.06.2010    source источник


Ответы (2)


изменено 20 октября: этот класс Invdisttree сочетает в себе взвешивание с обратным расстоянием и scipy.spatial.KDTree.
Забудьте исходный ответ методом перебора; Это imho метод выбора для интерполяции разрозненных данных.

""" invdisttree.py: inverse-distance-weighted interpolation using KDTree
    fast, solid, local
"""
from __future__ import division
import numpy as np
from scipy.spatial import cKDTree as KDTree
    # http://docs.scipy.org/doc/scipy/reference/spatial.html

__date__ = "2010-11-09 Nov"  # weights, doc

#...............................................................................
class Invdisttree:
    """ inverse-distance-weighted interpolation using KDTree:
invdisttree = Invdisttree( X, z )  -- data points, values
interpol = invdisttree( q, nnear=3, eps=0, p=1, weights=None, stat=0 )
    interpolates z from the 3 points nearest each query point q;
    For example, interpol[ a query point q ]
    finds the 3 data points nearest q, at distances d1 d2 d3
    and returns the IDW average of the values z1 z2 z3
        (z1/d1 + z2/d2 + z3/d3)
        / (1/d1 + 1/d2 + 1/d3)
        = .55 z1 + .27 z2 + .18 z3  for distances 1 2 3

    q may be one point, or a batch of points.
    eps: approximate nearest, dist <= (1 + eps) * true nearest
    p: use 1 / distance**p
    weights: optional multipliers for 1 / distance**p, of the same shape as q
    stat: accumulate wsum, wn for average weights

How many nearest neighbors should one take ?
a) start with 8 11 14 .. 28 in 2d 3d 4d .. 10d; see Wendel's formula
b) make 3 runs with nnear= e.g. 6 8 10, and look at the results --
    |interpol 6 - interpol 8| etc., or |f - interpol*| if you have f(q).
    I find that runtimes don't increase much at all with nnear -- ymmv.

p=1, p=2 ?
    p=2 weights nearer points more, farther points less.
    In 2d, the circles around query points have areas ~ distance**2,
    so p=2 is inverse-area weighting. For example,
        (z1/area1 + z2/area2 + z3/area3)
        / (1/area1 + 1/area2 + 1/area3)
        = .74 z1 + .18 z2 + .08 z3  for distances 1 2 3
    Similarly, in 3d, p=3 is inverse-volume weighting.

Scaling:
    if different X coordinates measure different things, Euclidean distance
    can be way off.  For example, if X0 is in the range 0 to 1
    but X1 0 to 1000, the X1 distances will swamp X0;
    rescale the data, i.e. make X0.std() ~= X1.std() .

A nice property of IDW is that it's scale-free around query points:
if I have values z1 z2 z3 from 3 points at distances d1 d2 d3,
the IDW average
    (z1/d1 + z2/d2 + z3/d3)
    / (1/d1 + 1/d2 + 1/d3)
is the same for distances 1 2 3, or 10 20 30 -- only the ratios matter.
In contrast, the commonly-used Gaussian kernel exp( - (distance/h)**2 )
is exceedingly sensitive to distance and to h.

    """
# anykernel( dj / av dj ) is also scale-free
# error analysis, |f(x) - idw(x)| ? todo: regular grid, nnear ndim+1, 2*ndim

    def __init__( self, X, z, leafsize=10, stat=0 ):
        assert len(X) == len(z), "len(X) %d != len(z) %d" % (len(X), len(z))
        self.tree = KDTree( X, leafsize=leafsize )  # build the tree
        self.z = z
        self.stat = stat
        self.wn = 0
        self.wsum = None;

    def __call__( self, q, nnear=6, eps=0, p=1, weights=None ):
            # nnear nearest neighbours of each query point --
        q = np.asarray(q)
        qdim = q.ndim
        if qdim == 1:
            q = np.array([q])
        if self.wsum is None:
            self.wsum = np.zeros(nnear)

        self.distances, self.ix = self.tree.query( q, k=nnear, eps=eps )
        interpol = np.zeros( (len(self.distances),) + np.shape(self.z[0]) )
        jinterpol = 0
        for dist, ix in zip( self.distances, self.ix ):
            if nnear == 1:
                wz = self.z[ix]
            elif dist[0] < 1e-10:
                wz = self.z[ix[0]]
            else:  # weight z s by 1/dist --
                w = 1 / dist**p
                if weights is not None:
                    w *= weights[ix]  # >= 0
                w /= np.sum(w)
                wz = np.dot( w, self.z[ix] )
                if self.stat:
                    self.wn += 1
                    self.wsum += w
            interpol[jinterpol] = wz
            jinterpol += 1
        return interpol if qdim > 1  else interpol[0]

#...............................................................................
if __name__ == "__main__":
    import sys

    N = 10000
    Ndim = 2
    Nask = N  # N Nask 1e5: 24 sec 2d, 27 sec 3d on mac g4 ppc
    Nnear = 8  # 8 2d, 11 3d => 5 % chance one-sided -- Wendel, mathoverflow.com
    leafsize = 10
    eps = .1  # approximate nearest, dist <= (1 + eps) * true nearest
    p = 1  # weights ~ 1 / distance**p
    cycle = .25
    seed = 1

    exec "\n".join( sys.argv[1:] )  # python this.py N= ...
    np.random.seed(seed )
    np.set_printoptions( 3, threshold=100, suppress=True )  # .3f

    print "\nInvdisttree:  N %d  Ndim %d  Nask %d  Nnear %d  leafsize %d  eps %.2g  p %.2g" % (
        N, Ndim, Nask, Nnear, leafsize, eps, p)

    def terrain(x):
        """ ~ rolling hills """
        return np.sin( (2*np.pi / cycle) * np.mean( x, axis=-1 ))

    known = np.random.uniform( size=(N,Ndim) ) ** .5  # 1/(p+1): density x^p
    z = terrain( known )
    ask = np.random.uniform( size=(Nask,Ndim) )

#...............................................................................
    invdisttree = Invdisttree( known, z, leafsize=leafsize, stat=1 )
    interpol = invdisttree( ask, nnear=Nnear, eps=eps, p=p )

    print "average distances to nearest points: %s" % \
        np.mean( invdisttree.distances, axis=0 )
    print "average weights: %s" % (invdisttree.wsum / invdisttree.wn)
        # see Wikipedia Zipf's law
    err = np.abs( terrain(ask) - interpol )
    print "average |terrain() - interpolated|: %.2g" % np.mean(err)

    # print "interpolate a single point: %.2g" % \
    #     invdisttree( known[0], nnear=Nnear, eps=eps )
person denis    schedule 25.06.2010
comment
Денис, Раньше вы спрашивали, сколько у меня очков .... максимум у меня будет пара тысяч исходных точек, так что не о чем беспокоиться. Это очень помогло, спасибо! - person Michael Allan Jackson; 29.06.2010
comment
@majgis, пожалуйста. N = 100000 Nask = 100000 дублей ~ 24 секунды 2d, 27 секунд 3d, на моем старом Mac g4 ppc. (Для интерполяции двумерных данных в однородную сетку matplotlib.delaunay примерно в 10 раз быстрее - см. scipy .org / Cookbook / Matplotlib / Gridding_irregularly_spaced_data) - person denis; 30.06.2010
comment
См. Предупреждение здесь: IDW - это ужасный выбор почти в каждом случае .... Тем не менее, IDW может иметь разумное сочетание простоты, скорости и плавности для ваших данных. - person denis; 12.09.2012
comment
@denfromufa, creativecommons.org/licenses/by-nc-sa/3.0 некоммерческий. Это нормально ? - person denis; 26.07.2015
comment
@denis, то его нельзя интегрировать в scipy - person denfromufa; 27.07.2015
comment
Invsdisttree, похоже, хорошо работает для моего конкретного приложения, хотя мне нужно запустить его несколько раз, и это всегда вызывает перезапуск ядра. Есть причина для этого? По общему признанию, мои входные данные имеют длину 52800, но я не замечаю увеличения использования памяти или ЦП в диспетчере задач. - person mfgeng; 07.04.2021
comment
@mfgeng, понятия не имею. Какие у вас версии macos python scipy, вылетает ли он из-за меньшего количества точек / разных параметров? - person denis; 07.04.2021

Изменить: @Denis прав, линейный Rbf (например, scipy .interpolate.Rbf с "function = 'linear'") не то же самое, что IDW ...

(Учтите, что все они будут использовать чрезмерный объем памяти, если вы используете большое количество точек!)

Вот простой пример IDW:

def simple_idw(x, y, z, xi, yi):
    dist = distance_matrix(x,y, xi,yi)

    # In IDW, weights are 1 / distance
    weights = 1.0 / dist

    # Make weights sum to one
    weights /= weights.sum(axis=0)

    # Multiply the weights for each interpolated point by all observed Z-values
    zi = np.dot(weights.T, z)
    return zi

В то время как вот что было бы линейным Rbf:

def linear_rbf(x, y, z, xi, yi):
    dist = distance_matrix(x,y, xi,yi)

    # Mutual pariwise distances between observations
    internal_dist = distance_matrix(x,y, x,y)

    # Now solve for the weights such that mistfit at the observations is minimized
    weights = np.linalg.solve(internal_dist, z)

    # Multiply the weights for each interpolated point by the distances
    zi =  np.dot(dist.T, weights)
    return zi

(Здесь используется функция distance_matrix :)

def distance_matrix(x0, y0, x1, y1):
    obs = np.vstack((x0, y0)).T
    interp = np.vstack((x1, y1)).T

    # Make a distance matrix between pairwise observations
    # Note: from <http://stackoverflow.com/questions/1871536>
    # (Yay for ufuncs!)
    d0 = np.subtract.outer(obs[:,0], interp[:,0])
    d1 = np.subtract.outer(obs[:,1], interp[:,1])

    return np.hypot(d0, d1)

Объединение всего этого в хороший пример копирования и вставки дает несколько сравнительных графиков:  Самодельный пример сюжета IDW
(источник: jkington на www.geology.wisc.edu)
 Пример самодельного линейного графика RBF
(источник: jkington на www.geology.wisc.edu)
 Пример графика линейного RBF Scipy
(источник: jkington на www.geology.wisc.edu)

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

def main():
    # Setup: Generate data...
    n = 10
    nx, ny = 50, 50
    x, y, z = map(np.random.random, [n, n, n])
    xi = np.linspace(x.min(), x.max(), nx)
    yi = np.linspace(y.min(), y.max(), ny)
    xi, yi = np.meshgrid(xi, yi)
    xi, yi = xi.flatten(), yi.flatten()

    # Calculate IDW
    grid1 = simple_idw(x,y,z,xi,yi)
    grid1 = grid1.reshape((ny, nx))

    # Calculate scipy's RBF
    grid2 = scipy_idw(x,y,z,xi,yi)
    grid2 = grid2.reshape((ny, nx))

    grid3 = linear_rbf(x,y,z,xi,yi)
    print grid3.shape
    grid3 = grid3.reshape((ny, nx))


    # Comparisons...
    plot(x,y,z,grid1)
    plt.title('Homemade IDW')

    plot(x,y,z,grid2)
    plt.title("Scipy's Rbf with function=linear")

    plot(x,y,z,grid3)
    plt.title('Homemade linear Rbf')

    plt.show()

def simple_idw(x, y, z, xi, yi):
    dist = distance_matrix(x,y, xi,yi)

    # In IDW, weights are 1 / distance
    weights = 1.0 / dist

    # Make weights sum to one
    weights /= weights.sum(axis=0)

    # Multiply the weights for each interpolated point by all observed Z-values
    zi = np.dot(weights.T, z)
    return zi

def linear_rbf(x, y, z, xi, yi):
    dist = distance_matrix(x,y, xi,yi)

    # Mutual pariwise distances between observations
    internal_dist = distance_matrix(x,y, x,y)

    # Now solve for the weights such that mistfit at the observations is minimized
    weights = np.linalg.solve(internal_dist, z)

    # Multiply the weights for each interpolated point by the distances
    zi =  np.dot(dist.T, weights)
    return zi


def scipy_idw(x, y, z, xi, yi):
    interp = Rbf(x, y, z, function='linear')
    return interp(xi, yi)

def distance_matrix(x0, y0, x1, y1):
    obs = np.vstack((x0, y0)).T
    interp = np.vstack((x1, y1)).T

    # Make a distance matrix between pairwise observations
    # Note: from <http://stackoverflow.com/questions/1871536>
    # (Yay for ufuncs!)
    d0 = np.subtract.outer(obs[:,0], interp[:,0])
    d1 = np.subtract.outer(obs[:,1], interp[:,1])

    return np.hypot(d0, d1)


def plot(x,y,z,grid):
    plt.figure()
    plt.imshow(grid, extent=(x.min(), x.max(), y.max(), y.min()))
    plt.hold(True)
    plt.scatter(x,y,c=z)
    plt.colorbar()

if __name__ == '__main__':
    main()
person Joe Kington    schedule 24.06.2010
comment
function = linear - это r, а не 1 / r. (Имеет ли это значение? Существует полдюжины вариантов function =, совершенно разных - некоторые подходят для некоторых данных.) - person denis; 25.06.2010
comment
@Denis: он использует 1 / function () для взвешивания вещей. В этом отношении документы могли бы быть более ясными, но по-другому это не имело бы смысла. В противном случае точки, расположенные дальше от интерполированной точки, будут иметь больший вес, а интерполированные значения рядом с определенной точкой будут иметь значение, наиболее близкое к наиболее удаленным точкам. Выбор функции имеет значение (очень много!), И IDW обычно оказывается неправильным выбором. Однако цель состоит в том, чтобы получить результаты, которые кажутся разумными человеку, выполняющему интерполяцию, поэтому, если IDW работает, он работает. В любом случае Rbf Scipy не дает вам особой гибкости. - person Joe Kington; 25.06.2010
comment
@joe, используя en.wikipedia.org/wiki/Radial_basis_function для обозначения: phi (r) Гауссово и обратно-мультиквадратичное убывают с расстоянием, остальные увеличиваются ?! Rbf решает Aw = z, чтобы быть точным в cj (коэффициенты w могут сильно различаться, выведите rbf.A rbf.nodes); тогда для phi (r) = r, y (x) = sum (wj | x - cj |), странно. Немного опубликуем (mho of) взвешивание обратного расстояния, тогда мы сможем сравнить - person denis; 25.06.2010
comment
@Joe, приятное сравнение и сюжеты, ++. Как насчет того, чтобы сделать отдельную заметку где-нибудь о преимуществах и недостатках RBF? - person denis; 27.06.2010
comment
@Denis, спасибо! На днях сделаю, но придется подождать, пока не закончатся отборочные. (Аспирантура - это немного отнимает время!) Между прочим, я полагаю, что это вы прислали мне электронное письмо с интерполяционной тирадой относительно обсуждения в scipy списке рассылки некоторое время назад. Извините, я так и не ответил! Точно так же на днях я напишу сообщение в блоге или что-то подобное ... - person Joe Kington; 28.06.2010
comment
Ссылки на ваши участки сейчас (6 лет спустя, ОК :-) не работают. - person gboffi; 15.11.2016
comment
Большое Вам спасибо. Я перенес ваш код на Ruby и изменил функцию расстояния, чтобы она работала так, как будто X и Y - это широта и долгота. Полезно для интерполяции поверхности земли. Вот суть: gist.github.com/alexandremcosta/ - person alexandrecosta; 08.05.2018
comment
возможно, вы можете добавить полный пример из шейп-файла или чего-то еще, используя геопанды, и отобразить его как тепловую карту? - person steven; 30.06.2019
comment
как получить экстент созданного изображения для размещения в качестве слоя наложения на карте. Я попытался использовать размер точек, но проблема с выравниванием осталась. - person Anju; 21.05.2021