例題12-2:実験結果のグラフを描く(初級)

A: さて、さっそく前回の続きです。今回は前回のサンプルプログラム12-1a.pyの最後の部分を取り上げます。 問題の部分を改めて下に抜き出しておきます。

259########################################
260# 実験結果のグラフを出力
261
262if fPlotData:
263    import pylab
264    import matplotlib.font_manager
265
266    fontprop = matplotlib.font_manager.FontProperties(fname=font_name)
267
268    data = pylab.array(gData)
269    mL = pylab.zeros(5)
270    sL = pylab.zeros(5)
271    mR = pylab.zeros(5)
272    sR = pylab.zeros(5)
273
274    for i in range(5):
275        idx = (data[:,0]==0) & (data[:,1]==30*(i+1))
276        mL[i] = pylab.mean(data[idx,2])
277        sL[i] = pylab.std(data[idx,2])
278        idx = (data[:,0]==1) & (data[:,1]==30*(i+1))
279        mR[i] = pylab.mean(data[idx,2])
280        sR[i] = pylab.std(data[idx,2])
281
282    pylab.plot([30,60,90,120,150],mL,'bs-',label=u'左にプローブ')
283    pylab.plot([30,60,90,120,150],mR,'rs-',label=u'右にプローブ')
284    for i in range(5):
285        pylab.plot([30*(i+1),30*(i+1)],[mL[i]-sL[i],mL[i]+sL[i]],'b-')
286        pylab.plot([30*(i+1),30*(i+1)],[mR[i]-sR[i],mR[i]+sR[i]],'r-')
287    pylab.plot([10,185],[cMidLineLength,cMidLineLength],'k:')
288    pylab.text(20,200,u'主線の物理的な長さ',fontproperties=fontprop)
289    pylab.xlabel(u'矢羽の角度(度)',fontproperties=fontprop)
290    pylab.ylabel(u'主線の主観的な長さ(ピクセル)',fontproperties=fontprop)
291    pylab.xticks((30,60,90,120,150))
292    pylab.legend(prop=fontprop,loc='lower right')
293
294    pylab.show()

B: ええと、どこから聞いたらいいのかわからないくらい、わからいところだらけなんですが…。

A: うむ。実は私もどう説明したらいいか迷っている。基本的には、例題11で紹介したpylab.plotが主役だ。 例題11と同じように、268行目で変数gDataに保存されている実験結果のリストをnumpy.ndarray型に変換している。

B: 268行目? pylab.array(gData)って書いてありますけど。

A: pylabモジュールの中でnumpyをimportしているから、pylab.arrayとしても実体はnumpy.ndarrayだ。 pythonインタプリタでtype()を使って型を確認してみればわかる。

>>> import pylab
>>> a = pylab.array([1,2,3])
>>> type(a)
<type 'numpy.ndarray'>

B: うーん、なんか釈然としないなあ。

A: まあまあ、ここでつまづいていたら全然話が進まないんで、そういうものだと思っておいてくれ。 で、続いて269から272行目に、プローブ刺激の左右別に5種類の矢羽の角度に対する反応の平均値と標準偏差を格納するための配列をpylab.zeros()という関数を用いて確保している。 pylab.zeros()はもともと要素が0のベクトルや行列を作るためのもので、この例のようにスカラー値が指定されればその次元のベクトルを、pylab.zeros((3,2))のようにタプルやリストを指定するとその次元の行列を作成する。 まあ、ここでは行列とか全然意識する必要はなくて、5つの実数を保存しておける変数を確保できればただのリストでも構わない。

B: じゃあなんでわざわざpylab.zeros()とかいうのを使ってるんですか?

A: 単にその時の気分だな。

B:

A: mLとsLが左にプローブ刺激が出た時の平均と標準偏差、mRとsRが右にプローブ刺激が出た時の平均と標準偏差だ。 これらの値を計算して代入していくのが274行目から280行目のforループだ。ここでぜひ覚えておいて欲しいテクニックが登場する。275行目と278行目がそうだ。

B: ふむふむ。

A: numpy.ndarray型の行列と数値を<や=といった演算子で比較すると、行列の個々の要素に対して比較を行った結果を格納した行列が得られる

In [1]: a = array([1,7,3,4,2,8,5,6])

In [2]: a < 4
Out[2]: array([ True, False,  True, False,  True, False, False, False],
dtype=bool)

B: 出力の最後のdtype=boolってなんですか?

A: まさにこの例が示す通り、numpy.ndarrayは行列といいつつ数値以外の値も要素として持つことができる。 このあたりは matlabの行列との大きな違い だ。で、最後のdtypeというのは演算の結果得られたこの行列がbool、すなわちTrueかFalseを要素としてもつ行列であることを示している。

B: うーん、なんだかよくわかりませんが、Aさんの口ぶりではすごいことのような感じがします。

A: いろいろ言いたいことはあるが、どんどん脱線してしまうので先を急ごう。 今回押さえておきたいnumpy.ndarray型のもうひとつの重要な性質は、 numpy.ndarray型の行列の添え字にbool型を要素とする行列を指定すると、Trueになっている要素に対応する要素だけを抜き出した行列が得られる ということだ。

B: ???

A: つまり、例えば上の例のa<4の結果を変数bに保存しておくと、a[b]とすればaの中で値が4未満の要素を抜き出してくることができる。

In [3]: a = array([1,7,3,4,2,8,5,6])

In [4]: b = a < 4

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

B: ??? ええと、4未満の要素だけを抜き出したというのはわかりましたが、今何をしようとしてるんでしたっけ。

A: 実験結果の矢羽の角度別に反応の平均値や標準偏差を計算しようとしている。275行目を詳しく見ていこう。 ちょっと前回のサンプルプログラムを確認してもらわないといけないんだが、変数dataは3列の行列で、0列目に標準刺激の位置が左=0、右=1として格納されている。 1列目には矢羽の角度が格納されている。30、60、90、120、150のいずれかだな。そして、2列目には被験者の反応が格納されている。それを踏まえて275行目を分解すると…

data[:,0]

dataの0列目を抜き出したもの

data[:,0]==0

dataの0列目が0である行がTrueであるベクトル

data[:,1]

dataの1列目を抜き出したもの

data[:,1]==30*(i+1)

dataの1列目が30*(i+1)である行がTrueであるベクトル

idx = (data[:,0]==0) & (data[:,1]==30*(i+1))

dataの0列目が0で1列目が30*(i+1)である行がTrueであるベクトルを変数idxに格納

B: むむむ。難しいな。何より「~である行がTrueであるベクトル」っていう日本語がよくわかりませんが。

A: うーん、どう言ったらわかりやすいかな。こちらが指定した条件に合致した行はTrue、しない行はFalseというラベルを作成するといった感じかなあ。 そして上の表の最後の行は、ラベル同士は&演算子で論理積や論理和を取ることができることを示している。これを使えば、さまざまな条件を組み合わせてデータを抜き出してくることができる。

B: ええと、論理積と論理和ってのは…

A: A & Bが論理積で「AかつB」、A | Bが論理和で「AまたはB」だな。こうやって作成したラベルを使ってデータを取り出す例が276行目。 行につけたラベルなんだから、行のところにこのラベルを指定すればいい。取り出したいデータは2列目だから、data[idx,2]とすればいいわけだ。

B: あのー、基本的なことですが、idx[x,y]のxが行でyが列ですか?

A: おっと、そこから説明しないといけなかったか。その通りだ。30*(i+1)は30、60、…になるから、i=0のときは 結果として矢羽の角度が30度でプローブ刺激が左の時の反応、i=1の時は矢羽の角度が60度でプローブ刺激が左の時の反応、…が取り出せる。

B: うーん、これをマスターするのは大変そうだなあ。

A: 決まりきったパターンなので、2、3回自分で書いたら簡単に覚えられると思うよ。 さて、こうやって取り出したデータに対して276行目、277行目でそれぞれpylab.mean()、pylab.std()で平均と標準偏差を計算している。

B: それはなんとなくわかります。

A: えーと、Matlabな読者の方々ためにふたつ注意しておきますと、まずMatlabのstd()は不偏標準偏差を計算しますが、pylab.std()は標本標準偏差を計算します。 pylab.std()で不偏標準偏差を計算する場合は、標準偏差の計算式でNから引く値を指定するddofという引数を使ってpylab.std(data,ddof=1)とします 。 それから、Matlabでは行列に対してmeanやstdを適用すると、要素数が1ではない最初の次元に対して計算しますが、pylab.mean()やpylab.std()は行列のすべての要素に対する平均や標準偏差を計算します。 次元を指定して計算するには、axisという引数を使ってpylab.mean(data,axis=1)のように指定します

B: ぐは、さっぱりわかりません。

A: 不偏標準偏差の計算の仕方は押さえておきたいところだが、ここではとにかくnumpy.ndarray型の行列から狙ったデータを抜き出す方法を覚えてほしい。 さて、どんどん解説が長くなっているので先を急ごう。計算した平均と標準偏差を用いて、282行目からグラフを描画している。 エラーバーがついた折れ線グラフを描こうとしていて、実はエラーバー付き折れ線グラフを描くpylab.errorbar()という関数があるんだが、練習のために敢えてpylab.plot()で描いている。 例題11のpylab.plot()の解説を踏まえながらよーく考えてほしい。

B: …解説が面倒くさくなって逃げましたね?

A: 何を言うか。このサンプルでは、もっと他に説明しておくべきことがたくさんあるんだ。 第一に282行目と283行目のpylab.plot()の第3引数。これは例題11でもちらっと触れたが、グラフの色や線を指定する文字列だ。具体的には以下の文字を組み合わせる。

線の種類

マーカーの種類

solid line

.

point

--

dashed line

,

pixel

-.

dash-dot line

o

circle

:

dotted line

v

triangle_down

^

triangle_up

<

triangle_left

b

blue

>

triangle_right

g

green

1

tri_down

r

red

2

tri_up

c

cyan

3

tri_left

m

magenta

4

tri_right

y

yellow

s

square

k

black

p

pentagon

w

write

star

h

hexagon1

H

hexagon2

plus

x

x

D

diamond

d

thin_diamond


vline

hline

B: うわ、結構ありますね。なんで英語なんですか?

A: help pylab.plotで出てくるのをそのままHTMLにしただけだからな。HTMLにするのが面倒くさくて表が完成した時点でやる気が尽きた。

B: 色ってこれだけの種類しか使えないんですか?

A: RGB値を直接指定する方法もあるが、今回はそこまでは勘弁してくれ。 こういうのはぐだぐだ例を挙げるより自分でいろいろやってみる方がわかりやすいと思うので、皆さんもぜひいろんなグラフを描いてみてほしい。

B: これが第一って言ってましたよね。次のポイントは?

A: やはりpylab.plot()の引数なんだが、282行目と283行目のpylab.plot()ではlabelという引数が指定されている。 これを指定しておくと、グラフを描画した後でpylab.legend()という関数を呼ぶとグラフの凡例を描画してくれる。サンプルプログラムでのpylab.legend()の呼び出しは292行目だね。 出力はこんな感じになる。

../_images/12-2-01.png

B: おお、結構ちゃんとしたグラフだ。

A: なんだよ、「結構ちゃんとした」って。補足しておくべき点が三つ。まず、日本語でラベルを付けるときにはUnicode文字列にしなきゃいけない。 これは例題1からずっとおなじみだよな。それから、凡例の位置はpylab.legend()のlocという引数で指定する。指定できるのは以下の通り。

自動判別

'best'

左上

'upper left'

'upper center'

右上

'upper right'

'center left'

中央

'center'

'center right'または 'right'

左下

'lower left'

'lower center'

右下

'lower right'

B: 'best'ってなんかすごいですね。VisionEggのanchorと似ているようで違う…

A: まあ作者が別だからその辺の違いは仕方がないな。 あともうひとつの補足だが、pylab.plot()で日本語を表示するためにはフォントを指定する必要がある。これはpylab.legend()に限った話ではないので、補足というよりは新しい話題だな。

B: まあ、また新しい話題ですか。

A: もう一息だから付き合え。pylab.plot()で日本語フォントを指定するのは厄介で、webを検索してもあまり情報がない。 で、どうすればいいかってことだが、結論だけいうとmatplotlib.font_manager.FontPropertiesを使うといい。

B: また長ったらしい名前のモジュールが…。matplotlibってなんですか?

A: pythonでグラフを描画するためのモジュールだ。 実は、今まで使っていたpylab.plotというのはmatplotlibの機能をMatlabっぽく使うためのモジュールなんだ。 もともと私自身はMatlabを買う金がないんで何か代わりになるものはないだろうかって魂胆でpythonを触りだしたんで、成り行きでpylabを使ってグラフを描くようになった。 全然Matlabなんか知らないって人は本当はpylabを使う必要はないんだけど、まあ万一学生さんがこれを読んでいて、ポスドクとかになった時にMatlabを使うように求められたりすることもあるかも知れないんで、 Matlabぽい作法を知っておくのも悪くなかろうと思ったりなんかして、その…

B: 要するに、面倒くさかったんですね?

A: その、まあ…そうだ。

B: もうこのパターンばかりなので慣れました。

A: とにかく。pylabの範囲でフォントを指定する方法がわかんなかったので、ご本尊のmatplotlibにお出ましいただいたわけだ。 matplotlib.font_manager.FontPropertiesは名前通りフォントのプロパティを扱うクラスで、pylabで文字を描画する関数の多くはmatplotlib.font_manager.FontPropertiesのインスタンスを引数に取ることができる。 まず、266行目のようにfnameにフォントファイル名を指定してmatplotlib.font_manager.FontPropertiesのインスタンスを作成し、変数に保存しておく。

B: font_nameってなんて値が入ってたんでしたっけ。

A: 前回示したサンプルプログラムの全体を見てもらえばわかるが、Win32環境ならr'C:\Windows\Fonts\msgothic.ttc'だな。 いわゆる「MSゴシック」だね。それで、288行目から292行目で日本語の文字列を含むラベルやらなんやらを出力しているんだが、その時にmatplotlib.font_manager.FontPropertiesのインスタンスをそれぞれ指定している。 ちなみにこの指定をしなければ、こんな風に文字化けしてしまう。

../_images/12-2-02.png

B: 日本語を使うって面倒ですねえ。英語圏の人はずるいなあ。

A: これで大体難しいところは解説できたかな。後は288行目から292行目で使っている関数について簡単に触れておこうか。 文字列を回転させたりとか、anchorをどこにするかとか、山ほどオプションがあるんだけど、そういうのはIPythonでhelp ほげほげ して調べてください。 気が向いたらいずれ解説します。以上、おしまい。

pylab.text(x,y,text)

(x,y)の位置にtextで指定された文字列を表示する。

pylab.xlabel(text)

X軸にtextで指定された文字列をラベルとして表示する。

pylab.ylabel(text)

Y軸にtextで指定された文字列をラベルとして表示する。

pylab.xticks(ticks,labels)

X軸のticksで指定された位置に目盛を付ける。labelsが指定されていれば、目盛の位置にlabelsに従ってラベルを付ける。Y軸に目盛を付けるときはyticks()を使う。

B: お疲れさまでした。ずいぶん長くなりましたね。この内容を前回のと合わせて1回に押し込もうとしていたとは、作者も見通しが甘いですね。

A: あー、見通しというか、奴はなーんも考えてないからな。

B: じゃあ、これで例題12はお開きですか?

A: いや、例題12では小ネタの在庫一掃ということで、もうひとつ実験プログラムを紹介する。まあ、実験プログラムといってもちょっとアレなんだが…

B: へ、なんですって?

A: いや、なんでもない。ではまた次回。