すこしふしぎ.

VR/HI系院生による技術ブログ.まったりいきましょ.(友人ズとブログリレー中.さぼったら焼肉おごらなきゃいけない)

numpy使い方_1_ndarrayの生成方法

新年ということで,先日購入したpythonによるデータ分析の本をゴロゴロ読んでいます.どうも1000chです. この本はnumpy,pandasを利用したデータ分析入門の本です. 4章にてnumpyの使い方を,5章にてpandasの使い方を解説してくれています. いままできちんとnumpyの使い方を勉強したことがなかったので,読んだことをまとめておこうと思います.

numpyってなんぞ?

numpyの公式サイトにあるdocumentation,numpy users guideのはじめ,what's numpyを読んでみました.

numpyはpythonにおける科学計算のためのパッケージです. 業界では様々なライブラリがnumpyベースで開発されているなど,デファクトスタンダードとなっているようです. なので,numpyに慣れておくと他のライブラリ使うのも抵抗少ないかもしれないですね.

numpyのコアとなるのはndarrayと呼ばれるN次元配列クラスです. pythonが提供する通常のlistに比べ,高速で動く様々な処理が追加されている. 四則演算やソートにはじまり,DFTや統計処理までいろいろ提供しているみたい.

pythonの通常listだと時間かかる部分をCで実装しており,pythonライクな使いやすさとCコンパイルコードの速度,そのいいとこ取りを狙ったのがndarrayということですね.

numpyの大きな特徴は次の二点とのこと.

vectorization

ベクトル化とは,配列同士の演算に対して面倒なループを書かなくてよくする仕組みのこと. index指定とかナシで配列間の演算が可能になります. コードの記述量が減りバグが生まれにくくなります. また数学における表記に近くなるので,理論屋さんにも直感的になるでしょう. こんな感じです.

In [1]: a = array([1,2,3])

In [2]: b = array([4,5,6])

In [3]: a + b
Out[3]: array([5, 7, 9])

対応するindexの値同士に演算子が適用されていますね.

broadcast

ブロードキャストとは,形状の異なる配列間での演算を実現するための仕組みです.

まずは1次配列とスカラの演算を見てみましょう.

//ベクトル化を利用したかけ算
In [4]: a * array([5,5,5])
Out[4]: array([ 5, 10, 15])

// ブロードキャストによるかけ算
In [5]: a * 5
Out[5]: array([ 5, 10, 15])

上の例がベクトル化による計算,下の例がブロードキャストによる計算です. 共に処理内容は同じですね. 1*3の配列に対し,スカラーをかけ算しています. 配列中のすべての要素に関して,演算が適用されています. これは直感的にわかりやすい例といえるでしょう.

もう一つ見てみましょう.

In [20]: a = array([[1,2,3],[4,5,6],[7,8,9],[10,11,12]])

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

In [22]: b = array([1,2,3])

In [23]: a + b
Out[23]:
array([[ 2,  4,  6],
       [ 5,  7,  9],
       [ 8, 10, 12],
       [11, 13, 15]])

In [24]: a * b
Out[24]:
array([[ 1,  4,  9],
       [ 4, 10, 18],
       [ 7, 16, 27],
       [10, 22, 36]])

4 * 3の配列に対し,1 * 3の配列の演算を行っています. これは数学的には無理な演算ですね. このような場合,numpyでは1 * 3の配列を自動で繰り返し4* 3の配列と見なします. 例では1列目に対し+1,*1が,2列目に対し+2,*2が適用されるという具合です.

ブロードキャストは動作が直感的でないこともあり,下手に利用すると思わぬエラーを招きかねません. とりあえずまずは配列 * スカラーが適用できるということを覚えておけばよいでしょう. そのうちブロードキャストの仕様についてもまとめてみます.

numpy使い方

それでは,基本的なnumpyの使い方を見ていきましょう.

インポート

numpyを利用するために,まずパッケージをインポートしましょう. (IPythonの--pylabオプション起動では,デフォルトでインポートされています)

import numpy as np

慣例により,numpyはnpという名前でインポートすることをお勧めします. 関数の上書きなどが怖いので, from numpy import *はさけた方が無難でしょう.

ndarrayの生成

ではまずndarrayのオブジェクトを作ってみましょう. 生成にはnp.array()を利用します. 引数にlistを与えるのが最も簡単です.

In [33]: ls = [1,2,3]

In [34]: a = np.array(ls)

In [35]: a
Out[35]: array([1, 2, 3])

1 * 3 の配列が生成されました.

引数のlistを入れ子にすると,複数次元の配列も生成できます.

In [36]: ls = [[1,2,3],[4,5,6]]

In [37]: a = np.array(ls)

In [38]: a
Out[38]:
array([[1, 2, 3],
       [4, 5, 6]])

では,入れ子になった配列の大きさが異なる場合はどうなるのでしょう?

In [39]: ls = [[1,2,3],[4,5]]

In [40]: a = np.array(ls)

In [41]: a
Out[41]: array([[1, 2, 3], [4, 5]], dtype=object)

dtype=objectですと. これは,配列中に含まれるデータがpythonのobject型である,という意味です. [[1,2,3],[4,5]]という要素をもった,オブジェクト型の 1 * 1 の配列が生成されたということですね.

numpyにおけるデータ型についてを少し見てみましょう.

ndarryaにおけるデータ型

ndarrayにおいては,含まれるデータの型をそろえる必要があります. メモリ上にどのような形でデータが保存されるかという情報を,dtypeという属性で管理しています. 例えばint8(8bit整数型),float32(単精度浮動小数点型)などです. numpyの実装はCなので,あらかじめ配列中のデータ型を明記する必要があるのですかね.

dtypeは自分で明記する必要はありません. ndarrayを生成する時点で,プログラムが自動で型を判別してdtypeに格納してくれます.

In [47]: np.array([1,2,3]).dtype
Out[47]: dtype('int64')

In [48]: np.array([1.2, -2, 0]).dtype
Out[48]: dtype('float64')

dtypeを変更することも可能です.ndarayのastypeメソッドを使います.

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

In [50]: a.dtype
Out[50]: dtype('int64')

In [51]: a.astype(np.float64)
Out[51]: array([ 1.,  2.,  3.])

In [52]: a.dtype
Out[52]: dtype('int64')

In [53]: a.astype(np.float64).dtype
Out[53]: dtype('float64')

ここで注意しなければならないのは,astypeを呼んだところでその配列自体のデータ型が変わる訳ではない,ということです. このメソッドに限ったことではないですが,numpyで配列を変形させると,変形した新しいndarrayを生成するようになっています. 上の例では,astypeでfloat64にキャストした後も,a自体はint64になっていますね. キャストされた新しい配列を利用する場合は,astypeで返されるndarrarを別変数に格納すればokです.

とはいえ,numpyを使う際にどのようなデータ型があるのかを覚えておく必要はあまりなさそうです. 扱っているndarrayのdtypeは何かだけ覚えておけばokです.

その他のndarray生成方法

順番が前後しますが,np.array()以外のndarray生成方法をまとめてみます.

# np.array() list,tupleなどを引数にndarray生成
In [57]: np.array([1,2,3])
Out[57]: array([1, 2, 3])

# np.asarray() array()と同様.ただし引数がndarrayの場合,コピーでなく引数そのものを返す
In [58]: np.asarray([1,2,3])
Out[58]: array([1, 2, 3])

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

In [69]: a
Out[69]: array([1, 2])

In [70]: b = np.asarray(a) # ndarrayを引数とするため,b = aと同値

In [71]: b
Out[71]: array([1, 2])

In [73]: b[0] = 2

In [74]: a
Out[74]: array([2, 2]) # asarrayの返り値bを変更すると,元のaも変更されている.

# np.arange() pythonのrange関数と同じ仕様でndarrayを生成
In [59]: np.arange(0,10,0.1)
Out[59]:
array([ 0. ,  0.1,  0.2,  0.3,  0.4,  0.5,  0.6,  0.7,  0.8,  0.9,  1. ,
        1.1,  1.2,  1.3,  1.4,  1.5,  1.6,  1.7,  1.8,  1.9,  2. ,  2.1,
        2.2,  2.3,  2.4,  2.5,  2.6,  2.7,  2.8,  2.9,  3. ,  3.1,  3.2,
        3.3,  3.4,  3.5,  3.6,  3.7,  3.8,  3.9,  4. ,  4.1,  4.2,  4.3,
        4.4,  4.5,  4.6,  4.7,  4.8,  4.9,  5. ,  5.1,  5.2,  5.3,  5.4,
        5.5,  5.6,  5.7,  5.8,  5.9,  6. ,  6.1,  6.2,  6.3,  6.4,  6.5,
        6.6,  6.7,  6.8,  6.9,  7. ,  7.1,  7.2,  7.3,  7.4,  7.5,  7.6,
        7.7,  7.8,  7.9,  8. ,  8.1,  8.2,  8.3,  8.4,  8.5,  8.6,  8.7,
        8.8,  8.9,  9. ,  9.1,  9.2,  9.3,  9.4,  9.5,  9.6,  9.7,  9.8,
        9.9])

# np.ones() 要素がすべて1のndarrayを生成.引数は出力されるndarrayのサイズ.
In [60]: np.ones(3)
Out[60]: array([ 1.,  1.,  1.])

In [62]: np.ones([3,2])
Out[62]:
array([[ 1.,  1.],
       [ 1.,  1.],
       [ 1.,  1.]])

# np,zeros() 要素がすべて0のndarray生成.
In [63]: np.zeros(2)
Out[63]: array([ 0.,  0.])

In [64]: np.zeros([2,3])
Out[64]:
array([[ 0.,  0.,  0.],
       [ 0.,  0.,  0.]])

# np.empty() 要素が空のndarray生成.初期化されないためゴミデータが入っている.
# ndarray生成にはones,zerosを利用するのが無難か.
In [65]: np.empty(3)
Out[65]: array([  2.00000000e+000,   2.00000000e+000,   6.94315054e-310])

#np.eye() N*Nの単位行列生成.
In [66]: np.eye(2)
Out[66]:
array([[ 1.,  0.],
       [ 0.,  1.]])

いろいろな生成法がありますね. asarray() , empty()あたりは利用に注意がいるかもしれません.

まとめ

  • numpy紹介
  • ndarray生成法
  • ndarrayのデータ型について