NumPyには、配列を複製してタイル状に並べた新しい配列を生成するnp.tile関数があります。

この関数は、Matlabのrepmat関数に相当する操作をします。複雑な引数は存在せず、どの軸(axis)方向に何個配列を配置したいかを指定するだけなので、非常にシンプルな関数です。

np.tile関数の使い方

まずは、np.tile関数のAPIドキュメントを確認します。

np.tile(A, reps)

params:

パラメータ名 概要
A array_like
配列に相当するもの
並べたい配列を指定します。
reps intまたはarray_like どの軸方向に何回配列を繰り返すかを指定します。数字が1つだけのときは、最も低次の軸(axis)方向に配列を繰り返します。

returns:

指定された回数分繰り返された配列が返されます。

実際に使い方を確認してみます。第二引数のrepsに軸ごとに繰り返す回数を指定することで、第一引数の配列がタイル状にコピーされて配置されます。

  
In [1]: import numpy as np

In [2]: a = np.array([0, 1, 2])

In [3]: np.tile(a, 2) # 1次元配列を2回繰り返す。
Out[3]: array([0, 1, 2, 0, 1, 2])

In [4]: np.tile(a,(3,2)) # 3×2の二次元配列の要素がaになっているイメージ
Out[4]:
array([[0, 1, 2, 0, 1, 2],
       [0, 1, 2, 0, 1, 2],
       [0, 1, 2, 0, 1, 2]])

In [5]: np.tile(a,(2,3,4)) # 3次元にも並べられる。
Out[5]:
array([[[0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2],
        [0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2],
        [0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2]],

       [[0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2],
        [0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2],
        [0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2]]])

In [6]: b = np.arange(6).reshape(2,3) # 2次元行列をしきつめる。

In [7]: np.tile(b, 2)
Out[7]:
array([[0, 1, 2, 0, 1, 2],
       [3, 4, 5, 3, 4, 5]])

In [8]: np.tile(b,(2,3))
Out[8]:
array([[0, 1, 2, 0, 1, 2, 0, 1, 2],
       [3, 4, 5, 3, 4, 5, 3, 4, 5],
       [0, 1, 2, 0, 1, 2, 0, 1, 2],
       [3, 4, 5, 3, 4, 5, 3, 4, 5]])

In [9]: np.tile(b,(2,1,1))
Out[9]:
array([[[0, 1, 2],
        [3, 4, 5]],

       [[0, 1, 2],
        [3, 4, 5]]])

こんな感じで敷き詰められます。

np.tile関数とブロードキャスト

np.tile関数を演算の整形のために使用することを考えているかもしれません。しかしながら、NumPyにはブロードキャスティング機能が実装されているため、わざわざnp.tileを使って配列を並べ直さなくても同じ操作を演算の際に自動的にやってもらえる場合があるので、最初にnp.tile関数を使う必要性は考えておきましょう。

以下のリンクにブロードキャスティングについて解説した記事がありますので参考にしてみてください。

NumPyのブロードキャストのメリットと解説 /features/numpy-broadcasting.html

例えば、ある2つの配列の要素同士の積をある範囲において全通り出したい場合、ブロードキャストを使うと以下のようになります。

a = np.arange(10000).reshape(-1,1) # 縦ベクトルに変換
b = np.arange(10000).reshape(1,-1) # 横ベクトルに変換
a * b

これをブロードキャスト機能を使わないと、以下のようになります。

np.tile(a, (1, 10000)) * np.tile(b, (10000, 1))

コードの見た目が少し悪いですね。実際の処理速度を比較してみます。

In [27]: a = np.arange(10000).reshape(-1, 1)

In [28]: b = np.arange(10000)

In [29]: %timeit a*b
636 ms ± 94.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [30]: %timeit np.tile(a,(1,10000))*np.tile(b,(10000,1))
4.25 s ± 535 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

ブロードキャストを使った方が7倍ほど速く処理ができていることがわかります。

では、どんな時にnp.tile関数は便利なのでしょうか。ブロードキャスト機能が適用できないときが分かりやすいです。例えば、ある1次元配列に対して周期的にある配列の値をかけたいときなどは有効です。

In [37]: c = np.random.rand(10)

In [38]: b = np.array([0, 1, 0, 2, 1])

In [39]: b = np.tile(b, 2)

In [40]: c * b
Out[40]:
array([ 0.        ,  0.94669908,  0.        ,  0.28325599,  0.56093969,
        0.        ,  0.14471329,  0.        ,  1.99921598,  0.61237971])

これを何もせずに掛け合わせると、エラーが返ってきます。

In [41]: b = np.array([0, 1, 0, 2, 1])

In [44]: c * b
---------------------------------------------------------------------
(エラーメッセージが表示される)
ValueError: operands could not be broadcast together with shapes (10,) (5,)

このようなブロードキャストが適用できないものの、他の場面で配列を並べる必要がある場合にnp.tile関数を使うといいと思います。

参考