Search code examples
pythonnumpyvectorization

Vectorized function fails to evaluate


I'm having troubles with a vectorized function:

def func(y):
    return sum(x**(-y) for x in range(1, 10))
vect_func = np.vectorize(func)
vect_func([1, 2, 3, 4, 5])

# Output:
# ValueError: Integers to negative integer powers are not allowed.

Whereas the following works just fine:

[func(t) for t in [1, 2, 3, 4, 5]]

# Output:
# [2.8289682539682537, 1.5397677311665408, 1.1965319856741932, 1.0819365834937567, 1.0368973413446938]

Is it possible to get np.vectorize to work on this example?


Solution

  • When a function doesn't work in np.vectorize, it's a good idea to verify what the argument is.

    Let's add a type print to the function:

    In [36]: def func(y):
        ...:     print(type(y))
        ...:     return sum(x**(-y) for x in range(1, 10))
        ...: 
    

    In the list comprehension, Python int are passed:

    In [37]: func(3)
    <class 'int'>
    Out[37]: 1.1965319856741932
    

    with the vectorized version:

    In [40]: vect_func(3)
    <class 'numpy.int64'>
    Traceback (most recent call last):
      File "<ipython-input-40-65e2816d8003>", line 1, in <module>
        vect_func(3)
      File "/usr/local/lib/python3.8/dist-packages/numpy/lib/function_base.py", line 2163, in __call__
        return self._vectorize_call(func=func, args=vargs)
      File "/usr/local/lib/python3.8/dist-packages/numpy/lib/function_base.py", line 2241, in _vectorize_call
        ufunc, otypes = self._get_ufunc_and_otypes(func=func, args=args)
      File "/usr/local/lib/python3.8/dist-packages/numpy/lib/function_base.py", line 2201, in _get_ufunc_and_otypes
        outputs = func(*inputs)
      File "<ipython-input-36-311d525a60ba>", line 3, in func
        return sum(x**(-y) for x in range(1, 10))
      File "<ipython-input-36-311d525a60ba>", line 3, in <genexpr>
        return sum(x**(-y) for x in range(1, 10))
    ValueError: Integers to negative integer powers are not allowed.
    

    y is not a python int, it is a numpy int.

    In [41]: func(np.int64(3))
    <class 'numpy.int64'>
    Traceback (most recent call last):
      File "<ipython-input-41-c34830937ffd>", line 1, in <module>
        func(np.int64(3))
      File "<ipython-input-36-311d525a60ba>", line 3, in func
        return sum(x**(-y) for x in range(1, 10))
      File "<ipython-input-36-311d525a60ba>", line 3, in <genexpr>
        return sum(x**(-y) for x in range(1, 10))
    ValueError: Integers to negative integer powers are not allowed.
    

    If we deliberately pass Python int it works:

    In [42]: vect_func(np.array([1,2,3], object))
    <class 'int'>
    <class 'int'>
    <class 'int'>
    <class 'int'>
    Out[42]: array([2.82896825, 1.53976773, 1.19653199])
    

    np.vectorize can be handy when passing several arrays to a scalar function, and you want to take advantage of broadcasting. But for a list, or lists that can be zipped, it doesn't add anything to a list comprehension - not even speed.

    And as this case illustrates, it has some gotchas than can catch the novice (and even more experienced users). Here it's the nature of the input. For many other SO questions, it's nature of the return value (the automatic otypes).