69 votes

La manière la plus efficace de remplir les valeurs NaN dans les tableaux numpy.

Exemple de problème

À titre d'exemple simple, considérons le tableau numpy arr comme défini ci-dessous :

import numpy as np
arr = np.array([[5, np.nan, np.nan, 7, 2],
                [3, np.nan, 1, 8, np.nan],
                [4, 9, 6, np.nan, np.nan]])

arr ressemble à ça en sortie de console :

array([[  5.,  nan,  nan,   7.,   2.],
       [  3.,  nan,   1.,   8.,  nan],
       [  4.,   9.,   6.,  nan,  nan]])

J'aimerais maintenant remplir les rangées en avant de l'écran. nan valeurs dans le tableau arr . J'entends par là le remplacement de chaque nan avec la valeur valide la plus proche à partir de la gauche. Le résultat souhaité ressemblerait à ceci :

array([[  5.,   5.,   5.,  7.,  2.],
       [  3.,   3.,   1.,  8.,  8.],
       [  4.,   9.,   6.,  6.,  6.]])

Essayé jusqu'à présent

J'ai essayé d'utiliser des boucles for :

for row_idx in range(arr.shape[0]):
    for col_idx in range(arr.shape[1]):
        if np.isnan(arr[row_idx][col_idx]):
            arr[row_idx][col_idx] = arr[row_idx][col_idx - 1]

J'ai également essayé d'utiliser un cadre de données pandas comme étape intermédiaire (puisque les cadres de données pandas ont une méthode intégrée très soignée pour le remplissage avant) :

import pandas as pd
df = pd.DataFrame(arr)
df.fillna(method='ffill', axis=1, inplace=True)
arr = df.as_matrix()

Les deux stratégies ci-dessus produisent le résultat souhaité, mais je continue à me demander : une stratégie qui utilise uniquement des opérations vectorielles numpy ne serait-elle pas la plus efficace ?


Résumé

Existe-t-il un autre moyen plus efficace de "remplir à l'avance" nan des valeurs dans des tableaux numpy ? (par exemple, en utilisant des opérations vectorielles numpy)


Mise à jour : Comparaison des solutions

J'ai essayé toutes les solutions jusqu'à présent. C'était ma configuration script :

import numba as nb
import numpy as np
import pandas as pd

def random_array():
    choices = [1, 2, 3, 4, 5, 6, 7, 8, 9, np.nan]
    out = np.random.choice(choices, size=(1000, 10))
    return out

def loops_fill(arr):
    out = arr.copy()
    for row_idx in range(out.shape[0]):
        for col_idx in range(1, out.shape[1]):
            if np.isnan(out[row_idx, col_idx]):
                out[row_idx, col_idx] = out[row_idx, col_idx - 1]
    return out

@nb.jit
def numba_loops_fill(arr):
    '''Numba decorator solution provided by shx2.'''
    out = arr.copy()
    for row_idx in range(out.shape[0]):
        for col_idx in range(1, out.shape[1]):
            if np.isnan(out[row_idx, col_idx]):
                out[row_idx, col_idx] = out[row_idx, col_idx - 1]
    return out

def pandas_fill(arr):
    df = pd.DataFrame(arr)
    df.fillna(method='ffill', axis=1, inplace=True)
    out = df.as_matrix()
    return out

def numpy_fill(arr):
    '''Solution provided by Divakar.'''
    mask = np.isnan(arr)
    idx = np.where(~mask,np.arange(mask.shape[1]),0)
    np.maximum.accumulate(idx,axis=1, out=idx)
    out = arr[np.arange(idx.shape[0])[:,None], idx]
    return out

suivi de cette entrée de console :

%timeit -n 1000 loops_fill(random_array())
%timeit -n 1000 numba_loops_fill(random_array())
%timeit -n 1000 pandas_fill(random_array())
%timeit -n 1000 numpy_fill(random_array())

ce qui donne cette sortie de console :

1000 loops, best of 3: 9.64 ms per loop
1000 loops, best of 3: 377 µs per loop
1000 loops, best of 3: 455 µs per loop
1000 loops, best of 3: 351 µs per loop

4 votes

Ce qui doit se passer si le premier élément d'une ligne est nan ?

0 votes

@TadhgMcDonald-Jensen Dans ce cas, pandas laisse l'élément NaN intacte. Je suppose que le PO veut le même comportement par souci de cohérence.

4 votes

2voto

christian_bock Points 36

Pour ceux qui s'intéressent au problème d'avoir des dirigeants np.nan après le remplissage de l'avant, ce qui suit fonctionne :

mask = np.isnan(arr)
first_non_zero_idx = (~mask!=0).argmax(axis=1) #Get indices of first non-zero values
arr = [ np.hstack([
             [arr[i,first_nonzero]]*(first_nonzero), 
             arr[i,first_nonzero:]])
             for i, first_nonzero in enumerate(first_non_zero_idx) ]

0 votes

Je ne suis pas sûr de comprendre le but de ce code. Que voulez-vous dire exactement par "problème d'avoir des np.nan en tête après le remplissage en avant" ?

2 votes

Dans l'exemple de tableau au début de la menace, chaque entrée commence par un non nan. Certaines personnes peuvent se trouver confrontées à un ensemble de données qui nécessite un remplissage en amont, car le remplissage en aval laisse les premières entrées intactes. J'ai donc pensé qu'il pourrait être utile de présenter une solution dans cette menace.

1voto

RobertHannah89 Points 118

Si vous êtes prêt à utiliser Pandas/ xarray : Laissez l'axe être la direction sur laquelle vous souhaitez effectuer le ffill/bfill, comme indiqué ci-dessous,

xr.DataArray(arr).ffill(f'dim_{axis}').values
xr.DataArray(arr).bfill(f'dim_{axis}').values

Plus d'informations : http://xarray.pydata.org/en/stable/generated/xarray.DataArray.ffill.html https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.ffill.html

1voto

Joseph Gonzalez Points 33

fonction de poussée du goulot d'étranglement est une bonne option pour le remplissage avant. Il est normalement utilisé en interne dans des paquets comme Xarray, il devrait être plus rapide que d'autres alternatives et le paquet dispose également d'un ensemble d'options de remplissage. repères .

Ejemplo:

import numpy as np

from bottleneck import push

a = np.array(
    [
        [1, np.nan, 3],
        [np.nan, 3, 2],
        [2, np.nan, np.nan]
    ]
)
push(a, axis=0)
array([[ 1., nan,  3.],
       [ 1.,  3.,  2.],
       [ 2.,  3.,  2.]])

1voto

Vikrant Gupta Points 82

Utilisez le module bottleneck, il est fourni avec le module pandas ou numpy donc pas besoin de l'installer séparément.

Le code ci-dessous devrait vous donner le résultat souhaité.

import bottleneck as bn
bn.push(arr,axis=1)

0voto

BetterEnglish Points 639

Sauf erreur de ma part, les solutions ne fonctionnent sur aucun exemple :

arr  = np.array([[ 3.],
 [ 8.],
 [np.nan],
 [ 7.],
 [np.nan],
 [ 1.],
 [np.nan],
 [ 3.],
 [ 8.],
 [ 8.]])
print("A:::: \n", arr)

print("numpy_fill::: \n ",  numpy_fill(arr))
print("loop_fill",  loops_fill(arr))

A:::: 
 [[ 3.]
 [ 8.]
 [nan]
 [ 7.]
 [nan]
 [ 1.]
 [nan]
 [ 3.]
 [ 8.]
 [ 8.]]
numpy_fill::: 
  [[ 3.]
 [ 8.]
 [nan]
 [ 7.]
 [nan]
 [ 1.]
 [nan]
 [ 3.]
 [ 8.]
 [ 8.]]
loop_fill [[ 3.]
 [ 8.]
 [nan]
 [ 7.]
 [nan]
 [ 1.]
 [nan]
 [ 3.]
 [ 8.]
 [ 8.]]

Comments ??

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X