Repository: dask/dask

Issue:
Dask.array deepcopy does not preserve masking since 0.18.2
If 'a' is a dask array wrapped around a numpy masked array (with "from _array(..., asarray=False)",
then the result of "copy.deepcopy(a).compute()" is now (wrongly) **not** a masked array,

Though the original "a.compute()", and even "copy.copy(a)" are correct.

Example code to show :
```
import dask.array as da
import numpy.ma as ma
import copy
t = ma.masked_array([1, 2], mask=[0, 1])
a = da.from_array(t, chunks=t.shape, asarray=False)
print(copy.deepcopy(a).compute())
```

For example, in dask 0.18.1
```
>>> import dask
>>> import dask.array as da
>>> import numpy.ma as ma
>>> import copy
>>> t = ma.masked_array([1, 2], mask=[0, 1])
>>> a = da.from_array(t, chunks=t.shape, asarray=False)
>>> print(dask.__version__)
0.18.1
>>> print(copy.copy(a).compute())
[1 --]
>>> print(copy.deepcopy(a).compute())
[1 --]
>>> 
```

But in 0.18.2 ...
```
>>> import dask
>>> import dask.array as da
>>> import numpy.ma as ma
>>> import copy
>>> 
>>> t = ma.masked_array([1, 2], mask=[0, 1])
>>> a = da.from_array(t, chunks=t.shape, asarray=False)
>>> 
>>> print(dask.__version__)
0.18.2
>>> print(copy.copy(a).compute())
[1 --]
>>> print(copy.deepcopy(a).compute())
[1 2]
>>> 
```


Discussion:
This is causing us to [pin dask in iris](https://github.com/SciTools/iris/pull/3127) :cry: 
as it breaks various unit tests for us.

I suspect the change [here](https://github.com/dask/dask/pull/3754/files#r207583443), but I don't know really enough to suggest a fix ... 
( but surely this anyway does not respect the comment on the previous line, as noted ? )
cc @jcrist

On Fri, Aug 3, 2018 at 11:32 AM, Patrick Peglar <notifications@github.com>
wrote:

> This has caused us to pin dask in iris <https://github.com/SciTools/iris>
> 😢
> as it breaks various unit tests for us.
>
> I suspect the change here
> <https://github.com/dask/dask/pull/3754/files#r207583443>, but I don't
> know really enough to suggest a fix ... but surely this did not respect the
> comment anyway ?
>
> —
> You are receiving this because you are subscribed to this thread.
> Reply to this email directly, view it on GitHub
> <https://github.com/dask/dask/issues/3848#issuecomment-410290664>, or mute
> the thread
> <https://github.com/notifications/unsubscribe-auth/AASszOdhXSMxZJ5q2rlYeXCsOgYehTD9ks5uNG0lgaJpZM4VuMMc>
> .
>

Looks like the `np.copy` in the `map_blocks` should be replaced with `dask.utils.M.copy` (which would call the `.copy` method on the array).

Resolving patch:
diff --git a/dask/array/core.py b/dask/array/core.py
--- a/dask/array/core.py
+++ b/dask/array/core.py
@@ -37,7 +37,7 @@
 from ..utils import (homogeneous_deepmap, ndeepmap, ignoring, concrete,
                      is_integer, IndexCallable, funcname, derived_from,
                      SerializableLock, ensure_dict, Dispatch, factors,
-                     parse_bytes, has_keyword)
+                     parse_bytes, has_keyword, M)
 from ..compatibility import (unicode, long, zip_longest, apply,
                              Iterable, Iterator, Mapping)
 from ..core import quote
@@ -1972,7 +1972,7 @@ def copy(self):
         Copy array.  This is a no-op for dask.arrays, which are immutable
         """
         if self.npartitions == 1:
-            return self.map_blocks(np.copy)
+            return self.map_blocks(M.copy)
         else:
             return Array(self.dask, self.name, self.chunks, self.dtype)
 


Test patch:
diff --git a/dask/array/tests/test_masked.py b/dask/array/tests/test_masked.py
--- a/dask/array/tests/test_masked.py
+++ b/dask/array/tests/test_masked.py
@@ -7,6 +7,7 @@
 import dask.array as da
 from dask.base import tokenize
 from dask.array.utils import assert_eq
+from copy import deepcopy
 
 pytest.importorskip("dask.array.ma")
 
@@ -28,6 +29,23 @@ def test_from_array_masked_array():
     assert_eq(dm, m)
 
 
+def test_copy_deepcopy():
+    t = np.ma.masked_array([1, 2], mask=[0, 1])
+    x = da.from_array(t, chunks=t.shape, asarray=False)
+    #x = da.arange(5, chunks=(2,))
+    y = x.copy()
+    memo = {}
+    y2 = deepcopy(x, memo=memo)
+
+    xx = da.ma.masked_where([False, True], [1,2])
+    assert_eq(x, xx)
+
+    assert_eq(y, t)
+    assert isinstance(y.compute(), np.ma.masked_array)
+    assert_eq(y2, t)
+    assert isinstance(y2.compute(), np.ma.masked_array)
+
+
 functions = [
     lambda x: x,
     lambda x: da.expm1(x),