Замените NaN в массиве NumPy ближайшим значением non-NaN

у меня есть массив NumPy a следующим образом:

>>> str(a)
'[        nan         nan         nan  1.44955726  1.44628034  1.44409573n  1.4408188   1.43657094  1.43171624  1.42649744  1.42200684  1.42117704n  1.42040255  1.41922908         nan         nan         nan         nann         nan         nan]'

Я хочу заменить каждый NaN ближайшим значением non-NaN, чтобы все NaN в начале были установлены на 1.449... и все NaN в конце получают набор в 1.419....

Я вижу, как это сделать для конкретных случаев, подобных этому, но мне нужно иметь возможность делать это вообще для любой длины массива, с любой длиной NaN в начале и конце массива (в массиве не будет NaN середина чисел). Есть идеи?

Я могу найти NaN достаточно легко с np.isnan(), но я не могу понять, как получить самое близкое значение для каждого NaN.

7 ответов


Я хочу заменить каждый NaN ближайшим значением non-NaN... в середине чисел не будет НАН

это сделает следующее:

ind = np.where(~np.isnan(a))[0]
first, last = ind[0], ind[-1]
a[:first] = a[first]
a[last + 1:] = a[last]

Это numpy решение, не требующее циклов Python,рекурсии, понимания списка и т. д.


в качестве альтернативного решения (это будет линейно интерполироваться для массивов NaNs в середине, а):

import numpy as np

# Generate data...
data = np.random.random(10)
data[:2] = np.nan
data[-1] = np.nan
data[4:6] = np.nan

print data

# Fill in NaN's...
mask = np.isnan(data)
data[mask] = np.interp(np.flatnonzero(mask), np.flatnonzero(~mask), data[~mask])

print data

Это дает:

[        nan         nan  0.31619306  0.25818765         nan         nan
  0.27410025  0.23347532  0.02418698         nan]

[ 0.31619306  0.31619306  0.31619306  0.25818765  0.26349185  0.26879605
  0.27410025  0.23347532  0.02418698  0.02418698]

NaNs имеют интересное свойство сравнения, отличное от них самих, поэтому мы можем быстро найти индексы элементов, отличных от nan:

idx = np.nonzero(a==a)[0]

теперь легко заменить nans на желаемое значение:

for i in range(0, idx[0]):
    a[i]=a[idx[0]]
for i in range(idx[-1]+1, a.size)
    a[i]=a[idx[-1]]

наконец, мы можем поместить это в функцию:

import numpy as np

def FixNaNs(arr):
    if len(arr.shape)>1:
        raise Exception("Only 1D arrays are supported.")
    idxs=np.nonzero(arr==arr)[0]

    if len(idxs)==0:
        return None

    ret=arr

    for i in range(0, idxs[0]):
        ret[i]=ret[idxs[0]]

    for i in range(idxs[-1]+1, ret.size):
        ret[i]=ret[idxs[-1]]

    return ret

редактировать

ой, исходя из C++, я всегда забываю о диапазонах списков... решение @aix является более элегантным и эффективным чем мои циклы c++ish, используйте это вместо моего.


рекурсивное решение!

def replace_leading_NaN(a, offset=0):
    if a[offset].isNaN():
        new_value = replace_leading_NaN(a, offset + 1)
        a[offset] = new_value
        return new_value
    else:
        return a[offset]

def replace_trailing_NaN(a, offset=-1):
    if a[offset].isNaN():
        new_value = replace_trailing_NaN(a, offset - 1)
        a[offset] = new_value
        return new_value
    else:
        return a[offset]

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

import numpy as np

Data = np.array([np.nan,1.3,np.nan,1.4,np.nan,np.nan])

nansIndx = np.where(np.isnan(Data))[0]
isanIndx = np.where(~np.isnan(Data))[0]
for nan in nansIndx:
    replacementCandidates = np.where(isanIndx>nan)[0]
    if replacementCandidates.size != 0:
        replacement = Data[isanIndx[replacementCandidates[0]]]
    else:
        replacement = Data[isanIndx[np.where(isanIndx<nan)[0][-1]]]
    Data[nan] = replacement

результат:

>>> Data
array([ 1.3,  1.3,  1.4,  1.4,  1.4,  1.4])

У меня что-то вроде этого

i = [i for i in range(len(a)) if not np.isnan(a[i])]
a = [a[i[0]] if x < i[0] else (a[i[-1]] if x > i[-1] else a[x]) for x in range(len(a))]

это немного неуклюже, хотя, учитывая, что он разделен на две строки с вложенным встроенным if в одном из них.


вот решение, использующее простые итераторы python. Они на самом деле более эффективны здесь, чем numpy.where, особенно с большими массивами! См. Сравнение аналогичного кода здесь.

import numpy as np

a = np.array([np.NAN, np.NAN, np.NAN, 1.44955726, 1.44628034, 1.44409573, 1.4408188, 1.43657094, 1.43171624,  1.42649744, 1.42200684, 1.42117704, 1.42040255, 1.41922908, np.NAN, np.NAN, np.NAN, np.NAN, np.NAN, np.NAN])

mask = np.isfinite(a)

# get first value in list
for i in range(len(mask)):
    if mask[i]:
        first = i
        break

# get last vaue in list
for i in range(len(mask)-1, -1, -1):
    if mask[i]:
        last = i
        break

# fill NaN with near known value on the edges
a = np.copy(a)
a[:first] = a[first]
a[last + 1:] = a[last]

print(a)

выход:

[1.44955726 1.44955726 1.44955726 1.44955726 1.44628034 1.44409573
 1.4408188  1.43657094 1.43171624 1.42649744 1.42200684 1.42117704
 1.42040255 1.41922908 1.41922908 1.41922908 1.41922908 1.41922908
 1.41922908 1.41922908]

он заменяет только первый и последний NaNs, как запрошено здесь.