Repository: numba/numba

Issue:
Sum with axis argument on 1d array returns array instead of skalar
When numba performs a sum reduction with specified axis parameter on a 1d array it returns again an array. However numpy returns a skalar for the same case.

Would you consider this a bug?

Best,
Peter

Example(tested on numba from git master):

```
import numpy as np
import numba

@numba.njit
def numba_sum():
    return np.array([1,2,3]).sum(axis=-1)

print('Expected: {}'.format(
        type(np.array([1,2,3]).sum(axis=0))
        ))
print('Numba jitted: {}'.format(
        type(numba_sum())
        ))

Expected: <class 'numpy.int64'>
Numba jitted: <class 'numpy.ndarray'>
```


Discussion:
Yes, this should be considered a bug.  Usually when someone specifies an axis, it is for a higher dimensional array, so the return is also an array.  We need to special case the 1D situation to return a scalar.
Thanks for the report. This is a bug.

```python
import numpy as np
import numba

@numba.njit
def f():
    return np.array([1,2,3]).sum(axis=-1)

print(f(), type(f()))
print(f.py_func(), type(f.py_func()))
```
gives
```
6 <class 'numpy.ndarray'>
6 <class 'numpy.int64'>
```

Resolving patch:
diff --git a/numba/targets/arraymath.py b/numba/targets/arraymath.py
--- a/numba/targets/arraymath.py
+++ b/numba/targets/arraymath.py
@@ -167,6 +167,10 @@ def array_sum_impl(arr):
                                     locals=dict(c=sig.return_type))
     return impl_ret_borrowed(context, builder, sig.return_type, res)
 
+@register_jitable
+def _array_sum_axis_nop(arr, v):
+    return arr
+
 @lower_builtin(np.sum, types.Array, types.intp)
 @lower_builtin(np.sum, types.Array, types.IntegerLiteral)
 @lower_builtin("array.sum", types.Array, types.intp)
@@ -181,8 +185,15 @@ def array_sum_axis(context, builder, sig, args):
     """
     # typing/arraydecl.py:sum_expand defines the return type for sum with axis.
     # It is one dimension less than the input array.
-    zero = sig.return_type.dtype(0)
 
+    retty = sig.return_type
+    zero = getattr(retty, 'dtype', retty)(0)
+    # if the return is scalar in type then "take" the 0th element of the
+    # 0d array accumulator as the return value
+    if getattr(retty, 'ndim', None) is None:
+        op = np.take
+    else:
+        op = _array_sum_axis_nop
     [ty_array, ty_axis] = sig.args
     is_axis_const = False
     const_axis_val = 0
@@ -209,7 +220,7 @@ def array_sum_impl_axis(arr, axis):
         if not is_axis_const:
             # Catch where axis is negative or greater than 3.
             if axis < 0 or axis > 3:
-                raise ValueError("Numba does not support sum with axis"
+                raise ValueError("Numba does not support sum with axis "
                                  "parameter outside the range 0 to 3.")
 
         # Catch the case where the user misspecifies the axis to be
@@ -252,7 +263,7 @@ def array_sum_impl_axis(arr, axis):
                     index_tuple4 = _gen_index_tuple(arr.shape, axis_index, 3)
                     result += arr[index_tuple4]
 
-        return result
+        return op(result, 0)
 
     res = context.compile_internal(builder, array_sum_impl_axis, sig, args)
     return impl_ret_new_ref(context, builder, sig.return_type, res)
diff --git a/numba/typing/arraydecl.py b/numba/typing/arraydecl.py
--- a/numba/typing/arraydecl.py
+++ b/numba/typing/arraydecl.py
@@ -625,10 +625,15 @@ def sum_stub(axis):
         out = signature(_expand_integer(self.this.dtype), *args,
                         recvr=self.this)
     else:
-        # There is an axis paramter so the return type of this summation is
-        # an array of dimension one less than the input array.
-        return_type = types.Array(dtype=_expand_integer(self.this.dtype),
-                                  ndim=self.this.ndim-1, layout='C')
+        # There is an axis parameter
+        if self.this.ndim == 1:
+            # 1d reduces to a scalar
+            return_type = self.this.dtype
+        else:
+            # the return type of this summation is  an array of dimension one
+            # less than the input array.
+            return_type = types.Array(dtype=_expand_integer(self.this.dtype),
+                                    ndim=self.this.ndim-1, layout='C')
         out = signature(return_type, *args, recvr=self.this)
     return out.replace(pysig=pysig)
 


Test patch:
diff --git a/numba/tests/test_array_methods.py b/numba/tests/test_array_methods.py
--- a/numba/tests/test_array_methods.py
+++ b/numba/tests/test_array_methods.py
@@ -165,6 +165,11 @@ def array_sum_const_multi(arr, axis):
     e = np.sum(arr, axis=-1)
     return a, b, c, d, e
 
+def array_sum_const_axis_neg_one(a, axis):
+    # use .sum with -1 axis, this is for use with 1D arrays where the above
+    # "const_multi" variant would raise errors
+    return a.sum(axis=-1)
+
 def array_cumsum(a, *args):
     return a.cumsum(*args)
 
@@ -778,6 +783,17 @@ def test_sum_kws(self):
         # OK
         self.assertPreciseEqual(pyfunc(a, axis=2), cfunc(a, axis=2))
 
+    def test_sum_1d_kws(self):
+        # check 1d reduces to scalar
+        pyfunc = array_sum_kws
+        cfunc = jit(nopython=True)(pyfunc)
+        a = np.arange(10.)
+        self.assertPreciseEqual(pyfunc(a, axis=0), cfunc(a, axis=0))
+        pyfunc = array_sum_const_axis_neg_one
+        cfunc = jit(nopython=True)(pyfunc)
+        a = np.arange(10.)
+        self.assertPreciseEqual(pyfunc(a, axis=-1), cfunc(a, axis=-1))
+
     def test_sum_const(self):
         pyfunc = array_sum_const_multi
         cfunc = jit(nopython=True)(pyfunc)