Repository: scikit-hep/vector

Issue:
Summation of Vectors (defined from px,py,pz,M data)  does not work as expected
### Vector Version

1.1.0

### Python Version

3.11

### OS / Environment

Mac OS

### Describe the bug

I had a (I thought) fairly simple use case

* generate events with 10 particles iwth random momenta for particle of known mass 1
* determine the total 4 vector

and I expected this to work

```
p = np.random.uniform(size = (123,10,3))
m = np.zeros((123,10,1))
V = np.concatenate([p,m],axis=-1)

vecs = vector.array({
    'px': V[:,:,0],
    'py': V[:,:,1],
    'pz': V[:,:,2],
    'M': V[:,:,3]
})

vecs.sum(axis=1)
```

but I got this error message

```
File ~/.pyenv/versions/3.11.4/envs/garbargeml/lib/python3.11/site-packages/numpy/core/fromnumeric.py:88, in _wrapreduction(obj, ufunc, method, axis, dtype, out, **kwargs)
     85         else:
     86             return reduction(axis=axis, out=out, **passkwargs)
---> 88 return ufunc.reduce(obj, axis, dtype, out, **passkwargs)

TypeError: 'NoneType' object cannot be interpreted as an integer
```

To me that seems like sth that would be in scope for `vector`

@jpivarski came up with this solution using awkward, but this should not require awkward

```
>>> import awkward as ak, numpy as np
>>> import vector
>>> vector.register_awkward()

>>> p = np.random.uniform(size = (123,10,3))
>>> m = np.zeros((123,10,1))
>>> V = np.concatenate([p,m],axis=-1)

>>> vecs = ak.zip({
...     'px': V[:,:,0],
...     'py': V[:,:,1],
...     'pz': V[:,:,2],
...     'M': V[:,:,3]
... }, with_name="Momentum4D")

>>> ak.sum(vecs, axis=1)
<MomentumArray4D [{t: 7.77, z: 3.4, x: 3.08, ...}, ...] type='123 * Momentu...'>
```

### Any additional but relevant log output

_No response_

Discussion:
Saransh-cpp: Thanks for the review!

> Does Vector implement any other reducers? If so, the same mistake might be made there, too.

Yes, just double checked, none of them makes the same mistake.

I'll create a quick bug-fix release after merging this PR.

Patch:
diff --git a/src/vector/backends/numpy.py b/src/vector/backends/numpy.py
index 2060228..a64d08b 100644
--- a/src/vector/backends/numpy.py
+++ b/src/vector/backends/numpy.py
@@ -68,7 +68,7 @@ def _reduce_sum(
     axis: int | None = None,
     dtype: typing.Any = None,
     out: typing.Any = None,
-    keepdims: bool | None = None,
+    keepdims: bool = False,
     initial: typing.Any = None,
     where: typing.Any = None,
 ) -> T:
@@ -753,7 +753,7 @@ class VectorNumpy(Vector, GetItem):
         axis: int | None = None,
         dtype: numpy.dtype[typing.Any] | str | None = None,
         out: ArrayLike | None = None,
-        keepdims: bool | None = None,
+        keepdims: bool = False,
         initial: typing.Any = None,
         where: typing.Any = None,
     ) -> SameVectorNumpyType:


Test patch:
diff --git a/tests/backends/test_numpy.py b/tests/backends/test_numpy.py
index d7bc78a..0eea351 100644
--- a/tests/backends/test_numpy.py
+++ b/tests/backends/test_numpy.py
@@ -247,7 +247,7 @@ def test_sum_2d():
         [[(1, 0.1), (4, 0.2), (0, 0)], [(1, 0.3), (4, 0.4), (1, 0.1)]],
         dtype=[("rho", numpy.float64), ("phi", numpy.float64)],
     )
-    assert numpy.sum(v, axis=0, keepdims=True).allclose(
+    assert numpy.sum(v, axis=0, keepdims=False).allclose(
         vector.VectorNumpy2D(
             [
                 [
@@ -269,6 +269,20 @@ def test_sum_2d():
             dtype=[("x", numpy.float64), ("y", numpy.float64)],
         )
     )
+    assert numpy.sum(v, axis=0).allclose(
+        vector.VectorNumpy2D(
+            [
+                (1.950340654403632, 0.3953536233081677),
+                (7.604510287376507, 2.3523506924148467),
+                (0.9950041652780258, 0.09983341664682815),
+            ],
+            dtype=[("x", numpy.float64), ("y", numpy.float64)],
+        )
+    )
+    assert numpy.sum(v, axis=0, keepdims=False).shape == (3,)
+    assert numpy.sum(v, axis=0, keepdims=True).shape == (1, 3)
+    assert numpy.sum(v, axis=0).shape == (3,)
+
     assert numpy.sum(v, axis=1, keepdims=True).allclose(
         vector.VectorNumpy2D(
             [[(4.91527048, 0.89451074)], [(5.63458463, 1.95302699)]],
@@ -281,6 +295,15 @@ def test_sum_2d():
             dtype=[("x", numpy.float64), ("y", numpy.float64)],
         )
     )
+    assert numpy.sum(v, axis=1).allclose(
+        vector.VectorNumpy2D(
+            [(4.91527048, 0.89451074), (5.63458463, 1.95302699)],
+            dtype=[("x", numpy.float64), ("y", numpy.float64)],
+        )
+    )
+    assert numpy.sum(v, axis=1, keepdims=False).shape == (2,)
+    assert numpy.sum(v, axis=1, keepdims=True).shape == (2, 1)
+    assert numpy.sum(v, axis=1).shape == (2,)
 
 
 def test_sum_3d():
@@ -317,6 +340,20 @@ def test_sum_3d():
             dtype=[("x", numpy.float64), ("y", numpy.float64), ("z", numpy.float64)],
         )
     )
+    assert numpy.sum(v, axis=0).allclose(
+        vector.VectorNumpy3D(
+            [
+                (2.0, 4.0, 25.55454594),
+                (8.0, 10.0, 33.36521103),
+                (1.0, 1.0, -0.48314535),
+            ],
+            dtype=[("x", numpy.float64), ("y", numpy.float64), ("z", numpy.float64)],
+        )
+    )
+    assert numpy.sum(v, axis=0, keepdims=False).shape == (3,)
+    assert numpy.sum(v, axis=0, keepdims=True).shape == (1, 3)
+    assert numpy.sum(v, axis=0).shape == (3,)
+
     assert numpy.sum(v, axis=1, keepdims=True).allclose(
         vector.VectorNumpy3D(
             [[(5.0, 7.0, 53.87369799)], [(6.0, 8.0, 4.56291362)]],
@@ -329,6 +366,15 @@ def test_sum_3d():
             dtype=[("x", numpy.float64), ("y", numpy.float64), ("z", numpy.float64)],
         )
     )
+    assert numpy.sum(v, axis=1).allclose(
+        vector.VectorNumpy3D(
+            [(5.0, 7.0, 53.87369799), (6.0, 8.0, 4.56291362)],
+            dtype=[("x", numpy.float64), ("y", numpy.float64), ("z", numpy.float64)],
+        )
+    )
+    assert numpy.sum(v, axis=1, keepdims=False).shape == (2,)
+    assert numpy.sum(v, axis=1, keepdims=True).shape == (2, 1)
+    assert numpy.sum(v, axis=1).shape == (2,)
 
 
 def test_sum_4d():
@@ -352,6 +398,15 @@ def test_sum_4d():
         (8, 10, 12, 2),
         (1, 1, 1, 3),
     ]
+    assert numpy.sum(v, axis=0).tolist() == [
+        (2, 4, 6, 12),
+        (8, 10, 12, 2),
+        (1, 1, 1, 3),
+    ]
+    assert numpy.sum(v, axis=0, keepdims=False).shape == (3,)
+    assert numpy.sum(v, axis=0, keepdims=True).shape == (1, 3)
+    assert numpy.sum(v, axis=0).shape == (3,)
+
     assert numpy.sum(v, axis=1, keepdims=True).tolist() == [
         [(5, 7, 9, 9)],
         [(6, 8, 10, 8)],
@@ -360,6 +415,13 @@ def test_sum_4d():
         (5, 7, 9, 9),
         (6, 8, 10, 8),
     ]
+    assert numpy.sum(v, axis=1).tolist() == [
+        (5, 7, 9, 9),
+        (6, 8, 10, 8),
+    ]
+    assert numpy.sum(v, axis=1, keepdims=False).shape == (2,)
+    assert numpy.sum(v, axis=1, keepdims=True).shape == (2, 1)
+    assert numpy.sum(v, axis=1).shape == (2,)
 
 
 def test_count_nonzero_2d():