読者です 読者をやめる 読者になる 読者になる

福岡は今日も雨

情報系大学生のブログ。主に技術,音楽について。

numpyについて2

インデキシング, ブロードキャスティングなどについて詳しく見ていく.

基本インデキシング

まず, numpyのデータに変数を与えることは普通の値を渡す状態なのではなく, 参照渡しであることに注意する.
つまり, ひとつのndarrayオブジェクトに変数を複数渡り当てた場合,ひとつの変数から値を変えると他の変数にも影響が出ることになる
これはビューといって, 余計なメモリを生み出さないための方法である.

In [1]: import numpy as np

In [2]: x2d = np.arange(12).reshape(3, 4)

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

# ここは, x2d[1, 1:]とも書ける(numpyのみ)
In [4]: a = x2d[1][1:]

In [5]: a
Out[5]: array([5, 6, 7])

In [6]: a[0] = -1

In [7]: a
Out[7]: array([-1,  6,  7])

In [8]: x2d
Out[8]:
array([[ 0,  1,  2,  3],
       [ 4, -1,  6,  7],
       [ 8,  9, 10, 11]])

応用インデキシング

先ほどのように, ビューが生成されるのではなく, コピーが生成される.
このため, メモリの量には気をつける必要がある.(大きいデータを扱うことがメインのため)

In [11]: dat = np.random.rand(2, 3)

In [12]: dat
Out[12]:
array([[ 0.98810811,  0.53605981,  0.80088131],
       [ 0.44439242,  0.4916097 ,  0.07521805]])

In [13]: bmask = dat > 0.5

In [14]: bmask
Out[14]:
array([[ True,  True,  True],
       [False, False, False]], dtype=bool)

In [15]: highd = dat[bmask]

In [16]: highd
Out[16]: array([ 0.98810811,  0.53605981,  0.80088131])

In [17]: highd[0] = 1000

# datには無影響である
In [18]: dat
Out[18]:
array([[ 0.98810811,  0.53605981,  0.80088131],
       [ 0.44439242,  0.4916097 ,  0.07521805]])

In [19]: highd
Out[19]: array([  1.00000000e+03,   5.36059805e-01,   8.00881315e-01])

整数配列でのインデキシングは, リストを渡すことによって可能である

In [24]: nda = np.arange(10)

In [25]: ndb = nda[1:4]

In [26]: ndb
Out[26]: array([1, 2, 3])

In [27]: ndc = nda[[1, 2, 3]]

In [28]: ndc
Out[28]: array([1, 2, 3])

浅いコピー, 深いコピー

numpyには浅いコピーと深いコピーがある.
浅いコピーには参照が渡される. 基本インデキシングはこちらになる.

深いコピーでは, 値が渡されるので, 元々のデータと別物になる.
応用インデキシングで見せた, ブール配列によるインデキシングや, 整数配列によるインデキシングだけでなく,
copy()メソッドや, flatten(一次元にする), a.clip(min, maxを指定し, min以下のものはminへ, max以上のものはmaxへ)などもこちらに属する

ufunc. ユニバーサル関数について

numpyには, map関数として配列の要素のそれぞれにアクセスして特定の処理をして返す関数があり,
ユニバーサル関数と言われる.

In [30]: nda = np.arange(12).reshape(2, 6)

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

In [32]: np.square(nda)
Out[32]:
array([[  0,   1,   4,   9,  16,  25],
       [ 36,  49,  64,  81, 100, 121]])

既存のpythonの関数を, frompyfuncを利用することによってufunc化することもできる

In [34]: hex_array = np.frompyfunc(hex, 1, 1)

In [35]: hex_array((10, 30, 100))
Out[35]: array(['0xa', '0x1e', '0x64'], dtype=object)

ブロードキャスティング

  1. , /, *, -などの計算は, 同じ大きさ同士のものでなければ計算することはできない.

この点を解消してくれるのが, ブロードキャスティングである. これが結構ややこしい(というか, 言葉にするより図に書くべきだと思う).
詳しくは, ここをみてほしい

In [36]: nda = np.arange(24).reshape(4, 3, 2)

In [37]: ndb = np.arange(6).reshape(3, 2)

In [38]: ndc = np.arange(3).reshape(3, 1)

In [39]: nda + ndb - ndc
Out[39]:
array([[[ 0,  2],
        [ 3,  5],
        [ 6,  8]],

       [[ 6,  8],
        [ 9, 11],
        [12, 14]],

       [[12, 14],
        [15, 17],
        [18, 20]],

       [[18, 20],
        [21, 23],
        [24, 26]]])

この例だと, ndaに合わせていく形になり, ndb, ndcの次元を一つ増やして3にする. あいているところは, 今と全く同じベクトル/行列で埋める.
次に大きい軸(z, x, y)ならyで埋めていくと, nbcは(4, 3, 2)の形にまで大きくすることが可能. これにより, (4, 3, 2)を出力の行列として計算することができる. このように, ぴったりと埋まらない場合にブロードキャスティングでエラーが発生する. 例えば, 以下の例である.

In [47]: nda = np.arange(12).reshape(3, 4)

In [48]: ndb = np.arange(4)

In [49]: nda + ndb
Out[49]:
array([[ 0,  2,  4,  6],
       [ 4,  6,  8, 10],
       [ 8, 10, 12, 14]])

In [56]: ndb.reshape(4, 1)
Out[56]:
array([[0],
       [1],
       [2],
       [3]])

In [60]: nda + ndb
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-60-c79787edb196> in <module>()
----> 1 nda + ndb

ValueError: operands could not be broadcast together with shapes (3,4) (4,1)

言葉より図をかいて考えるべきだった..