本記事では、np.ravel
関数を紹介します。
np.ravel
関数は、一見マイナーであまり使用されない関数のように見えますが、知っていると便利で強力な関数です。
np.flatten
関数と同様に、配列を一次元化することができる関数ですが、np.ravel
関数の方が高速に処理することができる場合があります。np.flatten
関数については、以下の記事で解説しているので参考にしてください。
配列を1次元に変換するNumPyのflatten関数の使い方 /features/numpy-flatten.html
np.ravel
まずは、ravel
関数のAPIドキュメントについてみていきましょう。
np.ravel(a, order=’C’)
params:
パラメータ名 | 型 | 概要 |
---|---|---|
a | array_like 配列に相当するもの |
1次元配列に変換したい配列を指定します。 |
order | ‘C’,’F’,’A’,’K’ のいずれか |
(省略可能)初期値’C’ データの読み取り方を指定します。 |
returns:
指定されたorder
で読み取られた、1次元配列が返されます。
np.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