Замените 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,рекурсии, понимания списка и т. д.
в качестве альтернативного решения (это будет линейно интерполироваться для массивов NaN
s в середине, а):
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]
NaN
s имеют интересное свойство сравнения, отличное от них самих, поэтому мы можем быстро найти индексы элементов, отличных от 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, как запрошено здесь.