Repository: numpy/numpy

Issue:
Behaviour change in 1.13 for expand_dims
From the mailing list, this used to succeed, but now fails, thanks to [these lines](https://github.com/numpy/numpy/pull/8584/files#diff-ba13cf8822527da9907ca1822369030dR290) in #8584.

```python
x = np.zeros((2, 3, 4))
np.expand_dims(x, 20)
```

This failing absolutely seems like correct behaviour to me - unfortunately, correctness is not the metric we get to use, as this breaks matplotlib (matplotlib/matplotlib#8610).

Evidently, we need to restore the old behaviour, but with a deprecation warning


Discussion:
For anyone else confused by this: it sounds like the old behavior was not to insert multiple dimensions so that the resulting array indeed had 20 dimensions, but rather to silently pretend the call said `np.expand_dims(a, a.ndim)`. Definitely a bit surprising and error prone.

Also definitely something that needs a nice long deprecation cycle to fix though...
@eric-wieser Has this been fixed?
No. Would appreciate if someone else could take this on. Should be a simple fix to limit the input to a.ndim

Resolving patch:
diff --git a/numpy/lib/shape_base.py b/numpy/lib/shape_base.py
--- a/numpy/lib/shape_base.py
+++ b/numpy/lib/shape_base.py
@@ -240,14 +240,20 @@ def expand_dims(a, axis):
     """
     Expand the shape of an array.
 
-    Insert a new axis, corresponding to a given position in the array shape.
+    Insert a new axis that will appear at the `axis` position in the expanded
+    array shape.
+
+    .. note:: Previous to NumPy 1.13.0, neither ``axis < -a.ndim - 1`` nor
+       ``axis > a.ndim`` raised errors or put the new axis where documented.
+       Those axis values are now deprecated and will raise an AxisError in the
+       future.
 
     Parameters
     ----------
     a : array_like
         Input array.
     axis : int
-        Position (amongst axes) where new axis is to be inserted.
+        Position in the expanded axes where the new axis is placed.
 
     Returns
     -------
@@ -291,7 +297,16 @@ def expand_dims(a, axis):
     """
     a = asarray(a)
     shape = a.shape
-    axis = normalize_axis_index(axis, a.ndim + 1)
+    if axis > a.ndim or axis < -a.ndim - 1:
+        # 2017-05-17, 1.13.0
+        warnings.warn("Both axis > a.ndim and axis < -a.ndim - 1 are "
+                      "deprecated and will raise an AxisError in the future.",
+                      DeprecationWarning, stacklevel=2)
+    # When the deprecation period expires, delete this if block,
+    if axis < 0:
+        axis = axis + a.ndim + 1
+    # and uncomment the following line.
+    # axis = normalize_axis_index(axis, a.ndim + 1)
     return a.reshape(shape[:axis] + (1,) + shape[axis:])
 
 row_stack = vstack


Test patch:
diff --git a/numpy/lib/tests/test_shape_base.py b/numpy/lib/tests/test_shape_base.py
--- a/numpy/lib/tests/test_shape_base.py
+++ b/numpy/lib/tests/test_shape_base.py
@@ -1,9 +1,11 @@
 from __future__ import division, absolute_import, print_function
 
 import numpy as np
+import warnings
+
 from numpy.lib.shape_base import (
     apply_along_axis, apply_over_axes, array_split, split, hsplit, dsplit,
-    vsplit, dstack, column_stack, kron, tile
+    vsplit, dstack, column_stack, kron, tile, expand_dims,
     )
 from numpy.testing import (
     run_module_suite, TestCase, assert_, assert_equal, assert_array_equal,
@@ -182,6 +184,25 @@ def test_simple(self):
         assert_array_equal(aoa_a, np.array([[[60], [92], [124]]]))
 
 
+class TestExpandDims(TestCase):
+    def test_functionality(self):
+        s = (2, 3, 4, 5)
+        a = np.empty(s)
+        for axis in range(-5, 4):
+            b = expand_dims(a, axis)
+            assert_(b.shape[axis] == 1)
+            assert_(np.squeeze(b).shape == s)
+
+    def test_deprecations(self):
+        # 2017-05-17, 1.13.0
+        s = (2, 3, 4, 5)
+        a = np.empty(s)
+        with warnings.catch_warnings():
+            warnings.simplefilter("always")
+            assert_warns(DeprecationWarning, expand_dims, a, -6)
+            assert_warns(DeprecationWarning, expand_dims, a, 5)
+
+
 class TestArraySplit(TestCase):
     def test_integer_0_split(self):
         a = np.arange(10)