Python >> Tutoriel Python >  >> Python Tag >> NumPy

éviter la division par zéro dans numpy.where()

Initialisez simplement le tableau de sortie avec les valeurs de repli (valeurs de condition non satisfaisantes) ou le tableau, puis masquez pour sélectionner les valeurs de condition satisfaisant à attribuer -

out = a.copy()
out[mask] /= b[mask]

Si vous recherchez la performance, nous pouvons utiliser un b modifié pour la division -

out = a / np.where(mask, b, 1)

Pour aller plus loin, superchargez-le avec numexpr pour ce cas précis de valeurs positives dans b (>=0) -

import numexpr as ne
    
out = ne.evaluate('a / (1 - mask + b)')

Analyse comparative

Code pour reproduire le tracé :

import perfplot
import numpy
import numexpr

numpy.random.seed(0)


def setup(n):
    a = numpy.random.rand(n)
    b = numpy.random.rand(n)
    b[b < 0.3] = 0.0
    mask = b > 0
    return a, b, mask


def copy_slash(data):
    a, b, mask = data
    out = a.copy()
    out[mask] /= b[mask]
    return out


def copy_divide(data):
    a, b, mask = data
    out = a.copy()
    return numpy.divide(a, b, out=out, where=mask)


def slash_where(data):
    a, b, mask = data
    return a / numpy.where(mask, b, 1.0)


def numexpr_eval(data):
    a, b, mask = data
    return numexpr.evaluate('a / (1 - mask + b)')


perfplot.save(
    "out.png",
    setup=setup,
    kernels=[copy_slash, copy_divide, slash_where, numexpr_eval],
    n_range=[2 ** k for k in range(22)],
    xlabel="n"
)

Une légère variation sur la réponse de Divakar est d'utiliser le where et out arguments de la fonction de division de Numpy

out = a.copy()
np.divide(a, b, out=out, where=mask)

Pour les grands tableaux, cela semble être deux fois plus rapide :

In [1]: import numpy as np

In [2]: a = np.random.rand(1000, 1000)
   ...: b = np.random.rand(1000, 1000)
   ...: b[b < 0.3] = 0.0

In [3]: def f(a, b):
   ...:     mask = b > 0
   ...:     out = a.copy()
   ...:     out[mask] = a[mask] / b[mask]
   ...:     return out
   ...:     

In [4]: def g(a, b):
   ...:     mask = b > 0
   ...:     out = a.copy()
   ...:     np.divide(a, b, out=out, where=mask)
   ...:     return out
   ...:     

In [5]: (f(a, b) == g(a, b)).all()  # sanity check
Out[5]: True

In [6]: timeit f(a,b)
26.7 ms ± 52.6 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [7]: timeit g(a,b)
12.2 ms ± 36 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

La raison pour laquelle cela est plus rapide est probablement que cela évite de créer un tableau temporaire pour le côté droit, et que le "masquage" est effectué en interne sur le divide fonction, au lieu de l'indexation de a[mask] , b[mask] et out[mask] .