Repository: statsmodels/statsmodels

Issue:
BUG: incorrect axis handling in statsmodels.tools.eval_measures.iqr
I get incorrect number when x1 is 2-dim as in exog and we want iqr for axis=0.

It works correctly for axis=1.

My guess is that the `sort` is missing the axis argument.

Do we need the function in this form?

The function chooses the index as rounded rank/quantile of the sorted array. i.e. it rounds and does not interpolate. 
IIRC, numpy percentile interpolates, as does median.



Discussion:

Resolving patch:
diff --git a/statsmodels/tools/eval_measures.py b/statsmodels/tools/eval_measures.py
--- a/statsmodels/tools/eval_measures.py
+++ b/statsmodels/tools/eval_measures.py
@@ -9,6 +9,8 @@
 """
 import numpy as np
 
+from statsmodels.tools.validation import array_like
+
 
 def mse(x1, x2, axis=0):
     """mean squared error
@@ -256,39 +258,34 @@ def stde(x1, x2, ddof=0, axis=0):
 
 
 def iqr(x1, x2, axis=0):
-    """interquartile range of error
-
-    rounded index, no interpolations
-
-    this could use newer numpy function instead
+    """
+    Interquartile range of error
 
     Parameters
     ----------
-    x1, x2 : array_like
-       The performance measure depends on the difference between these two
-       arrays.
-    axis : int
+    x1 : array_like
+       One of the inputs into the IQR calculation.
+    x2 : array_like
+       The other input into the IQR calculation.
+    axis : {None, int}
        axis along which the summary statistic is calculated
 
     Returns
     -------
-    mse : ndarray or float
-       mean squared error along given axis.
+    irq : {float, ndarray}
+       Interquartile range along given axis.
 
     Notes
     -----
-    If ``x1`` and ``x2`` have different shapes, then they need to broadcast.
-
-    This uses ``numpy.asarray`` to convert the input, in contrast to the other
-    functions in this category.
+    If ``x1`` and ``x2`` have different shapes, then they must broadcast.
     """
-    x1 = np.asarray(x1)
-    x2 = np.asarray(x2)
+    x1 = array_like(x1, 'x1', dtype=None, ndim=None)
+    x2 = array_like(x2, 'x1', dtype=None, ndim=None)
     if axis is None:
-        x1 = np.ravel(x1)
-        x2 = np.ravel(x2)
+        x1 = x1.ravel()
+        x2 = x2.ravel()
         axis = 0
-    xdiff = np.sort(x1 - x2)
+    xdiff = np.sort(x1 - x2, axis=axis)
     nobs = x1.shape[axis]
     idx = np.round((nobs-1) * np.array([0.25, 0.75])).astype(int)
     sl = [slice(None)] * xdiff.ndim
diff --git a/statsmodels/tools/validation/validation.py b/statsmodels/tools/validation/validation.py
--- a/statsmodels/tools/validation/validation.py
+++ b/statsmodels/tools/validation/validation.py
@@ -35,7 +35,7 @@ def _right_squeeze(arr, stop_dim=0):
 def array_like(obj, name, dtype=np.double, ndim=1, maxdim=None,
                shape=None, order='C', contiguous=False, optional=False):
     """
-    Convert array-like to an array and check conditions
+    Convert array-like to a ndarray and check conditions
 
     Parameters
     ----------
@@ -64,6 +64,11 @@ def array_like(obj, name, dtype=np.double, ndim=1, maxdim=None,
     optional : bool
         Flag indicating whether None is allowed
 
+    Returns
+    -------
+    ndarray
+        The converted input.
+
     Examples
     --------
     Convert a list or pandas series to an array


Test patch:
diff --git a/statsmodels/tools/tests/test_eval_measures.py b/statsmodels/tools/tests/test_eval_measures.py
--- a/statsmodels/tools/tests/test_eval_measures.py
+++ b/statsmodels/tools/tests/test_eval_measures.py
@@ -101,3 +101,23 @@ def test_ic():
                         aic(0, n, k) + 2*k*(k+1.)/(n-k-1.), decimal=14)
     assert_almost_equal(bic(0, 10, 2), np.log(n)*k, decimal=14)
     assert_almost_equal(hqic(0, 10, 2), 2*np.log(np.log(n))*k, decimal=14)
+
+
+def test_iqr_axis(reset_randomstate):
+    x1 = np.random.standard_normal((100, 100))
+    x2 = np.random.standard_normal((100, 100))
+    ax_none = iqr(x1, x2, axis=None)
+    ax_none_direct = iqr(x1.ravel(), x2.ravel())
+    assert_equal(ax_none, ax_none_direct)
+
+    ax_0 = iqr(x1, x2, axis=0)
+    assert ax_0.shape == (100,)
+    ax_0_direct = [iqr(x1[:,i], x2[:,i]) for i in range(100)]
+    assert_almost_equal(ax_0, np.array(ax_0_direct))
+
+    ax_1 = iqr(x1, x2, axis=1)
+    assert ax_1.shape == (100,)
+    ax_1_direct = [iqr(x1[i,:], x2[i,:]) for i in range(100)]
+    assert_almost_equal(ax_1, np.array(ax_1_direct))
+
+    assert any(ax_0 != ax_1)