pureで統計

2014 アケオメ コトヨロ

去年頂いた『空飛ぶPython』を読んだ。Python3で頑張りましょって趣旨で書かれた本だ。 おいらは見よう見まねでPythonを使ってきたんで、こういう本は有り難い。炬燵に入って、 ぬくぬくしながら読みましたよ。

そして、むくむくとパソコンに向かうのでした。とは言え、いまだにデストリでデフォで Python3系をシステムスクリプトとして採用するって言う、勇気のある開発者はいないみたい だけどね。 そんな訳で、Python3が入ってる万次郎で試しつつ。本では、開発環境として、なんてったって アイドルってのを薦めていたけど、おいらはipythonさ。

[sakae@manjaro ~]$ ipython
Python 3.3.3 (default, Nov 26 2013, 13:47:45)
Type "copyright", "credits" or "license" for more information.

IPython 1.1.0 -- An enhanced Interactive Python.
?         -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help      -> Python's own help system.
object?   -> Details about 'object', use 'object??' for extra details.

In [1]: a = ("Happy", "Python")

In [2]: a = ("Happy", "Ruby")

In [3]: a
Out[3]: ('Happy', 'Ruby')

あれ? タプルは書き換え出来ないよって、本のあちこちで注意されてたけど、出来ちゃったよ。 これって得意の仕様変更が入ったの? よう分からんわい。まてまて、幾らなんでも、もう少し 調べれ!

In [4]: a[1] = 'pure'
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-4-07d8b85b7f99> in <module>()
----> 1 a[1] = 'pure'

TypeError: 'tuple' object does not support item assignment

やっぱり、オイラーの早とちりだったわい。まだまだ修行が足りんな。でも何故タプルだけ 書き換え出来なくしたの? 作者さんが関数族議員から脅されて、しぶしぶ実装した?

In [6]: dir(a)
Out[6]:
['__add__',
   :
 '__rmul__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 'count',
 'index']

ここに出て来る、アンダーバーで固めた変数(?)が、Pythonの肝みたいだ。

In [7]: a.__str__
Out[7]: <method-wrapper '__str__' of tuple object at 0xb690340c>

gdbで開くと、オブジェクトそのものが見えるんだな。ちなみに、こやつはhaskellで言う showに相当するらしい。まあ、外枠は違っても、中は案外一緒っぽかったりして。

こんな暴言を吐くと、あの人が怒るかな。中身は一緒っぽかったりしても、それを取り巻く 応援団がいっぱい居るかが、言語の人気のバロメータだよと、かの本は言う。pythonは バッテリー。いろいろなライブラリィーが同梱されて出荷されてるから、余り余計な物は 入れなくても使えるよと。

便利そうなのの代表として、こんなのが出てた。

In [11]: import shelve
In [12]: lang = shelve.open("hoge")
In [13]: lang['ruby'] = ('matz', 'japan')
In [14]: lang['gauche'] = ('shiro', 'japan')
In [15]: lang.close()

Python流のデータ永続化。下請けにpickleモジュールを使って、簡単インターフェースを 実現してる。手軽に使えて便利そう。

便利だよー、だからみんなPythonで飛んでる世界を体験しよー。そして、disるのも忘れて いません。やっぱりあちらの人は一神教なんですな。 データ・サイエンスのプログラミング言語はRからPythonに置き換わる なんていう記事にも表れてきている。どうなんですかね? 2013 Advent Calendarでも眺めてみれば、 今後の方向性が見えてくるかな。 で、おいらは去年に引き続き pure なんていう超マイナーなドイツ方言丸出しのHaskell もどきを使ってみる。なんたって、おいらは八百万神の国の人だもの、おおらか、おおらかに。

ああ、その前に、LispでOSを書く なんて刺激的なのを発見したぞ。これを試すには、gauche用の c-wrapperを入れれば良いのか。 後で、fedoraででも試してみるかな。

pure-csv

年が明け、還暦を過ぎて還暦プログラマの挑戦(Haskell に挑む→F#による言語造り) という状況。この方は病気のデパート風に伺えるけど、おいらも負けてはいないぞ。血圧高め という状況で、ずっと薬を飲んでる。

薬が切れそうなので、また貰ってこなければいけないんだけど、おいらがPythonで書いた 血圧記録・グラフかプログラム。一年毎の漬物にしてるんで、年初はデータが十分に溜まって いないのよ。(医者に行く時は、グラフを持って行くんだけど、そのグラフを有難がって コピーさせて下さいって言われる)

そんな訳で、今回は去年のまとめを持って行こう。一年の総括ですな。どんな総括すれば いいの? そりゃ、医学 == 統計 でしょ。死亡率、罹患率、各種検査値からの異常発見、 これみんな統計技術ですよ。

最近患者が減ったなあ。じゃあ、患者を作ればいいんだな。誰でも分かる、腹回りなんてどうだ。85cm以上は 患者にしちゃえ。えと、腹回りと死亡率でも調べてみるか。カチャカチャカチャとEXCELを操作 する音。だめじゃな。そんじゃ、腹回りと 成人病の罹患率はどうだ。おおおー、優位な相関があるぞ。こりゃ使える。ってな具合。

なら、血圧データも統計処理したのを持ってけ。まあ、町医者に理解出来るかどうかは はなはだ疑問だけど。。おいらの知見の拡充にはなるな。プログラミングを通じてね。

Pythonからの出力は、いわゆるcsvファイルにしてる。

[sakae@pcbsd ~/src/pure]$ head -n 4 2013.csv
130101.04,118,73,57
130101.21,109,70,71
130102.04,128,82,60
130102.21,105,62,67

一行が4項のデータ。左から yymmdd.hhの測定日時、最高血圧、最低血圧、脈拍っていう並びだ。 これをpureで処理しよう。pureはマイナーゆえ、あれこれモジュールを同梱してない。 けど、有志がいろいろ作って提供してる。今回は、 Pure-CSV - Comma Separated Value Interface for the Pure Programming Language を使おう。

> using csv;
> using namespace csv;
> let p = open "2013.csv";
> let v = fgetr p;
> close p;
()

普通にcsvファイルをオープンして、読み込んでからファイルをクローズする。超簡単っぽい。 どんな具合に読み込まれたか、確認。

> # v;
720
> take 4 v;
[{"130101.04","118","73","57"},{"130101.21","109","70","71"},{"130102.04","128","82","60"},{"130102.21","105","62","67"}]

朝に夜に血圧を測定してるんで、データは730あるはずだけど、5日分欠落してる。どこかへ 遊びに行ったとかで、データ無しなんだな。そんな事より、取り込んだデータが、文字列に なってるのが、いやらしいな。ちゃんと数値として読み込んで欲しいぞ。でも、測定日時が 数値になっちゃうと、困るな。だって、起床時と就寝時と分けて解析したいし。問題山積み。

マニュアルを精読すると、数値は数値らしく読み込むオプションが有るっぽい。そうすると、 日時のフォーマットを変えておかねば。 ついでに、csvファイルを指定してデータを読み込んでくれる関数を定義しておくか。

mycsv f =  v with
             d = dialect {quote_flag=>MINIMAL};
             p = open (f, "r", d);
             v = fgetr p;
             z = close p;
           end;

これでいいかな?

> let m = mycsv "2013.csv";
> take 2 m;
[{"130101:04",118,73,57},{"130101:21",109,70,71}]

うん、これでいいな。尚、オプションに指定した dialect って、方言って意味なのね。 csv.pureなんてファイルを見ると、EXCEL方言とか、RFC4180とかが登録されてた。

[sakae@pcbsd ~]$ rfc 4180
The Result:
4180 Common Format and MIME Type for Comma-Separated Values (CSV)
     Files. Y. Shafranovich. October 2005. (Format: TXT=12931 bytes)
     (Status: INFORMATIONAL)

RFCになったのは、以外に遅かったのね。CSVは適当に複雑なので、これを肴にRubyのあの人が 初Ruby本に解説してたな。RWH本にも有るぞ。HaskellのHackageにも山のように登録 されてる。こんなに有るのも迷惑だなあ。余り沢山選択子があると、どれも選ばれない ってのは、ビジネス界の常識ですだ。その点、pureは安心。ああ、話がそれたわい。

えと、少し統計関係の関数を定義しておくかな。

using math;          // for sqrt

sum xs = foldl (+) 0 xs;

group n [] = [];
group n xs = (take n xs) : (group n (drop n xs));

avg xs     = sum xs / #xs ;
ss  xs     = sum [(x - avg xs) ^ 2 | x = xs;] / ( #xs - 1) ;
sd  xs     = sqrt $ ss xs ;

median xs  = if #xs mod 2 == 1 then
               head $ drop (#xs div 2) $ sort (<) xs
             else
               (rs ! 0 + rs ! 1) / 2 with
                 rs = drop (#xs div 2 - 1) $ sort (<) xs
                      end;

sumは、Haskellにはデフォで付いているんだけど、pureには見当たらなかったので自前で定義。 ひょっとしてドイツ方言で、合計って定義されてるんですかね?

groupはHaskell方面で他の機能を実現してるかも知れないけど、リストを指定したエレメント 数で分割してくれるもの。ssは有名な分散ですな。各項を求める度に平均を求めてるけど、 これって無駄かな。まあ、一行野郎って事で許してくだせえ。

medianって、データを並べ替えした時の真ん中にある数値を取り出す演算。データ群の代表値 としてavgが普通に使われるけど、medeainの方が良いと仰る先生がいます。データ数が奇数と 偶数で場合分けしなきゃならないんだけど、pure君はoddもevenも持っていない。んな訳で、 さりげなく紛れ込ませておいたよ。

取り合えず、これだけあればいいかな。

> let am = [x | x = m; x ! 0 ! 7 == "0"] ;
> let pm = filter (\x -> x ! 0 ! 7 == "2") m;

分析に先立ち、起床時と就寝時にデータを分けてみた。x!0 で、測定日時を選び出し、 そいつに!7を適用して、時の上位文字を指定してる。0なら起床時って訳。選び出すには、 条件を内包表記の中に書いてもいいし、ずばりfilter関数を使ってもよい。

> let hi  = [x!1 | x = am; ];
> let low = [x!2 | x = am; ];
> let pls = [x!3 | x = am; ];

更に、起床時のデータを、最高血圧、最低血圧、脈拍に分解。

> avg hi; sd hi; median hi;
119.644444444444
7.1654713865796
120.0

算術平均、標準偏差、中央値を出してみた。無駄に下位桁まで表示しているな。 こういう時は、表示を制限しちゃおう。

> using system;
> __show__ x::double = sprintf "%0.1f" x;
> avg low; sd low;
77.7
4.1

上記は、一年分のデータのまとめだったけど、季節の変化で血圧って変動するの? 24季節に分解してみる。

> map median $ group 15 hi;
[126,126,125,124,119,122,121,117,119,117,117,116,112,113,120,117,115,118,118,122,120,123,128,124]

グラフでも書いてみないと、目がちかちかしちゃうな。もう少し荒くてもいいか。

> map median $ group 30 hi;
[126.0,125.0,120.0,118.0,118.0,116.5,112.0,117.5,117.0,120.0,121.0,125.0]

うん、薬の飲んでいても、冬場は血圧高めなのね。よく分かるよ。

で、これだけじゃ、ちと味気ないので、相関とかに手を出してみる。最高血圧と最低血圧に 関係は有るのか、とかね。ぐぐったら、 高校数学C 統計学 なんてのが出てきた。 最近は高校でも統計学を教えるんですね。これって証券会社や日本競馬会がロビー活動して、 賭け事のイロハを若いうちに学んでおきましょって事だな。まあ、株のかけかたを研究して ノーベル賞を貰う御人が居るぐらいだから、世界的兆候なんでしょうな。

更にぐぐると、 平均と偏差、分散、相関の概念 なんてのが出てきて、このページの最後の式が簡単そうだったので、pureに落としてみる。

cor xs ys  = (sum $ map tmc $ zip xs ys) / (#xs - 1) with
               ax = avg xs ;
               ay = avg ys ;
               sx = sd xs ;
               sy = sd ys ;
               tmc z = ((z!0 - ax) / sx) * ((z!1 - ay) / sy) ;
             end ;

2つのデータ系列を同時に扱うので、zipでまとめると、データの組(タプル)のリストに なるんで、それをmapに掛けて、それぞれの項を計算。最後は畳み込んで計にまとめるとな。 統計式にシグマが出てきた場合の定石だな。

> clear __show__
> cor hi low;
0.537233078618474
> cor hi pls;
-0.0353964976740866

相関係数は、±1.0の範囲の数値になるんで、桁数表記をclearでデフォに戻した。それから 相関を取ってみた。最高血圧と最低血圧には正の相関有りって事だな。

pure-gplot

家でこうして遊んでいる分には、これで十分なのだけど、医者に結果を提出するには、数値を 持って行ってもしょうがない。ビジュアルじゃないとね。pureでグラフは書けるのか?

調べたら、gnuplotと連携出来るモジュールが提供されてた。パイプでgnuplotと接続して データを送り込むようだ。疎に結合してるっていいよね。unix的で。

昔おいらも、rubyのpopenを使って、gnuplotをコントロールし、pngファイルをCGIから 生成したっけなあ。その他、popenの先にgrepを接続して、よく使ったものだ。

使い方は簡単。

> using gplot;
> using namespace gplot;
> let gp = open "/usr/local/bin/gnuplot";
> plot gp hi ("style", "lines") ;
()
> output gp "png" "test.png" ;
gplot::output "png" "test.png"
> plotxy gp (hi, low) ("style", "lines") [] ;
> close gp;

いちいちコマンドを叩くって面倒。関数にしておくかな。

using gplot;
using system;  // for time

let  gp = gplot::open "/usr/local/bin/gnuplot";
lg xs      = gplot::plot gp xs ("style", "lines") ;

// line graph with title and save png file
ltg xs ttl   =  ~a && b && lg xs && c with
                  a =    gplot::title gp ttl;
                  s  =   str time + ".png";
                  b =    gplot::output gp "png" s;
                  c =    gplot::output gp "x11" "dmy";
                end;

最初の関数は、データの確認用。後の方は、ファイルに落とす用。一応タイトルを 記入しないと、後で分からなくなるので、表題付き。

outputの切り替えが旨く行っていないため、一度しかファイルに落とせない。そして、これを 使ってしまうと、確認用の方も表示しなくなる。もう少し格闘しないと、使えるものに ならないな。ファイル名は、エポックタイムにしてある。

速くなーれ

相関係数を求める関数を定義したんだけど、結果が出るまで23秒も待たされるんだ。 気の短いオイラーには耐えられないな。

> tm n = cor (1..n) (1..n);
> stats -m on

計算時間は計算サイズによって変わってくるはずなんで、簡単なベンチを書いて実行してみた。 (結果は見易いように、編集)

> tm 10;    0.001183s,  148 cells
> tm 20;    0.00897s,   288 cells
> tm 40;    0.059206s,  568 cells
> tm 80;    0.401135s, 1128 cells
> tm 160;   2.95929s,  2248 cells
> tm 320;  23.3961s,   4488 cells

使用セルは、データサイズに対して比例して増えている。実行時間は、データサイズが2倍に なると、8倍の割合で増えている。O記法で書くと、O(n^3) だな。corを再掲すると

cor xs ys  = (sum $ map tmc $ zip xs ys) / (#xs - 1) with
               ax = avg xs ;
               ay = avg ys ;
               sx = sd xs ;
               sy = sd ys ;
               tmc z = ((z!0 - ax) / sx) * ((z!1 - ay) / sy) ;
             end ;

withの中にあるのは、あくまで定義であって、評価はしていないんだな。withの前の 結果を計算してる所で、いちいち計算されてるのかな? 検証の為に、axからsyまで 事前に計算しておくか。そしてtmcの定義もtopに出しておくか。

> let ax = 120;
> let ay = 77;
> let sx = 7.7;
> let sy = 4.2;
> tmc z = ((z!0 - ax) / sx) * ((z!1 - ay) / sy) ;
> (sum $ map tmc $ zip hi low) / (#hi - 1);
0.542056399637085
0.007354s, 1804 cells

爆速になったぞ。思った通り、余計な計算をやってたんだ。どう書き換えればいいの? 例を見てたら、withとwhenを併用してた。おいらも真似下。

coR xs ys  = (sum $ map tmc $ zip xs ys) / (#xs - 1) with
               tmc z = ((z!0 - ax) / sx) * ((z!1 - ay) / sy) ;
             end when
               ax = avg xs ;
               ay = avg ys ;
               sx = sd xs ;
               sy = sd ys ;
             end ;

早速実行

> coR hi low ;
0.537233078618474
0.102592s, 1809 cells
> coR low pls;
0.0359148286634638
0.102687s, 1809 cells

withとwhenの使い所が分かったので、先にやったグラフをpngに落とす関数を修正。

ltG ttl xs   =  s when
                  a =    gplot::title gp ttl;
                  s =    str (time mod 86400) + ".png";
                  b =    gplot::output gp "png" s;
                  e =    lg xs ;
                  c =    gplot::output gp "x11" "";
                  d =    gplot::title gp "";
                end;

結局、when節に書いた式は、その場で評価されるって事だ。c式とd式は、gnuplotの状態を 元に戻す為に入れている。これで、上記の悩みは解消。思わぬ所で発見が有ったな。正月から 目出鯛いぞ。

ああ、使い方は

> lg $ map median $ group 15 hi ;
()
> ltG "hi 15 days priod" $ map median $ group 15 hi ;
"26589L.png"

lgで、グラフの状態を確認しておき OK だったら、前の式をコンソールに呼び出して、 ちょいとタイトルを加えて実行ですかね。結果はpngファイルに送られてしまうので、表示は しないけどね。

更なる前進

とまあ、pureでも一応Pythonの統計とグラフを表示するpylabっぽいのが出来る(Rのrepl環境っぽいかな) ようになった。後は山程ある統計関数をpureに移植してくだけですかね。

上で、平気に標準偏差を求めてみたけど、そもそもデータの集合が正規分布になってるの? そこら辺を確認しておかないといけないな。となると、次はヒストグラムの表示が欲しくなる。 どうやって、ランク分けしたらいいの?

ヒントはよそから貰ってくるのが一番。前にやった、 incanter あたりが良いですかね? それともnumpy.histogram っつう事で、すでに自分のパソコン内に有るジャン。pythonからpureに移植しれ。 その前にどんな機能が有るか ヒストグラムの書き方2・・・Python あたりで確認かな。

まてまて、schemeチックに書いてあるRでもいいか。はて、Rのコードは入っていたかな? それとも括弧まみれって事で、文字のヒストグラムを作ってみよう なんてのも楽しそう。

Hope

新年にあたり、今年1年、希望に満ちた年でありますようにってんで、 Hopeですよ。これ、haskellさんの親戚かな。

[sakae@pcbsd ~]$ hope
>: dec fact : num -> num;
>: --- fact 0 <= 1;
>: --- fact n <= n*fact(n-1);
>: fact 7;
>> 5040 : num
>: uses lists, range;
>: 1..7;
>> [1, 2, 3, 4, 5, 6, 7] : list num
>: product (1..7);
>> 5040 : num

入れて、すぐに使えます。変数にしろ関数にしろ、ちゃんと型宣言しないと、定義させて もらえません。ruby人やPython人(pure人も)のためのHaskell人、養成ギブスと申せましょう。

fedup

新年になったので、Fedra 19 をupgradeしてみた。 Fedra Homeへ行ったら、やりかたが書いてあった。

yum install fedup
fedup --network 20

19の環境で、上記を実行したら、1560個の20用rpmがDLされた。DLの一番最後がさっぱり 終わらないけど、じっくり待つ。そしてrebootしたら、実際にupgradeが適用されるのね。 何だか、毎月やってる某窓のあれみたいだな。これも永遠と待たされるけど、 お正月って事で、のんびり待ちましょう。 そんな訳で、無事に20になりました。

20になって、何か新しいのないかな。yum searchするのもいいんだけど、今回はyum list した結果をファイルに落とした。これって、バークレーDBか何かのdump結果になるのかな。 そして、そのカタログをgrepさ。こちらの方法の方がおいらに取っちゃストレス無いね。

[sakae@fedora ~]$ grep pure YUM-LIST
emacs-pure.noarch                        0.57-6.fc20                   installed
pure.i686                                0.57-6.fc20                   installed
pure-devel.i686                          0.57-6.fc20                   installed
pure-doc.noarch                          0.57-6.fc20                   installed
emacs-pure-el.noarch                     0.57-6.fc20                   fedora
ghc-pureMD5.i686                         2.1.2.1-2.fc20                updates
  :

探し求めていたfedora用のpureがキターーだったので、早速入れておいたよ。

debianはどうする

って事で、新目のものにしてみた。

# unstable
deb http://http.us.debian.org/debian/ unstable main contrib non-free
deb-src http://http.us.debian.org/debian/ unstable main contrib non-free

apt-get update, apt-get dist-upgrade, apt-get autoremoveした。これで、fedora並みに 先端を走ってくれるかな。