【问题标题】:Calling function on valid values of masked arrays对屏蔽数组的有效值调用函数
【发布时间】:2017-07-27 21:15:27
【问题描述】:

我有两个 numpy 掩码数组:

>>> x
masked_array(data = [1 2 -- 4],
             mask = [False False  True False],
       fill_value = 999999)
>>> y
masked_array(data = [4 -- 0 4],
             mask = [False  True False False],
       fill_value = 999999)

如果我尝试将x 除以y,则当其中一个操作数被屏蔽时,除法运算实际上并未执行,因此我不会收到被零除错误。

>>> x/y
masked_array(data = [0.25 -- -- 1.0],
             mask = [False  True  True False],
       fill_value = 1e+20)

如果我定义自己的除法函数div,这甚至可以工作:

>>> def div(a,b):
    return a/b

>>> div(x, y)
masked_array(data = [0.25 -- -- 1.0],
             mask = [False  True  True False],
       fill_value = 1e+20)

但是,如果我用 vectorize 包装我的函数,则函数会在掩码值上调用,我会收到错误:

>>> np.vectorize(div)(x, y)
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "/usr/lib64/python3.4/site-packages/numpy/lib/function_base.py", line 1811, in __call__
    return self._vectorize_call(func=func, args=vargs)
  File "/usr/lib64/python3.4/site-packages/numpy/lib/function_base.py", line 1880, in _vectorize_call
    outputs = ufunc(*inputs)
  File "<input>", line 2, in div
ZeroDivisionError: division by zero

有没有一种方法可以调用带有数组参数的函数,并且只有在所有参数都未屏蔽时才执行该函数?

【问题讨论】:

    标签: python numpy


    【解决方案1】:

    问题

    直接调用函数是因为,当你调用div(x,y)时,div的参数ab变成了MaskedArraysxy,而a/b的结果代码是x.__div__(y)(或__truediv__)。

    现在,由于x 是一个 MaskedArray,它有智能按照它的规则对另一个 MaskedArray 执行除法。

    但是,当您对其进行矢量化处理时,您的 div 函数不会看到任何 MaskedArrays,只会看到标量,在这种情况下会看到一对 ints。因此,当它在第三项中尝试 a/b 时,它将是“某事”为零,并且您会收到错误。

    MaskedArray 的实现似乎是基于专门为 MaskedArrays 重新实现 Numpy 的大部分内容。例如,请参阅您同时拥有numpy.lognumpy.ma.log。比较在包含负值的 MaskedArray 上运行它们。两者实际上都返回了一个正确的 MaskedArray,但普通的 numpy 版本也输出了一些关于除以零的抱怨:

    In [116]: x = masked_array(data = [-1, 2, 0, 4],
         ...:              mask = [False, False,  True, False],
         ...:        fill_value = 999999)
    
    In [117]: numpy.log(x)
    /usr/bin/ipython:1: RuntimeWarning: divide by zero encountered in log
      #!/usr/bin/python3
    /usr/bin/ipython:1: RuntimeWarning: invalid value encountered in log
      #!/usr/bin/python3
    Out[117]: 
    masked_array(data = [-- 0.6931471805599453 -- 1.3862943611198906],
                 mask = [ True False  True False],
           fill_value = 999999)
    
    In [118]: numpy.ma.log(x)
    Out[118]: 
    masked_array(data = [-- 0.6931471805599453 -- 1.3862943611198906],
                 mask = [ True False  True False],
           fill_value = 999999)
    

    如果您在普通列表上运行 numpy.log 版本,它将返回 naninf 无效值,而不是像您得到的 ZeroDivisionError 那样抛出错误。

    In [138]: a = [1,-1,0]
    
    In [139]: numpy.log(a)
    /usr/bin/ipython:1: RuntimeWarning: divide by zero encountered in log
      #!/usr/bin/python3
    /usr/bin/ipython:1: RuntimeWarning: invalid value encountered in log
      #!/usr/bin/python3
    Out[139]: array([  0.,  nan, -inf])
    

    更简单的解决方案

    这样,我看到了两种选择:首先,对于您列出的更简单的情况,您可以用无操作替换坏值:div 的情况下为 1(请注意,数据与您的略有不同,因为有一个零你没有标记为屏蔽):

    x = masked_array(data = [1, 2, 0, 4],
                 mask = [False, False,  True, False],
           fill_value = 999999)
    y = masked_array(data = [4, 0, 0, 4],
                 mask = [False,  True, True, False],
           fill_value = 999999)
    In [153]: numpy.vectorize(div)(x,y.filled(1))
    Out[153]: 
    masked_array(data = [0.25 2.0 -- 1.0],
                 mask = [False False  True False],
           fill_value = 999999)
    

    这种方法的问题在于,填充的值在结果中被列为未屏蔽,这可能不是您想要的。

    更好的解决方案

    现在,div 可能只是一个示例,您可能想要更复杂的行为,而没有“无操作”参数。在这种情况下,您可以像 Numpy 对 log 所做的那样,避免抛出异常,而是返回特定值。在这种情况下,numpy.ma.maskeddiv 的实现变成了这样:

    In [154]: def div(a,b):
         ...:     try:
         ...:         return a/b
         ...:     except Exception as e:
         ...:         warnings.warn (str(e))
         ...:         return numpy.ma.masked
         ...:     
         ...:         
    
    In [155]: numpy.vectorize(div)(x,y)
    /usr/bin/ipython:5: UserWarning: division by zero
      start_ipython()
    /usr/lib/python3.6/site-packages/numpy/lib/function_base.py:2813:     UserWarning: Warning: converting a masked element to nan.
      res = array(outputs, copy=False, subok=True, dtype=otypes[0])
    Out[155]: 
    masked_array(data = [0.25 -- -- 1.0],
                 mask = [False  True  True False],
           fill_value = 999999)
    

    更通用的解决方案

    但也许您已经拥有该功能并且不想更改它,或者它是第三方的。在这种情况下,您可以使用高阶函数:

    In [164]: >>> def div(a,b):
         ...:     return a/b
         ...: 
    
    In [165]: def masked_instead_of_error (f):
         ...:     def wrapper (*args, **kwargs):
         ...:         try:
         ...:             return f(*args, **kwargs)
         ...:         except:
         ...:             return numpy.ma.masked
         ...:     return wrapper
         ...:        
    
    In [166]: numpy.vectorize(masked_instead_of_error(div))(x,y)
    /usr/lib/python3.6/site-packages/numpy/lib/function_base.py:2813:             UserWarning: Warning: converting a masked element to nan.
      res = array(outputs, copy=False, subok=True, dtype=otypes[0])
    Out[166]: 
    masked_array(data = [0.25 -- -- 1.0],
                 mask = [False  True  True False],
           fill_value = 999999)
    

    在上述实现中,使用警告可能是一个好主意,也可能不是一个好主意。您可能还想限制返回 numpy.ma.masked 时将捕获的异常类型。

    另请注意,masked_instead_of_error 已准备好用作函数的装饰器,因此您无需每次都使用它。

    【讨论】:

    • 感谢您的彻底回复。它帮助我理解这里发生了什么。我不想将无效值传递给我的函数,而是防止使用屏蔽值调用该函数。我调整了您的装饰器方法以提出以下建议:gist.github.com/dbaston/b41c3fa8c02ac151e52e132509c89b4c
    • @dbaston,很高兴能帮上忙。我发现您的解决方案非常有趣。我唯一要指出的是,我认为np.ma.where 的最后一个参数应该调用fn,而不是vectorized,不是吗?
    • 如果fn包含if语句或其他条件逻辑,我不能直接调用。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-01-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多