Repository: dask/dask

Issue:
Transpose multidimensional array
Hi, I was wondering if there was any way to transpose a multidimensional array.  Unfortunately I have to work with tensors with fairly long dimensions and transposing is a necessity.

```
import numpy as np
from distributed import Client
import dask.dataframe as dd
import dask.array as da

nocc = 20
nvir = 4
nkpts = 8
nkpts_blk = 2
oovv   = da.ones((nkpts,nkpts,nkpts,nocc,nocc,nvir,nvir),chunks=(nkpts_blk,nkpts_blk,nkpts_blk,nocc,nocc,nvir,nvir))
```

In numpy I can do something like 
`oovvt = oovv.transpose(0,1,2,3,4,6,5)`
but this doesn't seem to work.  It's across an axis that isn't "chunked" per-say, so it wouldn't be that terrible to do.



Discussion:
Transpose is implemented.  However it looks like it expects a list rather than splatted out entries.  

``` python
In [1]: import dask.array as da

In [2]: nocc = 20
   ...: nvir = 4
   ...: nkpts = 8
   ...: nkpts_blk = 2
   ...: oovv   = da.ones((nkpts,nkpts,nkpts,nocc,nocc,nvir,nvir),chunks=(nkpts_b
   ...: lk,nkpts_blk,nkpts_blk,nocc,nocc,nvir,nvir))
   ...: 

In [3]: oovv
Out[3]: dask.array<wrapped..., shape=(8, 8, 8, 20, 20, 4, 4), dtype=float64, chunksize=(2, 2, 2, 20, 20, 4, 4)>

In [4]: oovv.transpose(axes=[0, 1, 2, 3, 4, 6, 5])
Out[4]: dask.array<transpo..., shape=(8, 8, 8, 20, 20, 4, 4), dtype=float64, chunksize=(2, 2, 2, 20, 20, 4, 4)>
```

This API mismatch looks like a bug to me.

NumPy accepts both forms... sigh... http://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.transpose.html


Resolving patch:
diff --git a/dask/array/core.py b/dask/array/core.py
--- a/dask/array/core.py
+++ b/dask/array/core.py
@@ -25,7 +25,7 @@
 from . import numpy_compat
 from ..base import Base, tokenize, normalize_token
 from ..utils import (deepmap, ignoring, concrete, is_integer,
-                     IndexCallable, funcname)
+                     IndexCallable, funcname, derived_from)
 from ..compatibility import unicode, long, getargspec, zip_longest, apply
 from .. import threaded, core
 
@@ -1145,9 +1145,13 @@ def A(self):
     def T(self):
         return transpose(self)
 
-    @wraps(np.transpose)
-    def transpose(self, axes=None):
-        return transpose(self, axes)
+    @derived_from(np.ndarray)
+    def transpose(self, *axes):
+        if not axes:
+            axes = None
+        elif len(axes) == 1 and isinstance(axes[0], Iterable):
+            axes = axes[0]
+        return transpose(self, axes=axes)
 
     @wraps(np.ravel)
     def ravel(self):
@@ -2110,7 +2114,11 @@ def _take_dask_array_from_numpy(a, indices, axis):
 
 @wraps(np.transpose)
 def transpose(a, axes=None):
-    axes = axes or tuple(range(a.ndim))[::-1]
+    if axes:
+        if len(axes) != a.ndim:
+            raise ValueError("axes don't match array")
+    else:
+        axes = tuple(range(a.ndim))[::-1]
     return atop(partial(np.transpose, axes=axes),
                 axes,
                 a, tuple(range(a.ndim)), dtype=a._dtype)
diff --git a/dask/utils.py b/dask/utils.py
--- a/dask/utils.py
+++ b/dask/utils.py
@@ -556,10 +556,13 @@ def wrapper(method):
             if doc is None:
                 doc = ''
 
-            method_args = getargspec(method).args
-            original_args = getargspec(original_method).args
+            try:
+                method_args = getargspec(method).args
+                original_args = getargspec(original_method).args
+                not_supported = [m for m in original_args if m not in method_args]
+            except TypeError:
+                not_supported = []
 
-            not_supported = [m for m in original_args if m not in method_args]
             if len(ua_args) > 0:
                 not_supported.extend(ua_args)
 


Test patch:
diff --git a/dask/array/tests/test_array_core.py b/dask/array/tests/test_array_core.py
--- a/dask/array/tests/test_array_core.py
+++ b/dask/array/tests/test_array_core.py
@@ -144,6 +144,15 @@ def test_transpose():
               x.transpose((2, 0, 1)))
     assert same_keys(d.transpose((2, 0, 1)), d.transpose((2, 0, 1)))
 
+    assert_eq(d.transpose(2, 0, 1),
+              x.transpose(2, 0, 1))
+    assert same_keys(d.transpose(2, 0, 1), d.transpose(2, 0, 1))
+
+    with pytest.raises(ValueError):
+        d.transpose(1, 2)
+    with pytest.raises(ValueError):
+        d.transpose((1, 2))
+
 
 def test_broadcast_dimensions_works_with_singleton_dimensions():
     argpairs = [('x', 'i')]