Repository: sunpy/ndcube

Issue:
Provide a shorthand for "one bin along this axis" in .rebin
### Describe the feature

If you want to "drop" an axis by averaging along an axis then you need to know the length of that axis to do `rebin((nx, 540, 20))` it would be nice to provide a shorthand for this, I propose a -1 sentinel so for a cube with dimensions `(100, 100, 100)` `.rebin((-1, -1, 50))` would give you a cube with shape `(1, 1, 2)`.

### Proposed solution

_No response_

Discussion:
DanRyanIrish: Great!  Write a test and let's get it on!

Patch:
diff --git a/changelog/777.feature.rst b/changelog/777.feature.rst
new file mode 100644
index 0000000..de009a6
--- /dev/null
+++ b/changelog/777.feature.rst
@@ -0,0 +1,1 @@
+Add support for passing ``-1`` as an element of the ``bin_shape`` argument to `.NDCube.rebin` which will not rebin along that dimension (sets the bin shape to equal the data shape).
diff --git a/ndcube/ndcube.py b/ndcube/ndcube.py
index a9827b4..b5d056e 100644
--- a/ndcube/ndcube.py
+++ b/ndcube/ndcube.py
@@ -1029,6 +1029,8 @@ class NDCube(NDCubeBase):
             Each element must be in int. If they are not they will be rounded
             to the nearest int. If provided as a `~astropy.units.Quantity` the
             units have to be convertible to pixels.
+            The sentinel value ``-1`` can be passed for a dimension which means
+            that no rebinning will occur along that dimension.
         operation : function
             Function applied to the data to derive values of the bins.
             Default is `numpy.mean`
@@ -1139,10 +1141,14 @@ class NDCube(NDCubeBase):
         naxes = len(data_shape)
         if len(bin_shape) != naxes:
             raise ValueError("bin_shape must have an entry for each array axis.")
+        bin_shape[bin_shape == -1] = np.array(data_shape)[bin_shape == -1]
+        if (bin_shape < 0).any():
+            raise ValueError("bin_shape should not be less than -1.")
         if (np.mod(data_shape, bin_shape) != 0).any():
             raise ValueError(
                 "bin shape must be an integer fraction of the data shape in each dimension. "
-                f"data shape: {data_shape};  bin shape: {bin_shape}")
+                f"data shape: {data_shape};  bin shape: {bin_shape}"
+            )
 
         # Reshape array so odd dimensions represent pixels to be binned
         # then apply function over those axes.


Test patch:
diff --git a/ndcube/tests/test_ndcube.py b/ndcube/tests/test_ndcube.py
index 5d8a258..81b2308 100644
--- a/ndcube/tests/test_ndcube.py
+++ b/ndcube/tests/test_ndcube.py
@@ -811,9 +811,9 @@ def test_wcs_type_after_init(ndcube_3d_ln_lt_l, wcs_3d_l_lt_ln):
     assert isinstance(cube.wcs, BaseHighLevelWCS)
 
 
-def test_rebin(ndcube_3d_l_ln_lt_ectime):
+@pytest.mark.parametrize("bin_shape", [(10, 2, 1), (-1, 2, 1)])
+def test_rebin(ndcube_3d_l_ln_lt_ectime, bin_shape):
     cube = ndcube_3d_l_ln_lt_ectime[:, 1:]
-    bin_shape = (10, 2, 1)
     with pytest.warns(NDCubeUserWarning, match="The uncertainty on this NDCube has no known way to propagate forward"):
         output = cube.rebin(bin_shape, operation=np.sum, propagate_uncertainties=True)
     output_sc, output_spec = output.axis_world_coords(wcs=output.wcs)