本記事では、np.ravel関数を紹介します。

np.ravel関数は、一見マイナーであまり使用されない関数のように見えますが、知っていると便利で強力な関数です。

np.flatten関数と同様に、配列を一次元化することができる関数ですが、np.ravel関数の方が高速に処理することができる場合があります。np.flatten関数については、以下の記事で解説しているので参考にしてください。

配列を1次元に変換するNumPyのflatten関数の使い方 /features/numpy-flatten.html

np.ravel

まずは、ravel関数のAPIドキュメントについてみていきましょう。

numpy.ravel(a, order=’C’)

params:

パラメータ名 概要
a array_like
配列に相当するもの
1次元配列に変換したい配列を指定します。
order ‘C’,’F’,’A’,’K’
のいずれか
(省略可能)初期値’C’
データの読み取り方を指定します。

returns:

指定されたorderで読み取られた、1次元配列が返されます。

numpy.ndarray.ravel(order=’C’)

パラメータ名 概要
order ‘C’,’F’,’A’,’K’
のいずれか
(省略可能)初期値’C’
データの読み取り方を指定します。

returns:

指定されたorderで読み取られた、1次元配列が返されます。

引数である、orderの扱いには注意が必要です。

引数 order について

ravel関数では引数orderを指定することで、データの読み取る順番を指定することができます。ここではメモリ上でのデータの並び方は関係ないので、他の配列生成系の関数で使われるorderとは少し意味合いが異なることに注意してください。どちらかというとaxisに近い役割となっています。

orderに’C’を指定すると、列方向(より厳密には最低次元の軸方向)から要素を読み取っていきます。

一方で、orderに’F’を指定すると、これが逆になり行方向(最高次元の軸方向)から要素を読み取っていきます。

order='A'にすると、元の配列のorder='F'、つまりFortranのスタイルでデータが格納されている場合、それと同様のスタイルでデータを読み取っていきます。

最後に、order='K'にすると、メモリの中で出て来る値を順番に読み取っていきます。

使い方

上記をふまえて、実際のコードで使い方をみていきます。

In [1]: import numpy as np

In [2]: a = np.arange(10).reshape(2,5) # 2×5の2次元配列を生成。

In [3]: a
Out[3]:
array([[0, 1, 2, 3, 4],
       [5, 6, 7, 8, 9]])

In [4]: a.ravel() # 1次元配列に変更
Out[4]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

In [5]: np.ravel(a) # こちらでもよい。
Out[5]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

次に、orderを変更していきます。

In [6]: a.ravel(order='C') # orderを'C'(初期値)にすると、同じ配列が出て来る
Out[6]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

In [7]: a.ravel(order='F') # orderを'F'にすると、行方向から値が読み取られる。
Out[7]: array([0, 5, 1, 6, 2, 7, 3, 8, 4, 9])

In [8]: a.ravel(order='A') # Fortranスタイルで要素を格納していないのでorder='C'と同じ結果になる。
Out[8]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

In [9]: a.ravel(order='K') # こちらも特に配列の`shape`変更などをしていないので、変化なし。
Out[9]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

In [10]: b = np.arange(10).reshape(2,5, order = 'F')

In [11]: b
Out[11]:
array([[0, 2, 4, 6, 8],
       [1, 3, 5, 7, 9]])

In [12]: b.ravel(order='F') # 連番になる。
Out[12]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

In [13]: b.ravel(order='A')
Out[13]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

In [14]: b.ravel() # order='C'だと、列方向に読み込む。
Out[14]: array([0, 2, 4, 6, 8, 1, 3, 5, 7, 9])

In [15]: c = b.T

In [16]: c.ravel() # 連番になる
Out[16]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

In [17]: c.ravel(order='K') # メモリの順番を読み込む.
Out[17]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

In [18]: c.T.ravel(order='K')
Out[18]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

flattenとの違い

ravel関数はflatten関数とは異なり基本的にはデータのコピーを返さず、破壊的な変更になります。そのため、処理が早く終わる傾向がありますが、元の値も変更になるため、バグを生みやすくなります。

特に小さなスコープでしか使用しない場合など、破壊的な変更を気にしない場合はnp.ravel関数を使う方が高速に処理することができるので、こちらを使用しましょう。

In [1]: import numpy as np

In [2]: a = np.random.randint(10, size=(1000, 1000))

In [3]: %timeit a.flatten()
3.32 ms ± 74.6 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [4]: %timeit a.ravel() # やはりravelの方が速い。
432 ns ± 12.8 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [5]: b = a.flatten()

In [6]: c = a.ravel()

In [7]: a[0, 20] = 23 # aの要素の1つの値を変更してみる。

In [8]: b[20] # flattenの方は変化なし。
Out[8]: 5

In [9]: c[20] # ravelの方では変化が反映される。
Out[9]: 23