Есть ли быстрый способ заменить все значения NaN в массиве numpy (скажем) линейно интерполированными значениями?
Например,
[1 1 1 nan nan 2 2 nan 0]
будет преобразован в
[1 1 1 1.3 1.6 2 2 1 0]
Есть ли быстрый способ заменить все значения NaN в массиве numpy (скажем) линейно интерполированными значениями?
Например,
[1 1 1 nan nan 2 2 nan 0]
будет преобразован в
[1 1 1 1.3 1.6 2 2 1 0]
Давайте сначала определим простую вспомогательную функцию, чтобы упростить обработку индексов и логических индексов 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, но аналогичные методы используются и в других контекстах.
Я придумал этот код:
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. ]
Просто используйте 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
Для двумерных данных у меня неплохо работает 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). Вся операция по-прежнему занимает около часа: /
Возможно, было бы проще изменить способ генерации данных в первую очередь, но если нет:
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
Замените исходные данные интерполированными значениями.
ValueError: setting an array element with a sequence.
за вызов интерполяции
- person Petter; 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.]]
Мне нужен был подход, который также заполнял бы 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))
Слегка оптимизированная версия на основе ответа 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.])
Опираясь на ответ Брайана Вудса, я изменил его код, чтобы также преобразовывать списки, состоящие только из из 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
Простое дополнение, надеюсь, оно кому-то будет полезно.
Следующее решение интерполирует значения 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()
Как было предложено в предыдущем комментарии, лучший способ сделать это - использовать рецензируемую реализацию. В библиотеке 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
pd.DataFrame([1, 3, 4, np.nan, 6]).interpolate().values.ravel().tolist()
- person Francisco Zamora-Martínez   schedule 20.09.2016pd.Series([1, 3, 4, np.nan, 6]).interpolate.get_values().tolist()
даже короче. - person Alfe   schedule 05.02.2018pd.Series([1, 3, 4, np.nan, 6]).interpolate().tolist()
еще короче - person Shadi   schedule 15.06.2021