Skip to content

ENH: change ufuncs when using where without out to initialize all values #17192

@tbenst

Description

@tbenst

First of all, thank you so much for the work on Numpy--an incredible contribution to science and progress.

Reproducing code example:

Numpy has excellent support for functional programming patterns, and the hundreds of np.* functions I regularly use act as pure functions with no side effects.

The current behavior of np.negative breaks this pattern, and is not deterministic:

>>> import numpy as np
>>> np.negative(np.arange(5), where=np.array([0,0,1,1,0],dtype=bool))
array([     94871006724704,        171798691860,                  -2,
                        -3, 4285579895102272626])
>>> np.negative(np.arange(5), where=np.array([0,0,1,1,0],dtype=bool))
array([ 94871006750048, 140541164903200,              -2,              -3,
                    48])
>>> x = np.arange(5)
>>> np.negative(x,out=x, where=np.array([0,0,1,1,0],dtype=bool))
array([ 0,  1, -2, -3,  4])

Thus, np.negative does not act as a mathematical function, which is confusing and error-prone. It's hard to imagine that a user would ever want uninitialized values in the returned values.

A quick search on github reveals that there are thousands (if not hundreds of thousands) of instances where code introduces uninitialized values due to calls to np.negative: https://github.com/search?q=%22np.negative%22&type=Code

Based on reading #7158, I believe this issue applies to all ufunc. I understand in #11086 that the documentation was improved, but I wanted to open this issue to highlight both the danger of uninitialized values being returned, as well as how frequently in the wild these uninitialized values are silently corrupting data.

Numpy/Python version information:

>>> import sys, numpy; print(numpy.__version__, sys.version)
1.19.1 3.8.5 (default, Aug  5 2020, 08:36:46) 
[GCC 7.3.0]

(Edit) Proposed default behavior

>>> def wrapped_negative(x, out=None, where=True):
...  if (out is None):
...    temp = x[()]
...  else:
...    temp = out
...  return np.negative(x, out=temp, where=where)
>>> wrapped_negative(np.arange(5), where=np.array([0,0,1,1,0],dtype=bool))
array([ 0,  1, -2, -3,  4])

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions