NumPy配列ndarrayを一次元化(平坦化)するravelとflatten
NumPy配列ndarray
を一次元化(平坦化、flatten)するには、numpy.ravel()
関数、または、numpy.ndarray
のravel()
メソッド、flatten()
メソッドを使う。
- numpy.ravel — NumPy v1.26 Manual
- numpy.ndarray.ravel — NumPy v1.26 Manual
- numpy.ndarray.flatten — NumPy v1.26 Manual
多次元のリスト(Python組み込みのlist
型)を一次元に平坦化する方法は以下の記事を参照。
本記事のサンプルコードのNumPyのバージョンは以下の通り。バージョンによって仕様が異なる可能性があるので注意。
import numpy as np
print(np.__version__)
# 1.26.1
numpy.ravel()関数による一次元化
numpy.ravel()
の第一引数に対象とするnumpy.ndarray
を指定すると、一次元化されたnumpy.ndarray
が返される。
a = np.arange(12).reshape(3, 4)
print(a)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
print(np.ravel(a))
# [ 0 1 2 3 4 5 6 7 8 9 10 11]
print(type(np.ravel(a)))
# <class 'numpy.ndarray'>
引数にはPython組み込み型のリストなど、いわゆるarray-likeオブジェクトも指定可能。その場合も返り値はnumpy.ndarray
。
print(np.ravel([[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]]))
# [ 0 1 2 3 4 5 6 7 8 9 10 11]
print(type(np.ravel([[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]])))
# <class 'numpy.ndarray'>
中のリストの要素数が異なる二次元リスト(リストのリスト)の場合、NumPy1.24
以降はエラーになるので注意。それより前のバージョンではリストを要素とするnumpy.ndarray
が返されていた。
# print(np.ravel([[0, 1, 2, 3], [4, 5, 6, 7], [8, 9]]))
# ValueError: setting an array element with a sequence. The requested array has an inhomogeneous shape after 1 dimensions. The detected shape was (3,) + inhomogeneous part.
このようなリストを平坦化したい場合は以下の記事を参照。
ndarray.ravel()メソッドによる一次元化
ravel()
はnumpy.ndarray
のメソッドとしても提供されている。
a = np.arange(12).reshape(3, 4)
print(a)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
print(a.ravel())
# [ 0 1 2 3 4 5 6 7 8 9 10 11]
ndarray.flatten()メソッドによる一次元化
numpy.ndarray
のメソッドにはflatten()
もある。
a = np.arange(12).reshape(3, 4)
print(a)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
print(a.flatten())
# [ 0 1 2 3 4 5 6 7 8 9 10 11]
ravel()
は可能な限りビューを返すが、flatten()
は常にコピーを返すという違いがある。flatten()
はメモリを新たに確保するため、ravel()
よりも遅い。詳細は後述。
なお、バージョン1.26
時点では、flatten()
はnumpy.ndarray
のメソッドのみで、numpy.flatten()
のような関数としては提供されていない。
reshape(-1)による一次元化
numpy.ndarray
の形状shape
を変換するreshape()
を利用して一次元化することもできる。-1
を使うとサイズが自動的に算出されるので、reshape(-1)
で一次元に変換できる。
numpy.ndarray
のreshape()
メソッド。
a = np.arange(12).reshape(3, 4)
print(a)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
print(a.reshape(-1))
# [ 0 1 2 3 4 5 6 7 8 9 10 11]
numpy.reshape()
関数。関数の場合は上述のnumpy.reval()
と同様、リストなどのarray-likeオブジェクトも処理できる。
print(np.reshape(a, -1))
# [ 0 1 2 3 4 5 6 7 8 9 10 11]
print(np.reshape([[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]], -1))
# [ 0 1 2 3 4 5 6 7 8 9 10 11]
ravel()とflatten()の違い(ビューとコピー)
ravel()
は可能な限りビューを返すが、flatten()
は常にコピーを返す。reshape()
もreval()
と同様、可能な限りビューを返す。
numpy.ndarray
におけるビューとコピーについての詳細は以下の記事を参照。
a = np.arange(12).reshape(3, 4)
print(a)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
print(np.shares_memory(a, a.ravel()))
# True
print(np.shares_memory(a, np.ravel(a)))
# True
print(np.shares_memory(a, a.flatten()))
# False
print(np.shares_memory(a, a.reshape(-1)))
# True
print(np.shares_memory(a, np.reshape(a, -1)))
# True
ビューの場合、元のnumpy.ndarray
とメモリを共有しているため、一方の値を変更すると他方の値も変わる。
a_ravel = a.ravel()
print(a_ravel)
# [ 0 1 2 3 4 5 6 7 8 9 10 11]
a_ravel[0] = 100
print(a_ravel)
# [100 1 2 3 4 5 6 7 8 9 10 11]
print(a)
# [[100 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
コピーの場合はそれぞれメモリが確保されるので、別々に処理される。
「可能な限り」と書いたように、ravel()
とreshape()
が常にビューを返すとは限らない。
例えばステップを指定したスライスを一次元化した結果、メモリ上のstrideが一定にならない場合などはravel()
もreshape()
もコピーを返す(メモリを共有しない)。
a = np.arange(12).reshape(3, 4)
print(a)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
print(a[:, ::3])
# [[ 0 3]
# [ 4 7]
# [ 8 11]]
print(np.shares_memory(a[:, ::3], np.ravel(a[:, ::3])))
# False
print(np.shares_memory(a[:, ::3], np.reshape(a[:, ::3], -1)))
# False
ravel()
とreshape()
で異なる場合もある。ステップを指定したスライスでもstrideが一定だと、ravel()
はコピーを返すが、reshape()
はビューを返す。
print(a[:, ::2])
# [[ 0 2]
# [ 4 6]
# [ 8 10]]
print(np.shares_memory(a[:, ::2], np.ravel(a[:, ::2])))
# False
print(np.shares_memory(a[:, ::2], np.reshape(a[:, ::2], -1)))
# True
公式ドキュメントのNotesにあるように、できる限りビューを返してほしい場合はravel()
よりreshape(-1)
のほうが適している。
When a view is desired in as many cases as possible, arr.reshape(-1) may be preferable.
numpy.ravel — NumPy v1.26 Manual
ravel()とflatten()の速度比較
コピーを返すflatten()
はメモリを新たに確保するため、ravel()
よりも遅い。
簡単なテスト結果を示す。
以下の例はJupyter Notebookのマジックコマンド%%timeit
を利用しており、Pythonスクリプトとして実行しても計測されない。
a = np.arange(12).reshape(3, 4)
print(a)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
%%timeit
a.ravel()
# 43.6 ns ± 0.298 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)
%%timeit
a.flatten()
# 249 ns ± 0.971 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
%%timeit
a.reshape(-1)
# 80.2 ns ± 0.145 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)
上の例ぐらいの小さなnumpy.ndarray
であれば特に気にしなくてもよいが、サイズが大きくなるとflatten()
は文字通り桁違いに時間がかかる。当然、メモリ使用量もflatten()
のほうが多くなる。
ビューでも問題ないのであればravel()
を使うほうが良いだろう。
a_large = np.arange(1000000).reshape(100, 100, 100)
%%timeit
a_large.ravel()
# 43.6 ns ± 0.118 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)
%%timeit
a_large.flatten()
# 423 µs ± 25.9 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
%%timeit
a_large.reshape(-1)
# 80 ns ± 0.0587 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)
なお、ravel()
でもコピーを返す場合はメモリを新たに確保するため、速度もメモリ使用量もflatten()
と同等。
引数order
ravel()
, flatten()
, reshape()
には引数order
を指定できる。
デフォルトはorder='C'
で、これまでの例のようにC言語ライクなrow-major(行優先)で平坦化されるが、order='F'
とすると、Fortranライクなcolumn-major(列優先)となる。
a = np.arange(12).reshape(3, 4)
print(a)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
print(a.ravel())
# [ 0 1 2 3 4 5 6 7 8 9 10 11]
print(a.ravel('F'))
# [ 0 4 8 1 5 9 2 6 10 3 7 11]
print(np.ravel(a, 'F'))
# [ 0 4 8 1 5 9 2 6 10 3 7 11]
print(a.flatten('F'))
# [ 0 4 8 1 5 9 2 6 10 3 7 11]
print(a.reshape(-1, order='F'))
# [ 0 4 8 1 5 9 2 6 10 3 7 11]
print(np.reshape(a, -1, order='F'))
# [ 0 4 8 1 5 9 2 6 10 3 7 11]
引数order
には、reshape()
では'C'
, 'F'
, 'A'
、ravel()
とflatten()
では'C'
, 'F'
, 'A'
, 'K'
が指定可能。
'C'
, 'F'
はメモリレイアウトは考慮されないが、'A'
, 'K'
ではメモリレイアウトが考慮される。
Note that the ‘C’ and ‘F’ options take no account of the memory layout of the underlying array, and only refer to the order of axis indexing. ‘A’ means to read the elements in Fortran-like index order if a is Fortran contiguous in memory, C-like order otherwise. ‘K’ means to read the elements in the order they occur in memory, except for reversing the data when strides are negative. By default, ‘C’ index order is used.
numpy.ravel — NumPy v1.26 Manual
np.info()
で確認できるnumpy.ndarray
の情報とともにorder
による結果の違いを示す。
例えばfortran
がTrue
の場合は'A'
は'F'
と等しく、fortran
がFalse
の場合は'A'
は'C'
と等しくなる。
np.info(a)
# class: ndarray
# shape: (3, 4)
# strides: (32, 8)
# itemsize: 8
# aligned: True
# contiguous: True
# fortran: False
# data pointer: 0x6000004bc000
# byteorder: little
# byteswap: False
# type: int64
print(a.ravel('C'))
# [ 0 1 2 3 4 5 6 7 8 9 10 11]
print(a.ravel('F'))
# [ 0 4 8 1 5 9 2 6 10 3 7 11]
print(a.ravel('A'))
# [ 0 1 2 3 4 5 6 7 8 9 10 11]
print(a.ravel('K'))
# [ 0 1 2 3 4 5 6 7 8 9 10 11]
T
属性による転置。
print(a.T)
# [[ 0 4 8]
# [ 1 5 9]
# [ 2 6 10]
# [ 3 7 11]]
np.info(a.T)
# class: ndarray
# shape: (4, 3)
# strides: (8, 32)
# itemsize: 8
# aligned: True
# contiguous: False
# fortran: True
# data pointer: 0x6000004bc000
# byteorder: little
# byteswap: False
# type: int64
print(a.T.ravel('C'))
# [ 0 4 8 1 5 9 2 6 10 3 7 11]
print(a.T.ravel('F'))
# [ 0 1 2 3 4 5 6 7 8 9 10 11]
print(a.T.ravel('A'))
# [ 0 1 2 3 4 5 6 7 8 9 10 11]
print(a.T.ravel('K'))
# [ 0 1 2 3 4 5 6 7 8 9 10 11]
負のステップのスライスによる反転。
print(a.T[::-1])
# [[ 3 7 11]
# [ 2 6 10]
# [ 1 5 9]
# [ 0 4 8]]
np.info(a.T[::-1])
# class: ndarray
# shape: (4, 3)
# strides: (-8, 32)
# itemsize: 8
# aligned: True
# contiguous: False
# fortran: False
# data pointer: 0x6000004bc018
# byteorder: little
# byteswap: False
# type: int64
print(a.T[::-1].ravel('C'))
# [ 3 7 11 2 6 10 1 5 9 0 4 8]
print(a.T[::-1].ravel('F'))
# [ 3 2 1 0 7 6 5 4 11 10 9 8]
print(a.T[::-1].ravel('A'))
# [ 3 7 11 2 6 10 1 5 9 0 4 8]
print(a.T[::-1].ravel('K'))
# [ 3 2 1 0 7 6 5 4 11 10 9 8]
3次元以上の多次元配列の場合
これまでの例は2次元配列だが、3次元以上の多次元配列の場合も同様に一次元化(平坦化)できる。
a_3d = np.arange(24).reshape(2, 3, 4)
print(a_3d)
# [[[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
#
# [[12 13 14 15]
# [16 17 18 19]
# [20 21 22 23]]]
print(a_3d.ravel())
# [ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23]
print(a_3d.ravel('F'))
# [ 0 12 4 16 8 20 1 13 5 17 9 21 2 14 6 18 10 22 3 15 7 19 11 23]
上の例のように、多次元配列の場合は引数order
の影響がややこしいので注意。シンプルな例で確認してみてから使うのをオススメする。