2

environment: Python 3.6.0 |Anaconda custom (64-bit), numpy version: 1.11.3
Example:

In[1]: import numpy as np
In[2]: a = np.array([[1,2,3], [4,5,6]])
In[3]: a
Out[4]: 
array([[1, 2, 3],
       [4, 5, 6]])
In[5]: a.transpose()[0] = -1
In[6]: a
Out[6]: 
array([[-1,  2,  3],
       [-1,  5,  6]])
In[7]: a.ravel()[0] = -2 
In[8]: a
Out[8]: 
array([[-2,  2,  3],
       [-1,  5,  6]])
In[9]: a.transpose().ravel()[0] = -3
In[10]: a
Out[10]: 
array([[-2,  2,  3],
       [-1,  5,  6]])

I know transpose() and ravel() return a view of array, so we can change its value of original array. However, when we use transpose().ravel(), we cannot change it? Why?

Ocxs
  • 149
  • 2
  • 10

2 Answers2

4

ravel is returning a copy, not a view

From the numpy.ravel docs:

A 1-D array, containing the elements of the input, is returned. A copy is made only if needed.

So basically, when ravelling the transpose, a copy is in fact needed. You're changing the value in a copy, so that's not reflected in the original array.

Testing whether a returned array is a view or a copy

For a simple case like this, you can test whether an array b is or is not a view of a by comparing the identity of b.base and a:

a = np.array([[1,2,3], [4,5,6]])
b = a.T
c = b.ravel()

print('b is a view of a\n%s\n' % (b.base is a))
print('c is a view of a\n%s\n' % (c.base is a))

Output:

b is a view of a
True

c is a view of a
False

Why does a.T.ravel() return a copy?

Shocker: there actually is a way to make a.T.ravel() return a view instead of a copy. You can do so by explicitly setting order='F' (ie Fortran order):

a = np.array([[1,2,3], [4,5,6]])
c = a.T.ravel()
d = a.T.ravel(order='F')

print('d is a view of a\n%s\n' % (d.base is a))

Output:

d is a view of a
True

However, changing the value of the order kwarg will change the order (fancy that) of the values in the raveled array:

print('c\n%s\n' % c)
print('d\n%s\n' % d)

Output:

c
[1 4 2 5 3 6]

d
[1 2 3 4 5 6]

In order to understand why a change in the order leads to a view being returned, we can look at the code of the ravel function itself. The implementation of np.ndarray.ravel is buried in the C layer. Reading over the source for that, it's clear that in order to return a view from ravel, two conditions must be met:

  • The input array must be contiguous.

  • The order of the contiguous input array must match that of the order kwarg passed into ravel.

The kwarg has a default value of order='C'. Thus, by default ravel will only return a view if you run it on a C-contiguous array. Most of the time when you initialize a fresh Numpy array a, it will start off as C-contiguous. However, the transpose a.T will be F-contiguous. You can see this in action in your code by checking the .flags property of your arrays:

a = np.array([[1,2,3], [4,5,6]])

print('the flags of a\n%s\n' % a.flags)
print('the flags of a.T\n%s\n' % a.T.flags)

Output:

the flags of a
  C_CONTIGUOUS : True
  F_CONTIGUOUS : False
  OWNDATA : True
  WRITEABLE : True
  ALIGNED : True
  WRITEBACKIFCOPY : False
  UPDATEIFCOPY : False

the flags of a.T
  C_CONTIGUOUS : False
  F_CONTIGUOUS : True
  OWNDATA : False
  WRITEABLE : True
  ALIGNED : True
  WRITEBACKIFCOPY : False
  UPDATEIFCOPY : False

What the heck do C- and F-contiguous mean?

There's a good chance that the terms C-contiguous and F-contiguous seem like gibberish to you. Explaining them would require a whole other question, which happily someone on SO has already asked. Here's a link to an old answer that gives a really intuitive overview of what C and F order actually mean.

Caveat

In your actual code, I wouldn't worry too much about whether ravel is return views or copies. In reality, you don't always get a performance boost by ensuring the use of views. Avoid premature optimization in general.

tel
  • 13,005
  • 2
  • 44
  • 62
  • why ravelling the transpose, a copy is in fact needed? – Ocxs Jan 13 '19 at 04:51
  • Okay, I've added all of the details about why `ravel` is returning a copy instead of a view. Sadly, it's a little complicated (and about 3 times as long as my original answer). – tel Jan 13 '19 at 05:26
0
In [382]: a = np.array([[1,2,3], [4,5,6]])
In [383]: a
Out[383]: 
array([[1, 2, 3],
       [4, 5, 6]])
In [384]: a.ravel()
Out[384]: array([1, 2, 3, 4, 5, 6])

ravel gives a 1d view of the array - and shows the values in the order that they occur in the data buffer.

In [385]: a.T
Out[385]: 
array([[1, 4],
       [2, 5],
       [3, 6]])
In [386]: a.T.ravel()
Out[386]: array([1, 4, 2, 5, 3, 6])

ravel of the transpose shows elements in a different order - unless we specify the order as 'F' (or 'K').

In [387]: a.T.ravel(order='F')
Out[387]: array([1, 2, 3, 4, 5, 6])

ravel (and other operations) makes a view if the array can use the original data, with only a change in shape and strides. If it can't, it has to make a copy.

Because of this change in element order with transpose, indexing with something other than [0], selects a different value:

In [397]: a.ravel()[3]
Out[397]: 4               # -1 in your Out[8]
In [398]: a.T.ravel()[3]
Out[398]: 5

You see there's a certain ambiguity when you ask to change the 4th element of the transpose. It can differ depending on how you traverse the elements.

hpaulj
  • 221,503
  • 14
  • 230
  • 353