Интерполировать значения NaN в массиве numpy

Есть ли быстрый способ заменить все значения NaN в массиве numpy (скажем) линейно интерполированными значениями?

Например,

[1 1 1 nan nan 2 2 nan 0]

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

[1 1 1 1.3 1.6 2 2  1  0]

person Petter    schedule 29.06.2011    source источник
comment
Прошу прощения за то, что написал в старую ветку, но я думаю, что это стоит путаницы. Более простой способ - использовать pandas и numpy: pd.DataFrame([1, 3, 4, np.nan, 6]).interpolate().values.ravel().tolist()   -  person Francisco Zamora-Martínez    schedule 20.09.2016
comment
Я нашел pd.Series([1, 3, 4, np.nan, 6]).interpolate.get_values().tolist() даже короче.   -  person Alfe    schedule 05.02.2018
comment
Начиная с pandas 1.2.4: pd.Series([1, 3, 4, np.nan, 6]).interpolate().tolist() еще короче   -  person Shadi    schedule 15.06.2021


Ответы (11)


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

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, но аналогичные методы используются и в других контекстах.

person eat    schedule 29.06.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.        ]
person Petter    schedule 29.06.2011
comment
@fmonegaglia, к сожалению, этот скрипт интерполирует только по одной оси 2D-массивов, это не 2D-интерполяция. Необходимость интерполяции по NaN в 2D-массивах имеет неприятную проблему: github.com/scipy/scipy/ issues / 1682 - person E. Douglas; 07.12.2015
comment
Из упомянутой проблемы вы можете напрямую использовать функцию convolve Astropy. - person ssnobody; 01.08.2017
comment
замените - на ~, чтобы он заработал (возможные версии со временем меняются) - person hootnot; 09.01.2019

Просто используйте numpy logic and there 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
person BRYAN WOODS    schedule 22.03.2012
comment
Это не обрабатывает NaN в начале или конце последовательности. - person EricP; 17.05.2017

Для двумерных данных у меня неплохо работает griddata SciPy:

>>> 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). Вся операция по-прежнему занимает около часа: /

person Gilly    schedule 17.06.2016
comment
спасибо за простое и компактное решение! Это занимает так много времени, поскольку по иронии судьбы griddata не использует свойство grid. - person Markus Dutschke; 20.03.2020
comment
Это отличное решение (хотя и долгое), спасибо! - person Laurent; 25.05.2021

Возможно, было бы проще изменить способ генерации данных в первую очередь, но если нет:

bad_indexes = np.isnan(data)

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

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

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

person Winston Ewert    schedule 29.06.2011
comment
У меня это не работает. Я получаю ValueError: setting an array element with a sequence. за вызов интерполяции - person Petter; 29.06.2011
comment
@Ben, извини, я не смог / не могу протестировать прямо сейчас. Попробуйте добавить [0] после обоих ненулевых () s. - person Winston Ewert; 29.06.2011

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

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.]]
person BBSysDyn    schedule 22.08.2012
comment
Это неплохо, но не работает, если по какой-то причине отсутствует более одного значения. - person mishaF; 01.06.2015

Мне нужен был подход, который также заполнял бы 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))
person nlml    schedule 30.08.2016

Слегка оптимизированная версия на основе ответа 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)

In [17]: adata = np.array([1, 2, np.NaN, 4])
Out[18]: array([ 1.,  2., nan,  4.])
In [19]: fill_nans_scipy1(adata)
Out[19]: array([1., 2., 3., 4.])
person Prokhozhii    schedule 29.10.2018
comment
TypeError: ufunc 'isfinite' не поддерживается для входных типов, и входные данные не могут быть безопасно приведены к каким-либо поддерживаемым типам в соответствии с правилом приведения `` безопасно '' - person Ayan Mitra; 18.07.2019
comment
Не могли бы вы уточнить? Что вы пытаетесь интерполировать? См. Мой пример выше. Все работает как положено. - person Prokhozhii; 05.09.2020

Опираясь на ответ Брайана Вудса, я изменил его код, чтобы также преобразовывать списки, состоящие только из из 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

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

person rbnvrw    schedule 20.09.2016

Интерполяция и экстраполяция с помощью ключевых слов заполнения

Следующее решение интерполирует значения nan в массиве на np.interp, если конечное значение присутствует с обеих сторон. Значения Nan на границах обрабатываются np.pad с такими режимами, как constant или reflect.

введите описание изображения здесь

    import numpy as np
    import matplotlib.pyplot as plt
    
    
    def extrainterpolate_nans_1d(
            arr, kws_pad=({'mode': 'edge'}, {'mode': 'edge'})
            ):
        """Interpolates and extrapolates nan values.
    
        Interpolation is linear, compare np.interp(..).
        Extrapolation works with pad keywords, compare np.pad(..).
    
        Parameters
        ----------
        arr : np.ndarray, shape (N,)
            Array to replace nans in.
        kws_pad : dict or (dict, dict)
            kwargs for np.pad on left and right side
    
        Returns
        -------
        bool
            Description of return value
    
        See Also
        --------
        https://numpy.org/doc/stable/reference/generated/numpy.interp.html
        https://numpy.org/doc/stable/reference/generated/numpy.pad.html
        https://stackoverflow.com/a/43821453/7128154
        """
        assert arr.ndim == 1
        if isinstance(kws_pad, dict):
            kws_pad_left = kws_pad
            kws_pad_right = kws_pad
        else:
            assert len(kws_pad) == 2
            assert isinstance(kws_pad[0], dict)
            assert isinstance(kws_pad[1], dict)
            kws_pad_left = kws_pad[0]
            kws_pad_right = kws_pad[1]
    
        arr_ip = arr.copy()
    
        # interpolation
        inds = np.arange(len(arr_ip))
        nan_msk = np.isnan(arr_ip)
        arr_ip[nan_msk] = np.interp(inds[nan_msk], inds[~nan_msk], arr[~nan_msk])
    
        # detemine pad range
        i0 = next(
            (ids for ids, val in np.ndenumerate(arr) if not np.isnan(val)), 0)[0]
        i1 = next(
            (ids for ids, val in np.ndenumerate(arr[::-1]) if not np.isnan(val)), 0)[0]
        i1 = len(arr) - i1
        # print('pad in range [0:{:}] and [{:}:{:}]'.format(i0, i1, len(arr)))
    
        # pad
        arr_pad = np.pad(
            arr_ip[i0:], pad_width=[(i0, 0)], **kws_pad_left)
        arr_pad = np.pad(
            arr_pad[:i1], pad_width=[(0, len(arr) - i1)], **kws_pad_right)
    
        return arr_pad
    
    
    # setup data
    ys = np.arange(30, dtype=float)**2/20
    ys[:5] = np.nan
    ys[20:] = 20
    ys[28:] = np.nan
    ys[[7, 13, 14, 18, 22]] = np.nan
    
    
    ys_ie0 = extrainterpolate_nans_1d(ys)
    kws_pad_sym = {'mode': 'symmetric'}
    kws_pad_const7 = {'mode': 'constant', 'constant_values':7.}
    ys_ie1 = extrainterpolate_nans_1d(ys, kws_pad=(kws_pad_sym, kws_pad_const7))
    ys_ie2 = extrainterpolate_nans_1d(ys, kws_pad=(kws_pad_const7, kws_pad_sym))
    
    fig, ax = plt.subplots()
    
    
    ax.scatter(np.arange(len(ys)), ys, s=15**2, label='ys')
    ax.scatter(np.arange(len(ys)), ys_ie0, s=8**2, label='ys_ie0, left_pad edge, right_pad edge')
    ax.scatter(np.arange(len(ys)), ys_ie1, s=6**2, label='ys_ie1, left_pad symmetric, right_pad 7')
    ax.scatter(np.arange(len(ys)), ys_ie2, s=4**2, label='ys_ie2, left_pad 7, right_pad symmetric')
    ax.legend()
person Markus Dutschke    schedule 10.03.2021

Как было предложено в предыдущем комментарии, лучший способ сделать это - использовать рецензируемую реализацию. В библиотеке pandas есть метод интерполяции для одномерных данных, который интерполирует значения np.nan в Series или DataFrame:

pandas.Series.interpolate или pandas.DataFrame.interpolate

Документация очень лаконична, рекомендую прочитать! Моя реализация:

import pandas as pd

magnitudes_series = pd.Series(magnitudes)    # Convert np.array to pd.Series
magnitudes_series.interpolate(
    # I used "akima" because the second derivative of my data has frequent drops to 0
    method=interpolation_method,

    # Interpolate from both sides of the sequence, up to you (made sense for my data)
    limit_direction="both",

    # Interpolate only np.nan sequences that have number sequences at the ends of the respective np.nan sequences
    limit_area="inside",

    inplace=True,
)

# I chose to remove np.nan at the tails of data sequence
magnitudes_series.dropna(inplace=True)

result_in_numpy_array = magnitudes_series.values
person Can H. Tartanoglu    schedule 13.06.2021