web-dev-qa-db-fra.com

Interpole les valeurs NaN dans un tableau numpy

Existe-t-il un moyen rapide de remplacer toutes les valeurs NaN dans un tableau numpy par (par exemple) les valeurs interpolées linéairement?

Par exemple, 

[1 1 1 nan nan 2 2 nan 0]

serait converti en

[1 1 1 1.3 1.6 2 2  1  0]
47
Petter

Définissons d’abord une fonction d’aide simple afin de faciliter la gestion des indices et des indices logiques de 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]

Maintenant, la nan_helper(.) peut maintenant être utilisée comme:

>>> 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.  ]

---
Bien qu’il puisse sembler un peu exagéré d’abord de spécifier une fonction distincte pour faire des choses comme celle-ci:

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

il finira par payer des dividendes. 

Ainsi, chaque fois que vous travaillez avec des données liées à NaN, encapsulez simplement toutes les fonctionnalités (nouvelles liées à NaN) nécessaires, sous des fonctions d'assistance spécifiques. Votre base de code sera plus cohérente et lisible, car elle suit des idiomes facilement compréhensibles. 

L'interpolation, en effet, est un contexte agréable pour voir comment le traitement de NaN est effectué, mais des techniques similaires sont également utilisées dans divers contextes.

77
eat

Je suis venu avec ce code:

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

Il imprime 

 [ 1.          1.33333333  1.66666667  2.          2.          1.          0.        ]
22
Petter

Utilisez simplement numpy logical et la directive where pour appliquer une interpolation 1D.

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
8
BRYAN WOODS

Il serait peut-être plus facile de modifier la façon dont les données sont générées, mais dans le cas contraire:

bad_indexes = np.isnan(data)

Créer un tableau booléen indiquant où sont les nans

good_indexes = np.logical_not(bad_indexes)

Créer un tableau booléen indiquant où se trouve la zone des bonnes valeurs

good_data = data[good_indexes]

Une version restreinte des données d'origine à l'exclusion des nans

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

Exécuter tous les mauvais index par interpolation

data[bad_indexes] = interpolated

Remplacez les données d'origine par les valeurs interpolées.

5
Winston Ewert

Ou construire sur la réponse de Winston

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

Résultat

[[   1.   20.  300.]
 [   2.   30.  400.]
 [   3.   40.  500.]]
4
user423805

J'avais besoin d'une approche qui renseignerait également les NaN au début et à la fin des données, ce que la réponse principale ne semble pas faire.

La fonction que j'ai créée utilise une régression linéaire pour renseigner les NaN. Ceci surmonte mon problème:

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

Voici un exemple d'utilisation:

# 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
nlml

Pour les données bidimensionnelles, la griddata de SciPy fonctionne assez bien pour moi:

>>> 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]])

Je l'utilise sur des images 3D, opérant sur des tranches 2D (4000 tranches de 350x350). Toute l'opération prend encore environ une heure: /

2
Gilly

S'appuyant sur la réponse de Bryan Woods , j'ai modifié son code pour convertir également les listes composées uniquement de NaN en une liste de zéros:

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

Ajout simple, j'espère que cela servira à quelqu'un.

2
rbnvrw

Version légèrement optimisée basée sur la réponse de BRYAN WOODS . Il gère correctement les valeurs de début et de fin des données source, et est plus rapide à 25-30% que la version d'origine. Vous pouvez également utiliser différents types d'interpolations (voir la documentation de scipy.interpolate.interp1d pour plus de détails).

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)
0
Prokhozhii