NumPyで散布図を書く

NumPy + Matplotlibの環境構築がうまく行かなくてほったらかしにしていたのだけどいい加減ほったらかしにしてられなくなってきたので「明確な目標を立ててそれの達成のために必要最小限だけタイムトライアルで学ぶ」というアプローチで頑張ってみた。

ipythonとnumpyとmatplotlibはインストール済み。しかしmatplotlibの方のチュートリアルはshowを叩いても何も表示されなくてインストールできてるのかできてないのかよくわからん状況。前回はそこまでやって放置した。Pyplot tutorial — Matplotlib v1.1.0 documentation

まずはNumPyのチュートリアルをざっと眺める。Tentative NumPy Tutorial -

ndarrayはmatrixの親クラスで、N-dimension array。arrayで作る。import *とか、何がどこからきたかわかりにくくなるから嫌いなんだけどとりあえず従っておく。

In [1]: from numpy import *

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

N次元の配列ということで、Pythonの普通のシーケンス型がxs[3]で値を取れるように、xs[3, 3]で値を取れるように拡張されている。そしてxs[:]ができるのと同様にxs[:, :]もvalid。面白い。

とりあえずベクトル演算とか試そうと思って array([1, 2, 3]).transpose() したら期待と違って変化せず。

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

In [4]: x.transpose()
Out[4]: array([1, 2, 3])

これはx.shapeを見れば理由がわかる。行列が2次元の構造でベクトルは1次元の構造だ、と思ったら間違いで、ベクトルに縦ベクトルと横ベクトルの2種類があるってのはやはりそれが2次元の構造だからなんだな。というわけで(3,)って1次元の構造だったのを(1, 3)にreshapeしたら期待通りの挙動をするようになった。

In [5]: x.shape
Out[5]: (3,)

In [6]: x.reshape(1, 3)
Out[6]: array([[1, 2, 3]])

In [7]: _.transpose()
Out[7]: 
array([[1],
       [2],
       [3]])

matrixを使えば解決なんだけどmatrixには罠がいっぱいあるという噂なので避けてみた。罠って具体的になんなんだろうね。ここらへんはc_とかr_とかで楽になるのかな。

In [14]: c_[[1, 2, 3]]
Out[14]: 
array([[1],
       [2],
       [3]])

arrayとスカラーで演算をするとスカラーがarrayの要素それぞれに対してブロードキャストされる。arrayの添字にはarrayを渡すこともできて、intのarrayならその添え字が選択され、boolのarrayならTrueの所が選択される。そしてそれに対して破壊的代入もできる。クール。

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

In [20]: x > 1
Out[20]: array([False,  True,  True], dtype=bool)

In [21]: x + 1
Out[21]: array([2, 3, 4])

In [22]: x[x % 2 == 1] -= 1

In [23]: x
Out[23]: array([0, 2, 2])

x.view()がシャローコピー、x.copy()がディープコピー。

max, min, sumがある。cov, mean, std, varがある。svdもある。楽チン。

さてそろそろ散布図を書こう。いろいろなチュートリアルやサンプルがいろいろ別なものをimportしていてよくわからないが、とりあえず一番シンプルなfrom pylab import *で試してみる。ref. pythonグラフライブラリ「matplotlib」覚書 - Pashango’s Blog ちなみにimport pylabしてpylab.でTABを押してどんなメンバがあるか一応眺めておいた。917個もあって萎えた。fromfunctionはこの中にあるのか。

In [27]: from pylab import *

In [28]: scatter(randn(100), randn(100))
Out[28]: <matplotlib.collections.CircleCollection at 0x103fabe90>

randnは標準正規分布に従う乱数のarrayを作る。で、ここでshow()するとグラフのウィンドウが出る環境もあるらしいのだが、僕の環境では出ない。困って調べていると、そうかファイルに出力すればいいか。ref. matplotlib でグラフを描画する。 - loumo.jp

In [30]: import matplotlib

In [31]: matplotlib.pyplot.savefig('test.png')

In [32]: !open test.png

できたできた。ちなみにいい忘れたけども僕の環境はMacOSX 10.6.8で、openはMacOSXのコマンド。ファイルを適当なアプリで開いてくれる。僕の場合プレビューが開く。で、プレビューは開いているファイルが変更された場合自動的に再読み込みをしてくれる。ん、じゃあshow()動かなくてもたいして困らないか。

ここでパラメータを変えてグラフを描き直してみたくなると思うんだけど、このまま描き直しても前のグラフに追記されてしまう。対話的にいろいろ試したいならグラフを消して書きなおす関数がclf()なのを覚えておく必要がある。 (clear figureの略だろうな)

まとめ

In [1]: from numpy import *
In [27]: from pylab import *
In [30]: import matplotlib

In [28]: scatter(randn(100), randn(100))
Out[28]: <matplotlib.collections.CircleCollection at 0x103fabe90>

In [31]: matplotlib.pyplot.savefig('test.png')
In [32]: !open test.png

# パラメータを変えて描き直してみる
In [33]: clf()

In [39]: data = c_[randn(100), randn(100)]

In [40]: data = data.dot(array([[1, 1], [0, 1]])) # 斜めにしてみる

In [42]: scatter(data[:, 0], data[:, 1])
Out[42]: <matplotlib.collections.CircleCollection at 0x104411290>

In [43]: matplotlib.pyplot.savefig('test.png')

追記: scatterのオプション引数でmarker="+"とかできるの書き忘れた

追記: alpha=0.5を指定するとかっこいい

ipythonとpdbの組み合わせで人生を楽にする方法

予期しない例外が起きた場合:

In [55]: scatter(data[:, 0], data[:, 1], marker=".")
---------------------------------------------------------------------------
(中略)
/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/matplotlib/axes.pyc in scatter(self, x, y, s, c, marker, cmap, norm, vmin, vmax, alpha, linewidths, faceted, verts, **kwargs)
   5736             sym = syms.get(marker)
   5737             if sym is None and verts is None:
-> 5738                 raise ValueError('Unknown marker symbol to scatter')
   5739             numsides, rotation, symstyle = syms[marker]
   5740 

ValueError: Unknown marker symbol to scatter

なんだよどこに使えるシンボルの一覧があるんだよこれ(matplotlib.lines.Line2D.set_marker)じゃないのかよと怒る前に焦らず騒がずpdb

In [56]: pdb
Automatic pdb calling has been turned ON

In [57]: scatter(data[:, 0], data[:, 1], marker=".")
> /opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/matplotlib/axes.py(5738)scatter()
   5737             if sym is None and verts is None:
-> 5738                 raise ValueError('Unknown marker symbol to scatter')
   5739             numsides, rotation, symstyle = syms[marker]

ipdb> syms
{'x': (4, 0.7853981633974483, 2), '^': (3, 0, 0), 'd': (4, 0, 0), 'h': (6, 0, 0), '+': (4, 0, 2), 'o': (0, 0, 3), 'p': (5, 0, 0), 's': (4, 0.7853981633974483, 0), 'v': (3, 3.141592653589793, 0), '8': (8, 0, 0), '<': (3, 4.71238898038469, 0), '>': (3, 1.5707963267948966, 0)}

うーん、少ない。三角形を4種類も入れなくていいから点を入れといて欲しかったなぁ。

ちなみにpdbモードはqで抜けられて、もう一回pdbって打つとAuto-pdbはOFFになる。

さらにちなみに「なんだよどこに使えるシンボルの一覧があるんだよ」に関してはhelp(scatter)したら書いてあったゴメンナサイ。