アプリの改修

JRが年寄りクラブを主催してるそうだ。

某行列で並んだ時、隣にいた爺さんと世間話をしてたんだ。そしたら、何がきっかけだったか 忘れたけど、15000円で4日間、電車乗り放題切符で旅行にしょっしゅう行っているって自慢 された。

そんな切符、確かにあったよな。えーっと、確か青春18切符とか言ったような。若者じゃ なくても利用出来るんですかって、少々失礼な質問をしちゃったぞ。

年会費3000円を払うと、割引切符が買えたり、色々な特典があるらしい。そのクラブへの 加入資格は、65歳以上。夫婦なら、どちらかが65歳を超していれば、夫婦でも加入出来るとか。

その切符を利用して、季節に合わせてあちこちを回っているとか。でも、その切符では 新幹線とか利用出来るの? そうでないと、行動範囲が狭くなっちゃうんじゃって、心配を ぶつけてみると、5回だか6回は新幹線や特急が利用出来るそうだ。

でも、宿代とか結構かかるんじゃって水を向けてみると、あらかじめ回る所を決め、息子に 安い宿を探させて取ってもらったとか。何度も行くうちに、定宿が出来そこに泊まるように なったとか。駅前のビジネスホテルがお勧めらしい。大体4000円代で朝食付きってのが相場 らしい。

いつもご夫婦で行かれるんですかって聞いたら、おとーさんは足が速くてスタスタ歩くので ついて行けないって理由で、一人旅らしい。

もし、オイラーの所なら、おとーさんはいびきに歯ぎしりに早寝早起きにはついて行けない から、お一人でどうぞって言われそう。その代わりに、あそこに行ったら、あれを土産に 買ってこいとか言われるんだろうな。

年寄りクラブじゃなくて、 大人の休日倶楽部って言うんか。年寄りは金持って そうなんで、搾り取ろうって寸法なんだな。

紙のサイズ

前回はpostscriptをやったりしたけど、これで描画する時の座標はデフォルトでは、72DPI。 1インチに72個の点を表示しますよっていう事だ。インチって、昔の王様の親指の長さを 元にしたものらしい。他にもフィートだとかヤードだとかあるね。これらも、みんな身体から 来てる。

そんな体の尺じゃ釈然としないって事で、地球を元にして長さを決めた。いわゆるメートル法。 これで、昔のインチを表すと、変な数字、2.54cm/インチになったりする。

で、紙のサイズも半端は数値なんだよな。印刷とかやろうとすると、紙のサイズを意識せざるを 得ない。また、何か突拍子もないものを基準に決めた? 調べてみる。

用紙サイズ A4・B4のなるほど!

長方形で、短辺と長辺の比が、1 : sqrt(2) という白銀比で、面積が1平方メートルのものを 基準にしましょう。後は、それを半分づつにしてく系列を考えたとな。基準サイズをA0、その 半分をA1、その半分をA2って具合に規格化。

ちょっと計算してみる。

(%i1) solve( sqrt(2)*x^2 - 1, x);
                                    1         1
(%o1)                       [x = - ----, x = ----]
                                    1/4       1/4
                                   2         2

欧米の大学に倣って、コンピュータに方程式を解いてもらいました。ちょいと恐ろしい形に なってますなあ。数値に直してみます。

(%i2) 1 / 2^0.25 ;
(%o2)                         0.8408964152537146
(%i3) % / 4 ;
(%o3)                         0.2102241038134287
(%i4) % * sqrt(2) ;
(%o4)                     0.2102241038134287 sqrt(2)
(%i5) float(%);
(%o5)                         0.2973017787506803

0.840(単位はm)ってのは、A0サイズの短辺の長さ。長編はこの1.41倍だから、ポスター並みの サイズだな。A0のサイズを4で割れば、我々がよく使うA4紙の短辺になる。

そんじゃ、欧米で使われる紙サイズは?

欧米サイズ『レターサイズ・リーガルサイズ』ってなに?

さすが富士ゼロックス。ちゃんと紹介があった。レターサイズって微妙にA4サイズと 異なっているのね。

久しぶりにアプリをupdate

今まで、postscriptがらみで、ちらちらとgnuplotの関連を見ていたら、結構オイラーの 知らない機能が有る事を知った。ならば、これらの機能を使って、医者に提出用の血圧グラフを かっこよくしたいと思った。

今使ってるのは、Windows7時代に作ったもの。それをそのままWindows10に持ってきて動かしている。一番改善したいのは、起床時と就寝時のグラフの2本建てになってる事。両面印刷して 持っていくんだけど、裏にもグラフが有る事に気が付かない看護婦さんが居るんだ。 その度に、裏にも有りますって言うのは、おばちゃん看護婦さんに文句を言われそうで怖い。 (ええ、オイラーは小心者ですから)

まずは、この点を改善したいな。それから、X軸は月日のいわゆる時間軸なんだけど、日時の 計算をgoに頼っていた。これも何とかgnuplot内で解決したい。それからgnuplotで統計データも取れるそうなので、活用してみたいぞ。

アプリを動かす舞台は、勿論Windows10。ここに入っているもので何とかしたい。goの開発環境は、Clear Linux に1.9が来てるんで、Windowsもそうなってるんだろうけど、今更入れる気が しない。はてどうしてくれよう。

改修計画

手軽に動かせるやつとして、NewLispが入っているんだ。で、こいつで書いた確認プログラムを みたら、もうチンプンカンプン。(オイラーが昔書いたんだけどね)

newlispを思い出すか。いや、オイラーの所でプチ流行のhaskellが良かろう。なんてったって 純粋ですからね。 Haskell の IO モナドと参照透過性の秘密

じゃ、HaskellをWindowsに入れるか? 今後の事を考えて入れておいてもいいんでなかろうか。但し、先端のghcじゃ大きすぎるんで、昔の仕様のhugsでよかろう。最先端の事をやる訳じゃないからね。 Downloading Hugsから、WinHugsを取ってきた。

インストールしようとしたら、途中でエラー。32Bit版だから入らないのかなあとか思って いたら、昔のやり方試してみるって言われて、やってみたら入った。

GUI版のWinHugsの他、CUI版のhugsとスクリプト実行用のrunhugsも入っていた。これはもう、 永らく愛用して下さいっていう思し召しだな。

-- pick am,pm data
pickN = 100 :: Int

csvRead :: FilePath -> IO [[Int]]
csvRead fn = do
  al <- readFile fn
  return $ map (\e -> read ("["++e++"]") :: [Int]) (lines al)

amP,pmP :: [Int] -> Bool
amP xs = head xs `mod` 100 < 12
pmP xs = head xs `mod` 100 >= 12

pick :: [[Int]] -> ([Int] -> Bool) -> [[Int]]
pick cs p = drop dn xs
            where
              xs = filter p cs
              dn = length xs - pickN

tof :: FilePath -> [[Int]] -> IO ()
tof fn cs = writeFile fn $ unlines $ map px cs
            where
              px x = show (head x)++" "++show (x!!1)++" "++show (x!!2)

main :: IO ()
main = do cs <- csvRead "current.csv"
          tof "am.dat" $ pick cs amP
          tof "pm.dat" $ pick cs pmP

いきなりHaskellのコードを書いちゃったけど、昔のコードから拾ってきたのは、csvReadだけ。他は書下ろし、って大した事やってないけどね。

今回も残念ながら、型の設計をしたわけではなく、いきなりのコード書きですよ。型宣言は ghciで :bro して、教えてもらった。amPとかは、Lispの述語の流れを汲んでいる。ここはもう、schemeぽく am?の方が良いかも。 writeFileで、書き出す文字列を作っておくってのは、どうも定石っぽいな。覚えておこう。

ghciで確かめながらコード書きしてて、ちゃんと動くようになったので、runhugsしたら、

$ runhugs pick.hs
runhugs: Error occurred
ERROR "pick.hs":15 - Type error in application
*** Expression     : length xs - pickN
*** Term           : length xs
*** Type           : Int
*** Does not match : Integer

こんな非互換性に見舞われた。hugs98で扱えるListの長さはIntに制限されてるのね。 と思って、GHCで確認したら、やっぱりIntだった。pickNの数値の解釈と言うか、追跡が hugsでは甘かったのね。

作ったスクリプトはhlintにかけて、改善点が無いようにしておいた。

全体像

Windows10に閉じたシステムで、処理したい。チラ裏にメモした血圧データは、goで書いた アプリを使って10日に一度ぐらいの割合で追加書き込みしてる。current.csvファイルは、 年に一度ぐらい、ガベコレしてる。

current.csvから、朝、夜のデータにより分けて、直近のデータを取り出すのは、上で 書いたHaskellスクリプト。本来ならこのスクリプト内でgnuplotを呼べばいいんだけど、 古いhugsは、そんな愛敬が無い。よってデータはファイル渡し。

gnuplotは、そのデータファイルを元にグラフを書く。よって、2行のバッチスクリプトを 書くはめになる。

注意は、hugsのパスが通っていない事。Windowsは伝統的にアプリをインストールしても パスを通してくれないので面倒作業を強いられる。

それから、gnuplotの出力グラフは、今までpngファイルだった。Windows10だと、写真アプリで 印刷出来た。でも、pngだと、縮小・拡大に弱い。折角作り変えるのだから、是非ベクター系に したい。

evinceでpsやeps、pdfを見てるんだけど、epsは印刷がサポートされていないみたい。今から やるなら、取り回しの楽なpdfが良いだろう。これなら、ブラウザーから見たり印刷出来るからね。

ps形式で出しておいて、それをgs付属のps2pdfで変換するのは、Windows10に閉じていないので、 オイラーの所では反則です。

gnuplot側

色々なサイトを見ていたら、 Gnuplot FAQのに出会った。犬も歩けば棒に当たる。

そして、注意点も見つけたぞ。

グラフを画像として保存する

注: ファイルへグラフ出力する場合,指示したファイル形式によっては(特に,PDFやPS形式),
再描画だけでは画像ファイルが正常に作成されない場合がある.
確実に画像ファイルが作成されるようにするには,画像ファイルへの出力を操作した後,一旦
,グラフが画面表示となるように戻しておいてから,もう一度,再描画操作を行えば,
大丈夫なようである.具体的には,次のような操作手順となる.

gnuplot> set terminal png (PNG形式ファイル出力へ切り替えを指示)
gnuplot> set output "sample.png" (sample.pngという名前のファイルへの出力を指示)
gnuplot> replot (グラフを再描画,ここではファイルへ出力される)
gnuplot> set terminal x11 (一旦,グラフ出力へ画面表示へ切り替える)
gnuplot> replot (念のため,画面上へグラフを再描画)

こういうのって、論理では片付けられないから、先人の鞭撻に感謝ですよ。

stats

gnuplotには、簡単な統計処理機構が組み込まれているとか。使い方は、下記のようになる。 元データは、こんなの

$ head -3 am.dat
17060604 136 76
17060704 134 76
17060803 121 78

2列目が最高血圧、3列目が最低血圧。これだけを取り出して統計処理

gnuplot> stats "am.dat" using 2:3

* FILE:
  Records:      100
  Out of range:   0
  Invalid:        0
  Blank:          0
  Data Blocks:    1

* COLUMNS:
  Mean:        131.8900             74.2200
  Std Dev:       6.0875              3.1546
  Sum:       13189.0000           7422.0000
  Sum Sq.:  1.74320e+06         551856.0000

  Minimum:     117.0000 [ 83]       67.0000 [ 45]
  Maximum:     144.0000 [ 72]       82.0000 [ 72]
  Quartile:    128.0000             72.0000
  Median:      131.0000             74.0000
  Quartile:    136.0000             77.0000

  Linear Model: y = 0.1904 x + 49.11
  Correlation:  r = 0.3673
  Sum xy:       9.796e+05

25%と75%の値まで出してくれている。更に、最高血圧から最低血圧を予測する式、相関係数 まで計算してくれている。今までgoとかで汗水垂らして実装してたのはなんだったんだ。 オイラーの時間を返してくれ。

で、これらの値を、グラフに反映するため、それぞれに変数が割り当てられている。 show variables ってやると、それぞれの変数名を確認出来る。

開発方法

これから、gnuplot用のスクリプトを書いていくけど、勿論開発環境はDebianとかOpenBSD上です。Windowsは、使うだけですからね。(上のHaskellの時もそうした)

コードをちょっと書いてそれを確認してってやるの、ちょいと辛い。そんなあなたをgvは 助けてくれます。gvを下記のように起動すると、ファイルの変化が有った時に、再描画して ます。

$ gv -watch zzz.pdf

コードはemacsで書きます。書いて実行の繰り返しです。画面を2つに割って(C-x 2)片方で eshellを走らせておきます。(M-x eshell)

/tmp $ gnuplot topdf.plt
/tmp $ !!

変更したらセーブして、eshell画面に移り、前のコマンドを!!で再実行。だって、コマンド ヒストリーを呼び出せないから、bashの機能を使うのさ。

$ file zzz.pdf
zzz.pdf: PDF document, version 1.5

出来上がるpdfファイルは、version 1.5 のやつ。新しい版だとJavascriptを許容したり して、ウィルスの巣になるんだうけど、これだと大丈夫だよね。

gnuplotスクリプト

# blood graph
set terminal pdfcairo mono font ",20" size 21cm, 29cm
set output "zzz.pdf"

# need stats first, can't support timedate mode
stats "am.dat" using 2:3
amh = sprintf(" Hi(Mean=%.1f Std=%.1f)", STATS_mean_x, STATS_stddev_x)
aml = sprintf(" Low(Mean=%.1f Std=%.1f)", STATS_mean_y, STATS_stddev_y)
stats "pm.dat" using 2:3
pmh = sprintf(" Hi(Mean=%.1f Std=%.1f)", STATS_mean_x, STATS_stddev_x)
pml = sprintf(" Low(Mean=%.1f Std=%.1f)", STATS_mean_y, STATS_stddev_y)

set grid
set yrange [50:160]
set ytics 10
unset key                   # no label on right top
set xdata time
set timefmt "%y%m%d%H"
set format x "%m/%d"        # m/d/y -> m/d

set multiplot layout 2,1

set title 'Morning  ' . amh . aml
plot "am.dat" using 1:2 with lines, "am.dat" using 1:3 with lines

set title 'Night  ' . pmh . pml
plot "pm.dat" using 1:2 with lines, "pm.dat" using 1:3 with lines

unset multiplot
set terminal dumb

実際のコードは上記なんだけど、ここに到達するまで、紆余曲折があったので、忘れない ように記しておく。

困った事、飛んでケー

その1として、下記にあるように、statsコマンドってのは、timedataモードがセット されていると拒否される。

gnuplot> load "topdf.plt"
         "topdf.plt", line 6: Stats command not available in timedata mode

しょうがないので、timedateモードに入る前に、統計データ処理をしちゃって、結果を (amh等に)保存しちゃう。それから、timedataモードに突入。よって、loadコマンドで 再度実行すると、怒られます。

その2として、

set label 3 at graph 0.1,0.9 pmh
set label 4 at graph 0.1,0.1 pml

のように、グラフの中にデータを埋め込もうとすると、何故か2つ目のグラフでは、数字が 重なってしまう。色々調べたけど、原因不明。逃げの手として、タイトル中に文字列結合 (演算子はperlっぽく、ドットだった)して、収めてしまった。

毒喰えば皿までで、グラフの表示エリアには一切、文字列を置かない事にした。いわゆる凡例ね。これって、keyって名前みたい。

その3として、グリッド問題。ただ指定しただけだと、Y軸(血圧値)のグリッドが20刻み でしかつかなかった。もう少し細かくってんで、ytics を追加した。X軸のグリッドは、 勝手に1週間おきに引かれた。こんな時間区切りをgnuplotは知ってるのね。よって、 それに合わせるべく、データ量を調整したよ。(pick.hsのpickN)

その4として、X軸は時間軸として月日を選んだ。(元のam.datも第1列目は、ymdH表現) その為の設定をtimefmtでする。普通は、09/30 みたいに月/日で表示されるんだけど、年を またぐと途端に、12/25/16 みたいに、月/日/年の形式に変わってしまう。

おばちゃん看護婦さんは、こういうのに付いて行けないはず(勝手にそう決めるなと、いつも 女房に注意される)なので、おばちゃんにやさしい表示に固定した。

ああ、それから、何もterminalで設定しないと、カラーになっちゃうので、あえて白黒 指定した。(プリンターのインクが無くなっても、黒印刷なら頑張ってくれるだろうから)

Windowsへ持って行ったら、busybox付属の unix2dos で、行末を変更し、最悪メモ帳でも 開けるようにしておこう。

ああ、それ以前にローカライゼーションが必要だな。おばちゃん看護婦にも分かるように、 日本語化しなくちゃ。標準偏差って統計用語分かるかなあ? なにそれ、なんで偏差値なんて 言葉がここに出てくんのよ。うちの息子、偏差値低いいから、ぼろ大学しか入れないのよとか、 愚痴られたらいやだな。心配してた日本語表示は、何の問題もなく出来たよ。

gnuplot bug ?

上でグラフの中にデータを表示すると、2番目のグラフで数字が重なってしまう件の 再確認。こんなスクリプトだった。

出来上がるpdfファイルは、邪悪なバイナリーだ。これじゃ調べようがない。これはもう、pdfの 祖先であるpostscriptで確認するしかないか。これで実験したら、現象が発現した。DNAは受け継がれていたのね。

set terminal postscript mono font ",14" size 21cm, 29cm
  :
set title 'Morning  '
set label 1 at graph 0.1,0.9 amh
plot "am.dat" using 1:2 with lines, "am.dat" using 1:3 with lines

set title 'Night  '
set label 3 at graph 0.1,0.9 pmh
plot "pm.dat" using 1:2 with lines, "pm.dat" using 1:3 with lines

出来上がったpsファイルの該当箇所。テキストファイルだからeditorで容易に確認出来る。 これぞunix流。そういえば、どこのぐぐるか知らんけど、httpをバイナリーでって叫んでるな。 W3Cも、秘密めいた事を許すし、世の中どんどん邪悪な方向へ走ってますよ。

まあ、httpをバイナリーにしたいって気持ちは分かりますよ。今回のpdfファイル、12kに対して、psファイルは26k有りましたからねえ。塵も積もれば山となるだな。 で、gzipで圧縮したら、5.4kになったから、pdfと言えども、中途半端だな。

3122 8009 M
(Morning  ) Cshow
1.000 UP
1061 7458 M
( Hi\(Mean=131.9 Std=6.4\)) Lshow
  :
3122 3900 M
(Night  ) Cshow
1.000 UP
1061 3349 M
( Hi\(Mean=131.9 Std=6.4\)) Lshow
1061 3349 M
( Hi\(Mean=119.1 Std=8.5\)) Lshow
1.000 UL
LTb

確かに別のデータが同じ場所に描かれている。で、ぱっとして気が付いた。最初のグラフに label 1 として書かれたデータが記憶されてて、2番目のグラフを書く時、label 1 と label 3を書いた。ユーザーの指示通りの事を、gnuplotはやったのだ。新犯人はオイラーだ。 疑ってすまん。

そうと分かれば、回避策は簡単。

set title 'Night  '
unset label 1
set label 3 at graph 0.1,0.9 pmh

2番目のグラフを書く時に、label 1 を無効にすればいいんだ。あるいは、label 1を再利用する。 何で、こんな単純な事に気が付かなかったんだろう? マルチプロットって、グラフ内の事は 独立したものと、勝手に合点、思い込みしてたんだな。女房の言う事は正しいな。これが 今日の教訓でした。

色々見てて面白い職人技を繰り出しておられる方がいたので、めもめも。

Gnuplot で複数のグラフを並べる方法