通过计算传播NaN

时间:2012-04-05 19:02:58

标签: python python-3.x nan numerical

通常,NaN(不是数字)通过计算传播,因此我不需要在每个步骤中检查NaN。这几乎总是有效,但显然有例外。例如:

>>> nan = float('nan')
>>> pow(nan, 0)
1.0

我发现了following comment

  

通过算术运算传播安静的NaNs   在一系列操作结束时检测到的错误   在中间阶段进行广泛的测试但请注意   根据语言和功能,NaN可以默默地   在表达式中删除,这将为所有其他表达式提供恒定的结果   浮点值,例如NaN ^ 0,可以定义为1,所以在   通常,需要稍后测试一组INVALID标志来检测所有   引入NaNs的案例。

     

满足那些希望更严格地解释权力的人   功能应该行动,2008年标准定义了两个额外的力量   功能; pown(x,n)其中指数必须是整数,和   powr(x,y),只要参数是NaN或者,就返回NaN   取幂会给出一种不确定的形式。

有没有办法通过Python检查上面提到的INVALID标志?或者,是否有其他方法可以捕获NaN不传播的情况?

动机:我决定使用NaN来丢失数据。在我的应用程序中,缺少输入应导致缺少结果。它工作得很好,除了我描述的例外。

4 个答案:

答案 0 :(得分:3)

我意识到自问这个问题已经过去了一个月,但我遇到了类似的问题(即pow(float('nan'), 1)在某些Python实现中抛出异常,例如Jython 2.52b2),我发现了上面的内容答案并不是我想要的。

使用6502建议的MissingData类型似乎是要走的路,但我需要一个具体的例子。我尝试了Ethan Furman的NullType类,但发现这不适用于任何算术运算,因为它不强制数据类型(见下文),我也不喜欢它明确命名每个被覆盖的算术函数。

从Ethan的例子和我发现here的调整代码开始,我到了下面的课程。虽然这个类有很多注释,但你可以看到它实际上只有一些功能代码行。

关键点是: 1。 使用coerce()为混合类型(例如NoData + float)算术运算返回两个NoData对象,为基于字符串(例如concat)操作返回两个字符串。 2。 使用getattr()为所有其他属性/方法访问返回可调用的NoData()对象 3. 使用call()实现NoData()对象的所有其他方法:返回NoData()对象

以下是其使用的一些示例。

>>> nd = NoData()
>>> nd + 5
NoData()
>>> pow(nd, 1)
NoData()
>>> math.pow(NoData(), 1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: nb_float should return float object
>>> nd > 5
NoData()
>>> if nd > 5:
...     print "Yes"
... else:
...     print "No"
... 
No
>>> "The answer is " + nd
'The answer is NoData()'
>>> "The answer is %f" % (nd)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: float argument required, not instance
>>> "The answer is %s" % (nd)
'The answer is '
>>> nd.f = 5
>>> nd.f
NoData()
>>> nd.f()
NoData()

我注意到使用带有NoData()的pow会调用**运算符,因此可以使用NoData,但是使用math.pow并不是因为它首先尝试将NoData()对象转换为float。我很高兴使用非数学能力 - 希望6502等人在他们上面的评论中遇到pow问题时使用math.pow。

另一个我想不出解决方法的问题是使用格式(%f)运算符...在这种情况下没有调用NoData的方法,如果你不提供运算符就会失败一个浮子。无论如何这里是班级本身。

class NoData():
"""NoData object - any interaction returns NoData()"""
def __str__(self):
    #I want '' returned as it represents no data in my output (e.g. csv) files
    return ''        

def __unicode__(self):
    return ''

def __repr__(self):
    return 'NoData()'

def __coerce__(self, other_object):
    if isinstance(other_object, str) or isinstance(other_object, unicode):
        #Return string objects when coerced with another string object.
        #This ensures that e.g. concatenation operations produce strings.
        return repr(self), other_object  
    else:
        #Otherwise return two NoData objects - these will then be passed to the appropriate
        #operator method for NoData, which should then return a NoData object
        return self, self

def __nonzero__(self):
    #__nonzero__ is the operation that is called whenever, e.g. "if NoData:" occurs
    #i.e. as all operations involving NoData return NoData, whenever a 
    #NoData object propagates to a test in branch statement.       
    return False        

def __hash__(self):
    #prevent NoData() from being used as a key for a dict or used in a set
    raise TypeError("Unhashable type: " + self.repr())

def __setattr__(self, name, value):
    #This is overridden to prevent any attributes from being created on NoData when e.g. "NoData().f = x" is called
    return None       

def __call__(self, *args, **kwargs):
    #if a NoData object is called (i.e. used as a method), return a NoData object
    return self    

def __getattr__(self,name):
    #For all other attribute accesses or method accesses, return a NoData object.
    #Remember that the NoData object can be called (__call__), so if a method is called, 
    #a NoData object is first returned and then called.  This works for operators,
    #so e.g. NoData() + 5 will:
    # - call NoData().__coerce__, which returns a (NoData, NoData) tuple
    # - call __getattr__, which returns a NoData object
    # - call the returned NoData object with args (self, NoData)
    # - this call (i.e. __call__) returns a NoData object   

    #For attribute accesses NoData will be returned, and that's it.

    #print name #(uncomment this line for debugging purposes i.e. to see that attribute was accessed/method was called)
    return self

答案 1 :(得分:2)

如果只是pow()给你头疼,你可以轻松地重新定义它以在任何你喜欢的情况下返回NaN

def pow(x, y):
    return x ** y if x == x else float("NaN")

如果NaN可用作指数,您还需要检查;这会引发ValueError异常,除非基数为1(显然理论上1对任何幂,即使不是数字,也是1)。

(当然pow()实际上需要三个操作数,第三个是可选的,我将作为练习留下遗漏......)

不幸的是**运算符具有相同的行为,并且没有办法重新定义内置数值类型。捕获这种情况的可能性是编写实现float__pow__()的{​​{1}}子类,并将该类用于__rpow__()值。

Python似乎不提供对计算设置的任何标志的访问;即使它确实如此,也是每次单独操作后都要检查的东西。

事实上,经过进一步考虑,我认为最好的解决方案可能是简单地使用虚拟类的实例来表示缺失值。 Python会阻塞您尝试对这些值执行的任何操作,引发异常,您可以捕获异常并返回默认值或其他值。如果缺少所需的值,则没有理由继续进行剩余的计算,因此异常应该没问题。

答案 2 :(得分:2)

为什么使用已经有另一个语义的NaN而不是使用自己定义的类MissingData的实例?

定义MissingData个实例上的操作以便传播应该很容易......

答案 3 :(得分:2)

回答你的问题:不,没有办法使用普通浮动来检查标志。但是,您可以使用Decimal类,它提供更多控件。 。 但速度有点慢。

您的另一个选择是使用EmptyDataNull类,例如:

class NullType(object):
    "Null object -- any interaction returns Null"
    def _null(self, *args, **kwargs):
        return self
    __eq__ = __ne__ = __ge__ = __gt__ = __le__ = __lt__ = _null
    __add__ = __iadd__ = __radd__ = _null
    __sub__ = __isub__ = __rsub__ = _null
    __mul__ = __imul__ = __rmul__ = _null
    __div__ = __idiv__ = __rdiv__ = _null
    __mod__ = __imod__ = __rmod__ = _null
    __pow__ = __ipow__ = __rpow__ = _null
    __and__ = __iand__ = __rand__ = _null
    __xor__ = __ixor__ = __rxor__ = _null
    __or__ = __ior__ = __ror__ = _null
    __divmod__ = __rdivmod__ = _null
    __truediv__ = __itruediv__ = __rtruediv__ = _null
    __floordiv__ = __ifloordiv__ = __rfloordiv__ = _null
    __lshift__ = __ilshift__ = __rlshift__ = _null
    __rshift__ = __irshift__ = __rrshift__ = _null
    __neg__ = __pos__ = __abs__ = __invert__ = _null
    __call__ = __getattr__ = _null

    def __divmod__(self, other):
        return self, self
    __rdivmod__ = __divmod__

    if sys.version_info[:2] >= (2, 6):
        __hash__ = None
    else:
        def __hash__(yo):
            raise TypeError("unhashable type: 'Null'")

    def __new__(cls):
        return cls.null
    def __nonzero__(yo):
        return False
    def __repr__(yo):
        return '<null>'
    def __setattr__(yo, name, value):
        return None
    def __setitem___(yo, index, value):
        return None
    def __str__(yo):
        return ''
NullType.null = object.__new__(NullType)
Null = NullType()

您可能想要更改__repr____str__方法。另请注意,Null不能用作字典键,也不能存储在集合中。