Pythonでデータ分析の勉強を始めるとまず初めに出てくるパッケージ numpy。これはそもそも何か。
numpyのuser documentを読みながらいじってみる。
https://docs.scipy.org/doc/numpy-1.8.0/numpy-user-1.8.0.pdf
numpyがなんであるかは下記の記述に集約されている。
NumPy is the fundamental package for scientific computing in Python. It is a Python library that provides a multidimensional array object,
NumPyはPythonで科学演算を実施する場合に利用できる基本パッケージである。でもなんで基本パッケージなのか?実体としては多次元配列のオブジェクトとそれに関連する計算を提供する、ということである。
多次元配列の作り方、その操作については下記のQuickstartマニュアルを参照しながらいろいろいじってみる。
https://docs.scipy.org/doc/numpy/user/quickstart.html
配列の作成
NumPyは配列を操作するためのライブラリであるから何はともあれ、配列を作成する必要がある。
import numpy as np >>> a = np.array([1,2,3]) >>> a array([1, 2, 3]) >>> a.dtype dtype('int32')
まずはnumpyライブラリを読み込み、numpyが使えるようにする。ここでは3次元空間のある一点を指す値を配列にすることを想定する。3次元空間にはx, y, z軸があるために3つの値が必要である。
配列はarray()を利用する。ここに値を[]で囲って指定すれば配列が出来上がる。出来上がった配列の要素は値によりデータタイプが決まる。上記の場合にはint32である。
指定する値を小数点にすると、データ型は自動的にfloat64になることがわかる。
>>> b = np.array([1.2, 2.5, 3.4]) >>> b array([ 1.2, 2.5, 3.4]) >>> b.dtype dtype('float64')
次に複数の点を持つ3次元配列を作る。これは単純で[]に指定する要素を増やしてあげればよい。ただし(, [, (の位置関係に気を付ける必要がある。
c = np.array([(1,2,3), (1.1, 2.2, 3.3)]) >>> c array([[ 1. , 2. , 3. ], [ 1.1, 2.2, 3.3]]) >>> c.dtype dtype('float64')
さて配列の初期化のためにすべての要素が0もしくは1である配列を作ることはよくある。このようなときに利用できるのがzero()とone()である。
>>> np.zeros((3,4)) array([[ 0., 0., 0., 0.], [ 0., 0., 0., 0.], [ 0., 0., 0., 0.]]) >>> np.ones((3,4)) array([[ 1., 1., 1., 1.], [ 1., 1., 1., 1.], [ 1., 1., 1., 1.]])
多次元配列とは微妙に違うが、数列を作りたいときにもNumPyを利用すれば簡単に生成できる。数列を作成するときにはarray()を利用する。指定するパラメータは初期値、範囲、ステップである。
>>> np.arange(10,30,5) array([10, 15, 20, 25])
さて最後にこれもテストのときには便利なメソッドを紹介する。ランダムの数値で配列を作るメソッドである。
> np.random.rand(3,2) array([[ 0.73481906, 0.54196177], [ 0.91315356, 0.80792015], [ 0.40299783, 0.35722434]]) >>> np.random.randn(3,2) array([[-0.13484072, 0.39052784], [ 0.16690464, 0.18450186], [ 0.80770591, 0.07295968]])
rand()とrandn()の違いは利用する分布である。rand()は(0,1)の正規分布を使う。それに対してrandn()は特定の平均と偏差を持つ分布から値を返す。
配列の表示
作成した配列はデバッグやレポートで表示をする。画面に表示するためにはprintメソッドを利用する。
>>> a = np.arange(6) >>> print(a) [0 1 2 3 4 5]
さて、表示だけならprintメソッドを用いればそれで済むのだが、配列数が多いと見にくくなる。例えば上記の配列に100個の要素がふくまれていると、一行に100個表示される。見にくいし、どの値がいくつ目の要素かもわかりにくい。ここで利用するのがreshapeメソッドである。reshapeメソッドは配列要素の値は変えないが次元を変更できる。
>>> print(a) [0 1 2 3 4 5] >>> print(a.reshape(2,3)) [[0 1 2] [3 4 5]] >>>
基本操作
ここまで配列を作成し、表示もできた。これで作成した配列を画面で確認できる。次に配列に対していろいろな操作をしてみる。ここではまず四則演算を行ってみる。
>> a = np.array([20,30,40,50]) >>> b = np.arange(4) >>> b array([0, 1, 2, 3]) >>> a+b array([20, 31, 42, 53]) >>> a-b array([20, 29, 38, 47]) >>> b*2 array([0, 2, 4, 6]) >>> a<35 array([ True, True, False, False], dtype=bool) >>> a[a<35] array([20, 30])
Universal関数
numpyでもっとも利用頻度が高い機能の一つがUniversal関数である。行列に関係する演算はほぼすべてカバーしている。行列演算の基本処理は自分で実装する必要はまずない。行列演算をしようと考えたらまずはこのUniversal関数で目的とする関数が提供されていないか、確認すべきである。
>> a=np.arange(3) >>> a array([0, 1, 2]) >>> np.exp(a) array([ 1. , 2.71828183, 7.3890561 ]) >>> np.max(a) 2 >>> np.min(a) 0 >>> np.sqrt(a) array([ 0. , 1. , 1.41421356])
インデックス、スライス、イテレーション
さて基本演算はできた。ここから先は行列要素に対する演算というよりも、行列全体に対する演算である。行列を扱うときに頻繁に出てくる処理の一つとして、部分要素を取り出して、新しく行列を作る操作がある。これを実現する方法に3種類ある。インデックス、スライス、イテレーションである。
まずはインデックスを見てみる。
>>> a=np.arange(10)**3 >>> a array([ 0, 1, 8, 27, 64, 125, 216, 343, 512, 729], dtype=int32) >>> a[2] 8
インデックスは文字通り、インデックス番号で値を取得する。インデックスで指定できるのは一つである。よってインデックスで戻る要素も一つである。インデックスの初期値は0である。そのためにインデックスの最大は配列サイズ-1になることについて注意をしておく。
>>> a[a.size-1] 729
個別の要素に対して処理を加える時に、インデックス処理とループを組み合わせて、利用されることが多い。
次にスライスである。スライスは文字通り、配列をスライスして、部分を切り取る。
>>> a[2:5] array([ 8, 27, 64], dtype=int32)
最後がイテレーションである。イテレーションとはなんじゃらほい、という疑問があるかもしれない。要するに要素をループで簡単に処理しましょうというコード記述方法である。
ループ処理では取得する要素を順番にカウントアップする必要がある。しかしnditer()メソッドを利用すると配列の中の要素を勝手に順番に取得して、一時変数に代入してくれる。
>>> for x in np.nditer(a): ... print(x) ... 0 1 8 27 64 125 216 343 512 729
shape
shape()は要素の値を変えずに配列の形式を変更する。これが使いこなせると、numpyでの行列操作がより身近に感じられる、自由に行列を操作できるようになる。
>>> a = np.floor(10*np.random.random((3,4))) >>> a array([[ 5., 9., 2., 3.], [ 4., 8., 3., 2.], [ 1., 6., 4., 0.]]) >>> a.shape (3, 4) >>> a.reshape(6,2) array([[ 5., 9.], [ 2., 3.], [ 4., 8.], [ 3., 2.], [ 1., 6.], [ 4., 0.]]) >>> a.T array([[ 5., 4., 1.], [ 9., 8., 6.], [ 2., 3., 4.], [ 3., 2., 0.]]) >>> a.reshape(6,2).T array([[ 5., 2., 4., 3., 1., 4.], [ 9., 3., 8., 2., 6., 0.]])
複製とビュー
行列を操作するときに、代入と複製は全く違うということに注意を払う。
下記は代入操作である。代入操作の場合には、変数aとbが示す行列を同一であることがわかる。bに加えた操作はすなわちaに加えた操作にもなる。操作後のbと操作後のaは必ず同じ結果を返す。
>>> a = np.arange(12) >>> a array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]) >>> b = a >>> b is a True >>> b.shape = 3,4 >>> a array([[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11]])
ビュー(浅いコピー)
ビューもしくは浅いコピーと呼ばれる操作は表示形式のみを複製し、データは複製元を共有する。
この操作はview()メソッドを利用する。
>>> c = a.view() >>> c is a False >>> c.shape = 2,6 >>> c array([[ 0, 1, 2, 3, 4, 5], [ 6, 7, 8, 9, 10, 11]]) >>> a array([[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11]]) >>> c[0,4] = 1234 >>> a array([[ 0, 1, 2, 3], [1234, 5, 6, 7], [ 8, 9, 10, 11]])
上記を見るとわかるが、shapeでcについて表示形式を変更しても複製元であるaの表示形式には影響していない。それに対してcに含まれる要素の値を変更するとaについても対応する要素の値が変わることがわかる。
さて最後は形式と値の両方を複製する深いコピー操作である。この操作はcop()メソッドを利用する。
>> d=a.copy() >>> d array([[ 0, 1, 2, 3], [1234, 5, 6, 7], [ 8, 9, 10, 11]]) >>> d.shape = 2,6 >>> a array([[ 0, 1, 2, 3], [1234, 5, 6, 7], [ 8, 9, 10, 11]]) >>> d array([[ 0, 1, 2, 3, 1234, 5], [ 6, 7, 8, 9, 10, 11]]) >>> d[0,0] =6789 >>> d array([[6789, 1, 2, 3, 1234, 5], [ 6, 7, 8, 9, 10, 11]]) >>> a array([[ 0, 1, 2, 3], [1234, 5, 6, 7], [ 8, 9, 10, 11]])
numpyでよく利用される操作についてまとめてみた。numpyにはこれらの基本となる操作以外にも様々な応用操作が提供されている。こちらは実際のデータ分析に取り組みながら勉強していきたい。