Интерполировать значения NaN в массиве NumPy - PullRequest
53 голосов
/ 29 июня 2011

Существует ли быстрый способ замены всех значений NaN в массиве с числами на (скажем) линейно интерполированные значения?

Например,

[1 1 1 nan nan 2 2 nan 0]

будет преобразовано в

[1 1 1 1.3 1.6 2 2  1  0]

Ответы [ 9 ]

86 голосов
/ 29 июня 2011

Давайте сначала определим простую вспомогательную функцию, чтобы упростить обработку индексов и логических индексов NaNs :

import numpy as np

def nan_helper(y):
    """Helper to handle indices and logical indices of NaNs.

    Input:
        - y, 1d numpy array with possible NaNs
    Output:
        - nans, logical indices of NaNs
        - index, a function, with signature indices= index(logical_indices),
          to convert logical indices of NaNs to 'equivalent' indices
    Example:
        >>> # linear interpolation of NaNs
        >>> nans, x= nan_helper(y)
        >>> y[nans]= np.interp(x(nans), x(~nans), y[~nans])
    """

    return np.isnan(y), lambda z: z.nonzero()[0]

Теперь можно использовать nan_helper(.)как:

>>> y= array([1, 1, 1, NaN, NaN, 2, 2, NaN, 0])
>>>
>>> nans, x= nan_helper(y)
>>> y[nans]= np.interp(x(nans), x(~nans), y[~nans])
>>>
>>> print y.round(2)
[ 1.    1.    1.    1.33  1.67  2.    2.    1.    0.  ]

---
Хотя сначала может показаться немного излишним указывать отдельную функцию для выполнения таких вещей, как это:

>>> nans, x= np.isnan(y), lambda z: z.nonzero()[0]

это в конечном итоге принесет дивиденды.

Итак, всякий раз, когда вы работаете с данными, относящимися к NaN, просто инкапсулируйте все необходимые (новые, связанные с NaN) функциональные возможности в рамках некоторых определенных вспомогательных функций.Ваша кодовая база будет более понятной и удобочитаемой, поскольку она следует понятным идиомам.

Интерполяция, действительно, является хорошим контекстом, чтобы увидеть, как осуществляется обработка NaN, но аналогичные методы используются и в различных других контекстах.

22 голосов
/ 29 июня 2011

Я придумал этот код:

import numpy as np
nan = np.nan

A = np.array([1, nan, nan, 2, 2, nan, 0])

ok = -np.isnan(A)
xp = ok.ravel().nonzero()[0]
fp = A[-np.isnan(A)]
x  = np.isnan(A).ravel().nonzero()[0]

A[np.isnan(A)] = np.interp(x, xp, fp)

print A

печатает

 [ 1.          1.33333333  1.66666667  2.          2.          1.          0.        ]
8 голосов
/ 22 марта 2012

Просто используйте NumPy Logical и оператор where для применения одномерной интерполяции.

import numpy as np
from scipy import interpolate

def fill_nan(A):
    '''
    interpolate to fill nan values
    '''
    inds = np.arange(A.shape[0])
    good = np.where(np.isfinite(A))
    f = interpolate.interp1d(inds[good], A[good],bounds_error=False)
    B = np.where(np.isfinite(A),A,f(inds))
    return B
5 голосов
/ 29 июня 2011

Может быть проще изменить способ генерации данных, но если нет:

bad_indexes = np.isnan(data)

Создать логический массив, указывающий, где находятся nans

good_indexes = np.logical_not(bad_indexes)

Создайте логический массив, указывающий, где находится область хороших значений

good_data = data[good_indexes]

Ограниченная версия исходных данных, исключая nans

interpolated = np.interp(bad_indexes.nonzero(), good_indexes.nonzero(), good_data)

Запуск всех неверных индексов с помощью интерполяции

data[bad_indexes] = interpolated

Заменить исходные данные интерполированными значениями.

4 голосов
/ 22 августа 2012

Или опираясь на ответ Уинстона

def pad(data):
    bad_indexes = np.isnan(data)
    good_indexes = np.logical_not(bad_indexes)
    good_data = data[good_indexes]
    interpolated = np.interp(bad_indexes.nonzero()[0], good_indexes.nonzero()[0], good_data)
    data[bad_indexes] = interpolated
    return data

A = np.array([[1, 20, 300],
              [nan, nan, nan],
              [3, 40, 500]])

A = np.apply_along_axis(pad, 0, A)
print A

Результат

[[   1.   20.  300.]
 [   2.   30.  400.]
 [   3.   40.  500.]]
3 голосов
/ 30 августа 2016

Мне нужен подход, который бы также заполнял значения NaN в начале и конце данных, чего, как представляется, основной ответ не делает.

Приведенная мной функция использует линейную регрессию дляв NaN.Это решает мою проблему:

import numpy as np

def linearly_interpolate_nans(y):
    # Fit a linear regression to the non-nan y values

    # Create X matrix for linreg with an intercept and an index
    X = np.vstack((np.ones(len(y)), np.arange(len(y))))

    # Get the non-NaN values of X and y
    X_fit = X[:, ~np.isnan(y)]
    y_fit = y[~np.isnan(y)].reshape(-1, 1)

    # Estimate the coefficients of the linear regression
    beta = np.linalg.lstsq(X_fit.T, y_fit)[0]

    # Fill in all the nan values using the predicted coefficients
    y.flat[np.isnan(y)] = np.dot(X[:, np.isnan(y)].T, beta)
    return y

Вот пример использования:

# Make an array according to some linear function
y = np.arange(12) * 1.5 + 10.

# First and last value are NaN
y[0] = np.nan
y[-1] = np.nan

# 30% of other values are NaN
for i in range(len(y)):
    if np.random.rand() > 0.7:
        y[i] = np.nan

# NaN's are filled in!
print (y)
print (linearly_interpolate_nans(y))
3 голосов
/ 17 июня 2016

Для двумерных данных SciPy's griddata работает довольно хорошо для меня:

>>> import numpy as np
>>> from scipy.interpolate import griddata
>>>
>>> # SETUP
>>> a = np.arange(25).reshape((5, 5)).astype(float)
>>> a
array([[  0.,   1.,   2.,   3.,   4.],
       [  5.,   6.,   7.,   8.,   9.],
       [ 10.,  11.,  12.,  13.,  14.],
       [ 15.,  16.,  17.,  18.,  19.],
       [ 20.,  21.,  22.,  23.,  24.]])
>>> a[np.random.randint(2, size=(5, 5)).astype(bool)] = np.NaN
>>> a
array([[ nan,  nan,  nan,   3.,   4.],
       [ nan,   6.,   7.,  nan,  nan],
       [ 10.,  nan,  nan,  13.,  nan],
       [ 15.,  16.,  17.,  nan,  19.],
       [ nan,  nan,  22.,  23.,  nan]])
>>>
>>> # THE INTERPOLATION
>>> x, y = np.indices(a.shape)
>>> interp = np.array(a)
>>> interp[np.isnan(interp)] = griddata(
...     (x[~np.isnan(a)], y[~np.isnan(a)]), # points we know
...     a[~np.isnan(a)],                    # values we know
...     (x[np.isnan(a)], y[np.isnan(a)]))   # points to interpolate
>>> interp
array([[ nan,  nan,  nan,   3.,   4.],
       [ nan,   6.,   7.,   8.,   9.],
       [ 10.,  11.,  12.,  13.,  14.],
       [ 15.,  16.,  17.,  18.,  19.],
       [ nan,  nan,  22.,  23.,  nan]])

Я использую его на 3D-изображениях, работая на 2D-срезах (4000 срезов 350x350). Вся операция по-прежнему занимает около часа: /

2 голосов
/ 20 сентября 2016

Опираясь на ответ Брайана Вудса , я изменил его код, чтобы также преобразовывать списки, состоящие только из NaN, в список нулей:

def fill_nan(A):
    '''
    interpolate to fill nan values
    '''
    inds = np.arange(A.shape[0])
    good = np.where(np.isfinite(A))
    if len(good[0]) == 0:
        return np.nan_to_num(A)
    f = interp1d(inds[good], A[good], bounds_error=False)
    B = np.where(np.isfinite(A), A, f(inds))
    return B

Простое дополнение, надеюсь, оно кому-нибудь пригодится.

1 голос
/ 29 октября 2018

Слегка оптимизированная версия на основе ответа BRYAN WOODS .Он правильно обрабатывает начальные и конечные значения исходных данных и работает на 25-30% быстрее, чем оригинальная версия.Также вы можете использовать различные виды интерполяции (подробности см. В документации scipy.interpolate.interp1d).

import numpy as np
from scipy.interpolate import interp1d

def fill_nans_scipy1(padata, pkind='linear'):
"""
Interpolates data to fill nan values

Parameters:
    padata : nd array 
        source data with np.NaN values

Returns:
    nd array 
        resulting data with interpolated values instead of nans
"""
aindexes = np.arange(padata.shape[0])
agood_indexes, = np.where(np.isfinite(padata))
f = interp1d(agood_indexes
           , padata[agood_indexes]
           , bounds_error=False
           , copy=False
           , fill_value="extrapolate"
           , kind=pkind)
return f(aindexes)
...