python – numpyで配列スライス、形状変更、連結分割
今回は、業務の中で非常に頻繁に使用するNumpyの基本となる配列のスライスとreshape, newaxisを使用して配列の形状変更、最後にconcatenate, splitなどを使用して、配列の連結と分割について記載しようと思います。
1. NumPyで配列のスライス(slice)
角カッコを使用して個々の配列要素にアクセスできるように、コロン(:)でマークされたスライス(slice)表記を使用して部分配列にアクセスすることができます。
NumPyのスライス構文は、標準のPythonリストの構文に従います。配列xのスライスにアクセスするには、次の指定方法を使います。
x[start:stop:step]
これらのいずれかが指定されていない場合は、デフォルト値start = 0、stop = その次元のsize、step = 1になります。まず1次元配列の部分配列を扱い、その後で多次元配列に進みます。
x[start:stop] # items start through stop-1
x[start:] # items start through the rest of the array
x[:stop] # items from the beginning through stop-1
x[:] # a copy of the whole array
-1の扱いについてはこちら。
a[-1] # last item in the array
a[-2:] # last two items in the array
a[:-2] # everything except the last two items
ちなみに、pandas dataframeのilocでもスライスを使います。1000レコードのdataframeにて、0~299をdf_1にセットし、300~1000をdf_2にセットする場合の例はこちら。
index = 300
df_1, df_2 = df.iloc[:index].copy(), df_out.iloc[index:].copy()
modeling時などに、最後のカラムにtarget variableがある場合は、このような指定でスライスを使ったりもしています。
X = df.iloc[:, :-1]
y = df.iloc[:, -1]
Numpyで1次元配列のスライス(slice)
まずは、numpyをimportしてから、今回使用するデータを作成して変数の中身を確認してみます。
In [1]: import numpy as np
In [2]: x = np.arange(10)
In [3]: x
Out[3]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
次に、numpyのスライス(slice)を使用して先頭から5つの要素を取得してみます。
In [4]: x[:5]
Out[4]: array([0, 1, 2, 3, 4])
今度は、numpyのスライス(slice)を使用して末尾から5つの要素を取得してみます。
In [5]: x[5:]
Out[5]: array([5, 6, 7, 8, 9])
numpyのスライス(slice)を使用して配列中間の要素3つを取得してみます。
In [6]: x[3:6]
Out[6]: array([3, 4, 5])
numpyのスライス(slice)を使用して1つおきに要素を取得してみます。
In [7]: x[::2]
Out[7]: array([0, 2, 4, 6, 8])
それでは、1をスタート地点として1つおきに要素を取得してみます。
In [8]: x[1::2]
Out[8]: array([1, 3, 5, 7, 9])
少し混乱するケースは、stepが負の場合です。この場合、startとstopのデフォルト値は入れ替わります。つまり、末尾から配列データを取得する場合に使用します。
In [9]: x[::-1]
Out[9]: array([9, 8, 7, 6, 5, 4, 3, 2, 1, 0])
では、同様に末尾からインデックス5からスタートして、1つおきの要素をnumpyのスライスを使用して取得します。
In [10]: x[5::-2]
Out[10]: array([5, 3, 1])
Numpyで多次元配列のスライス(slice)
Numpyで多次元配列のスライス(slice)は、複数のスライスをカンマ区切りで指定します。まずは、np.random.randintを使用して2次元配列データを作成します。
In [11]: x2 = np.random.randint(10, size=(3, 4))
In [12]: x2
Out[12]:
array([[0, 1, 2, 8],
[8, 6, 0, 7],
[1, 8, 0, 1]])
では、numpyのスライスを使用して、2行のデータと3列分のデータを取得します、
In [13]: x2[:2, :3]
Out[13]:
array([[0, 1, 2],
[8, 6, 0]])
次に、3行全てと先頭から1つおきのデータを取得します。全ての列を取得する場合、”:(コロン)”のみでも取得できます。
In [14]: x2[:3, ::2]
Out[14]:
array([[0, 2],
[8, 0],
[1, 0]])
In [15]: x2[:, ::2]
Out[15]:
array([[0, 2],
[8, 0],
[1, 0]])
配列のアクセスに関して、最も頻繁に行われる操作は、行または列の抽出です。これは、インデクスと1つのコロン(:)による空のスライスとの組み合わせで行います。
numpyのスライスを使用して、先頭の列の値のみ取得してみます。
In [16]: x2[:, 0]
Out[16]: array([0, 8, 1])
今度は、先頭の行のみを取得してみます。
In [17]: x2[0, :]
Out[17]: array([0, 1, 2, 8])
また、上記と同様のデータを取得する場合(行にアクセスする場合)、空のスライスを省略することで非常にシンプルにデータを取得することができます。
In [18]: x2[0]
Out[18]: array([0, 1, 2, 8])
2. NumPyで配列の形状変更(reshape)
配列に対する重要な操作の1つが形状の変更です。これを行うには、reshapeメソッドを使う方法が最も柔軟です。
それではまず、np.arangeを使用して1〜9の配列を作成します。
In [19]: tmp = np.arange(1, 10)
...: tmp
Out[19]: array([1, 2, 3, 4, 5, 6, 7, 8, 9])
次に、numpyのreshapeを使用して3行3列のデータに形状を変更してみます。
In [20]: grid = tmp.reshape((3, 3))
...: grid
Out[20]:
array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
これが機能するためには、最初の配列の要素数と、変更後の要素数が一致しなければならないことに注意が必要です。
可能であれば、reshapeメソッドは初期配列のコピーではなくビューを使用しますが、メモリが連続していないなどの理由により、コピーが行われることもあります。
別の一般的な形状変更パターンは、1次元配列を2次元の行または列ベクトルに変換することです。これは、スライス操作にnewaxisキーワードを使用することで、より簡単に行うことができます。
In [4]: x = np.array([1, 2, 3])
In [5]: x
Out[5]: array([1, 2, 3])
In [6]: x.reshape((1, 3))
Out[6]: array([[1, 2, 3]])
次に、newaxisを使用して行ベクトルを作成してみます。
In [7]: x[np.newaxis, :]
Out[7]: array([[1, 2, 3]])
reshapeを使用して、列ベクトルを作成してみます。
In [8]: x.reshape((3, 1))
Out[8]:
array([[1],
[2],
[3]])
最後に、newaxisを使用して、列ベクトルを作成します。
In [9]: x[:, np.newaxis]
Out[9]:
array([[1],
[2],
[3]])
3. 配列の連結と分割
Numpy 配列の連結(np.concatenate, np.vstack, np.hstack)
NumPyによる2つの配列の連結または結合は、主にnp.concatenate、np.vstack、およびnp.hstackによって実行します。
a) np.concatenate
np.concatenateは、最初の引数としてタプルまたは配列のリストを取ります。
In [10]: x = np.array([1, 2, 3])
...: y = np.array([3, 2, 1])
...: np.concatenate([x, y])
Out[10]: array([1, 2, 3, 3, 2, 1])
一度に2つ以上の配列を連結することもできます。
In [11]: z = [99, 99, 99]
...: np.concatenate([x, y, z])
Out[11]: array([ 1, 2, 3, 3, 2, 1, 99, 99, 99])
np.concatenate([grid, grid], axis=1)は、2次元配列に対しても使用できます。
In [12]: grid = np.array([[1, 2, 3],
...: [4, 5, 6]])
In [13]: grid
Out[13]:
array([[1, 2, 3],
[4, 5, 6]])
np.concatenate()を使用して、2次元配列をSQLのunionのように垂直方向に連結してみます。
In [14]: np.concatenate([grid, grid])
Out[14]:
array([[1, 2, 3],
[4, 5, 6],
[1, 2, 3],
[4, 5, 6]])
np.concatenate()を使用して、2次元配列水平方向に連結してみます。
In [15]: np.concatenate([grid, grid], axis=1)
Out[15]:
array([[1, 2, 3, 1, 2, 3],
[4, 5, 6, 4, 5, 6]])
b) np.vstack
混合次元の配列を扱う場合、np.vstack(垂直スタック)関数とnp.hstack(水平スタック)関数を使用する方がよりシンプルになります。
In [16]: np.vstack([x, grid])
Out[16]:
array([[1, 2, 3],
[1, 2, 3],
[4, 5, 6]])
c) np.hstack
np.hstack(水平スタック)関数を使用してみます。
In [17]: y = np.array([[99],
...: [99]])
In [18]: np.hstack([grid, y])
Out[18]:
array([[ 1, 2, 3, 99],
[ 4, 5, 6, 99]])
Numpy配列の分割(np.split, no.hsplit, np.vsplit)
連結の反対は分割です。これは、関数np.split、np.hsplit、およびnp.vsplitによって行います。これらの関数には、分割点を与えるインデクスのリストを渡すことができます。
a) np.split
まず、np.split()で分割を行なってみます。
In [19]: x = [1, 2, 3, 99, 99, 3, 2, 1]
...: x1, x2, x3 = np.split(x, [3, 5])
...: x1, x2, x3
Out[19]: (array([1, 2, 3]), array([99, 99]), array([3, 2, 1]))
b) np.hsplit
次に、np.vsplitで水平方向の分割を行なってみます。
In [20]: grid = np.arange(16).reshape((4, 4))
...: grid
Out[20]:
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11],
[12, 13, 14, 15]])
In [21]: upper, lower = np.vsplit(grid, [2])
...: upper, lower
Out[21]:
(array([[0, 1, 2, 3],
[4, 5, 6, 7]]),
array([[ 8, 9, 10, 11],
[12, 13, 14, 15]]))
c) np.hsplit
最後に、np.hsplitで垂直方向の分割を行なってみます。
In [22]: left, right = np.hsplit(grid, [2])
...: left, right
Out[22]:
(array([[ 0, 1],
[ 4, 5],
[ 8, 9],
[12, 13]]),
array([[ 2, 3],
[ 6, 7],
[10, 11],
[14, 15]]))
4. まとめ
ということで、python – numpyで配列スライス、形状変更、連結分割でした。業務の中で非常に頻繁に使用するNumpyの基本となる配列のスライスとreshape, newaxisを使用して配列の形状変更、最後にconcatenate, splitなどを使用して、配列の連結と分割について記載しました。