Search code examples
pythonarraysnumpycopy

How to test whether numpy array slice is a copy in multidim case?


According to numpy documentation, advanced indexing is supposed to return a copy of the array. This works as expected in the one-dim case. However, if array is multidimensional, then numpy does not recognize it as a copy. How can I test whether a copy or view is returned in multidim case if I don't have access to the original array?

Example:

import numpy as np

y = np.ones((1, 10))
b = y[:, [0, 5, 6]]

# b is not a copy
assert not b.flags['OWNDATA']
assert b.base is not None

But it actually is a copy

b[:] = 33
print(y)

Solution

  • lets make y a bit more interesting:

    In [915]: y = np.arange(10).reshape(2,5);y, y.base
    Out[915]: 
    (array([[0, 1, 2, 3, 4],
            [5, 6, 7, 8, 9]]),
     array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]))
    

    It is a view of a 1d array.

    Now use advanced indexing:

    In [916]: b = y[:,[0,2,4]];b, b.base
    Out[916]: 
    (array([[0, 2, 4],
            [5, 7, 9]]),
     array([[0, 5],
            [2, 7],
            [4, 9]]))
    
    In [917]: b.strides, b.base.strides
    Out[917]: ((4, 8), (8, 4))
    

    b is a (2,3) array, as we expect from the indexing, but it is actually a transpose of a (3,2) array. Evidently numpy is selecting values using the list index, the 2nd dimension, and putting the slice dimension last. Then it transposes to get the desired shape. This hints at why mixing advanced and basic indexing can produce unexpected shapes (as documented and explained in other SO).

    In any case, I don't see a way of testing b to verify it isn't a view of y - except by comparing the base attributes. The simplest thing is to accept the documentation's claim that it is a copy - just not a 'direct' copy.

    We could get the same array with basic indexing - and get a view of y.base:

    In [918]: c = y[:,::2]; c, c.base
    Out[918]: 
    (array([[0, 2, 4],
            [5, 7, 9]]),
     array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]))
    

    A couple of other ways of getting a simple copy:

    In [924]: d = y[[0,1], ::2]; d, d.base
    Out[924]: 
    (array([[0, 2, 4],
            [5, 7, 9]]),
     None)
    
    In [925]: d = y[[[0],[1]], [0,2,4]]; d, d.base
    Out[925]: 
    (array([[0, 2, 4],
            [5, 7, 9]]),
     None)