pythonでも

『私の方丈記』(河出文庫)を読んでみた。鴨長命が1212年に、3帖相当の庵で綴った有名な書。 秋には、こういうのもいいよね。

今年は災害が多いな。台風、集中豪雨、御嶽山の噴火。日本に住む限り逃れられない宿命。

著者の過酷な体験を方丈記に重ねて、エッセイになってた。うんうんと肯ける部分多し。 不思議なもので、オイラーは歳を取って枯れてきたというか欲が無くなってきたな。

年寄りを騙して金を巻き上げる詐欺。色々な手口があるけど、ロト6の必勝法を教えますとか、 儲かる投資を教えますとかに、ころっと騙されてしまう年寄りの多い事よ。そんなに 稼いで、どうするのって思っちゃうぞ。

三日月を眺めていて、ふと思った。地球のシルエットが月をスクリーンとして投影されてる んだよね。と言う事は、太陽、地球、月が同一平面上にあるって事。月の周回面が地球の 周回面と一致してるって、偶然な事? それとも、天体物理学上の必然?

こういうの、考えるのは楽しい事だな。

読書メーター 8冊 / 2164頁 / 11120円

python + emacs

以前、clojureとincanterでちっちゃなアプリを書いた。測定日時と血圧データをCSVファイルに 格納。それをグラフにして医者に提出するってやつ。血圧手帳の自家製版。

それはそれでいいんだけど、emacs なりを起動して、使えるようになるまで暫く待たされる。短気なおいらは血圧上がりますよ。

そこでだ、clojureをpythonに置き換えてやってみようと思う。それには、開発環境が必要だな。 環境と言えばemacsって相場が決まっている。ipythonが鉄板って事は知ってますけどね。

コードを書く時、せめて補完ぐらいはやって欲しいぞ。という事で、ac-comlateとac-pythonを 入れてみたんだけど、何故か動かんぞ。ネットをさ迷ったら、jediいいよって声が有ったので、 Pythonがサクサク書ける!emacsにjediをインストールする を参考に、まずは万次郎Linuxに入れてみた。 補完以外の設定は、emacs の python 開発環境を整える を参考にさせてもらった。

初回利用時に次のコマンドを実行しておかないと、文句を言われる。そして、補完のための データだかを作っているみたいなので、暫くしないと補完が有効にならないので注意。

    M-x jedi:install-server

CUIのターミナルから起動したemacsでは、補完の際に出て来るメニューの色使いが酷い 事になってた。多分調整出来るんだろうけど。

主目的は、Windows7側のemacs24で動かす事。心配は設定途中で、コマンドを叩かなければ ならない事なんだ。そしてpipとかでモジュールを取り寄せる事。pipなんてのは入っていないんよ。

でも軽く調べてみたら、 Python for Windows インストールメモ に、先駆者がいましたよ。無事にpipも入って、次はコマンドを叩くんだけど、source env/bin/activate が失敗した。envなんてのが、そもそも無かったし。で、そこは飛ばして先を急いだよ。

で、設定は次のようになった。

;; python
(require 'epc)
(require 'auto-complete-config)
(require 'python)
(setenv "PYTHONPATH" "C:\\Python27\\Lib\\site-packages")
(require 'jedi)
(add-hook 'python-mode-hook 'jedi:setup)
(setq jedi:complete-on-dot t)

(add-hook 'python-mode-hook
          (lambda ()
            (define-key python-mode-map "\"" 'electric-pair)
            (define-key python-mode-map "\'" 'electric-pair)
            (define-key python-mode-map "(" 'electric-pair)
            (define-key python-mode-map "[" 'electric-pair)
            (define-key python-mode-map "{" 'electric-pair)))
(defun electric-pair ()
  "Insert character pair without sournding spaces"
  (interactive)
  (let (parens-require-spaces)
    (insert-pair)))

(add-hook 'python-mode-hook '(lambda ()
     (define-key python-mode-map "\C-m" 'newline-and-indent)))

(setq-default indent-tabs-mode nil)
(setq-default tab-width 4)

これで、どうやら動いているみたい。コードを書いたら C-c C-c で、pythonに送って 評価してもらう。あるいは、C-c C-lでファイル毎評価。こちらは、main()が動いてくれるので、 気分は、ターミナルからスクリプトをそのまま起動したと同じ事で、非常に便利。 pythonの画面は、C-c C-z で出てくるのは、お約束事項かな。

便利と言えば、ネットにはいろいろな資料が公開されてて調べ物には事かかない。 Python Snippetsなんてのをちらちら見てたら、 pythonのprintって、いわゆるprintlnなのね。改行を抑止するには、最後にカンマをおけば いいよ、なんてオラーに取ってはトリビアな事が開陳されてた。

clojure語をpython語にする

python語にするには、CSVが使える事が必要。pythonバッテリーには、当然CSVモジュールが 付いてた。2011年に健康増進とかいってコードを書いた時は、何をトチ狂ったかピクルス なんていう方言モジュールを使っちゃったからなあ。データの可搬性が全くなくて、苦労 したよ。

CSVモジュールでは、CSVファイルを一気読みしてくれる関数も定義されてたけど、それだと どうも、ヘッダーの処理がうまくいかない。しょうがないので、forで回れ回れさせたよ。

で、何とかCSVファイルの読み込み、メモリー上の更新、ファイルへの書き出しが出来る ようになった。

ああ、これと合わせて、メモリー上のデータの確認に目がくらくらしない様に補助関数と 言うか、清書表示出来るようにしてみた。名前はpp。これを組み立てる為のモジュールも バッテリーの中に入っていた。こういうのを探す鼻と言うか目利き力が必要だな。

次は、フィルター群なんだけど、これ、map系のfilterを最初使おうとしたんだ。でも、 pythonの匿名関数定義 lamdaが貧弱なんで、使うのを諦めた。Lispからやってくる人は、 この点は攻撃の対象になりますな。素直に、内包表記を使っとけとな。

python(とrubyもだけど)で、嬉しいのは、配列のスライスが、すらすら書ける事。 この点は、clojureもまねして欲しいぞ。

データを2次元配列からデータを拾い出してくるのに、無骨なインデッスク番号を 直接書きたくない。こういう用途には確か、enumなんてのが有ったなと思い出した。 果たして、pythonに有るかと思ったら、 Python 3.4 から標準ライブラリに入る Enum 型が今からでも便利 と、python2の人に餌をぶら下げていましたよ。

3は便利だから、早く移っておいで! 言葉の壁もちゃんと解消して、ほれ日本語文書を 用意してるよと。これが、その証拠だぞ。 列挙型のサポート

eval使うんか

出来たコードはスクリプトとして実行するんだけど、色々な解析用関数を自在に呼び出したい。 そうすると、自前のreplが欲しくなる。pythonでエバっていいですか?

def repl():
    cl = raw_input('repl> ')
    print(eval(cl))
    repl()

てへぇ、書いちゃった。間違った入力でpythonが怒り狂って落ちちゃう版ですけど、自家使用だから、 そこは事故責任で。

困ったのは、clojureに有った、パイプ演算器。どんなコードになってるかソースを 引っ張り出してきて眺めてみました。

(defmacro ->>
  "Threads the expr through the forms. Inserts x as the
  last item in the first form, making a list of it if it is not a
  list already. If there are more forms, inserts the first form as the
  last item in second form, etc."
  {:added "1.1"}
  ([x form] (if (seq? form)
              (with-meta `(~(first form) ~@(next form)  ~x) (meta form))
              (list form x)))
  ([x form & more] `(->> (->> ~x ~form) ~@more)))

得意のマクロですよ。どんな風に動くか、少々実験してみました。

(->> (am BLD) (from 1305))
;===> (from 1305 (am BLD))
(->> (am BLD) (from 1305) (tl 3))
;===> (->> (->> (am BLD) (from 1305)) (tl 3))
;===> (tl 3 (from 1305 (am BLD)))               ; C-c M-m で全展開

Pythonにマクロなんて、勿論無いさ。またしてevalの出番だな。例を見ると、直前の 関数を取り込んで、次の関数の引数の中に埋め込んでいる。これをどんどん繰り返して、 ソース(字面)上で、関数を組み立て、最後にそれをevalすればいいんだな。

素直にclojureの真似をすれば、そうなるけど、今回はMSDOSの隠れパイプ風にやってみる。

def m(s):
    val = BLD
    for cmd in re.split('\s+', s):
        if re.search('\)', cmd):
            do_cmd = cmd.replace(')', ',val)')
        else:
            do_cmd = cmd + '(val)'
        val = eval(do_cmd)

前の結果をvalに入れておいて、それを次の関数の引数にする方法。フィルターの関数は、 最後の引数が配列で、それをフィルターした結果が配列で返るようにAPIを統一しておいたから、 こういう単純な、書き換え、実行で事が済んじゃう。

なお、フィルター関数に引数が無い場合は、関数呼び出し時に括弧を省略する約束にしといた。 これで、解析(って程の事もないけど)が楽になる。

このマクロもどき関数は、空白区切りの関数列を受け取るんだけど、実使用の時にわざわざ m(' ....') とか、書きたくない。きっと間違えて、pythonが死んじゃう事になる。

よって、マクロ文字列をそのまま受け付けるようなreplが欲しい。10秒考えて、先のreplを 改造する事にした。

def Mrepl():
    cl = raw_input('Mrepl> ')
    if len(re.split('\s+', cl)) < 2:
        print(eval(cl))
    else:
        m(cl)
    Mrepl()

何の事はない。入力された文字列を、空白を頼りに一度分解し、2つ以上に分解出来たら、 そりゃマクロだぜぇってやってるだけ。

グラフ内に文字表示

今回は、グラフの中に要約データを表示しようと思う。昔書いたプログラムは、その機能を 入れて無かったので、手書きしてたんだ。えと、グラフ中に文字列を表示させるには、text ってまんまの手続きが使えるらしい。

引数は、グラフ上のX座標、Y座標、それに表示したい文字列。実際にやってみると、表示に 使われるフォントが等幅じゃないものだから、表示が狂っちゃう。あてずっぽで、fontname なんてのを指定したら旨くいった。WindowsでもLinuxでも使える共通フォントって何が いいんでしょうかね? 今回はmonospaceって、まんまの名前のやつを指定したけど、ちと 間延びしちゃった。

    text(5, 90, 'mean:' + ssf(mean,hlp), fontname='monospace')
    text(5, 87, 'sd:  ' + ssf(std, hlp), fontname='monospace')

Y座標は、折れ線がこないあたりを指定しといた。低い方の血圧が上昇してきたら、本当に ヤバイ事になりそうだよ。80以下が良いらしい。

使い方と言う備忘録

記憶の助けになるmyhelp()ってのを入れておいたけど。

am、pmは、起床時、就寝時のデータを抽出。

hd(n) tl(n) は、流れてきたデータの先頭、若しは尻尾のn個のデータを抽出。

frm(ym) utl(ym) で、ymは年月の4桁の数字。frmはそれ以降、utlはその年月(を含む)までの データを抽出。HAMな人なら、HighPass、LowPassフィルターね。組み合わせると、バンドパスにも、 バンドレジェクトにもなるよ。

ire(ym) は、測定データの追加。clojure時には、inputって関数だったけど、pythonでは 名前がかぶりそうだったので入れ(居れ)の語呂合わせ。本当は、昔懐かしく、キーパンチャーって 名前にでもと最初思ったけど、年代がバレそうなので止めた。ああ、入力の終了は fin って フランス語を採用。exitかquitとか何時も悩むのがイヤだから! finすると、すかざず、ファイルに 自動セーブされる。

後は、ssは要約表示、lgaとlgpは、医者提出用起床時、就寝時グラフ作成。マクロを 使って定義してある。

lineg(ttl) は、パイプから流れてきたデータを折れ線グラフ表示。ttlはグラフの タイトル。プログラムの関係で、空白の使用はご法度。

以下、ちょっとした実行例

C:\Users\sakae\Documents\newbld>python newbld.py
filter:  am pm hd(n) tl(n) frm(ym) utl(ym)
control: myload mysave ire(ym) m(cmds) myhelp
stats:   pp ss lineg(ttl) lga lgp
OK load size = 722
Mrepl> ire(1401)
1401> 921 118 66 58
1401> 1004 122 71 55
1401> fin
OK save size = 724
None
Mrepl> tl(4) pp
[   [14010105, 120, 64, 56],
    [14010121, 124, 70, 60],
    [14010921, 118, 66, 58],
    [14011004, 122, 71, 55]]
Mrepl> tl(4) ss
size: 4
min:     118.0  64.0  55.0
median:  121.0  68.0  57.0
max:     124.0  71.0  60.0
mean:    121.0  67.8  57.2
std:       2.2   2.9   1.9

(20)14年1月のデータを追加。そして、最後の4データを確認。それから、統計データの計算。 スクリプトの終了方法は、Ctl-Z等のEOFを送り込むか、空行を喰わせて、pythonを怒らせる事です。 (をぃ、凄い方法だな)

code

# -*- coding: utf-8 -*-
# blood check CSV version

import csv
import re
import pprint
import numpy as np
from pylab import *

csvfile = 'current.csv'
BLD = []
hi = 1 ; low = 2 ; pls = 3    # for index no.

def pp(obj):
    sty = pprint.PrettyPrinter(indent=4, width=50)
    sty.pprint(obj)
    
def myload(cf=csvfile):
    with open(cf, 'r') as f:
        reader = csv.reader(f)
        header = next(reader)  # header skip
        for row in reader:
            BLD.append([int(x) for x in row])
        print(('OK load size = ' + str(len(BLD))))

def mysave(cf=csvfile):
    with open(cf, 'w') as f:
        writer = csv.writer(f)
        writer.writerow(['ymdh','hi','low','pls'])
        for x in BLD:
            writer.writerow(x)
        print(('OK save size = ' + str(len(BLD))))

def add_data(yymm, dat):
    nd = [int(x) for x in dat]
    nd[0] = yymm * 10000 + nd[0]
    BLD.append(nd)

def ire(yymm):
    cl = raw_input(str(yymm) + '> ').strip()
    if re.match('fin', cl):
        mysave()
        return
    dat = re.split('\D+', cl)
    if len(dat) == 4:
        add_data(yymm, dat)
    else:
        print('Bad')
    ire(yymm)

def hd(n=30, ds=BLD):
    return ds[:n]

def tl(n=30, ds=BLD):
    return ds[-n:]

def am(ds=BLD):
    return [x for x in ds if (x[0] % 100) < 12]

def pm(ds=BLD):
    return [x for x in ds if (x[0] % 100) >= 12]

def frm(start=0, ds=BLD):
    v = start * 10000
    return [x for x in ds if x[0] >= v]

def utl(stop=9912, ds=BLD):
    v = stop * 10000 + 3124
    return [x for x in ds if x[0] < v]

def w(idx=hi, ds=BLD):
    return [x[idx] for x in ds]

def mk_hlp(ds):
    return [w(hi,ds), w(low,ds), w(pls,ds)]
    
def ssf(fn, hlp):
    return ''.join(['{0:6.1f}'.format(fn(x)) for x in hlp])
    
def ss(ds=BLD):
    hlp = mk_hlp(ds)
    print('size: ' + str(len(ds)))
    print('min:    ' + ssf(min, hlp))
    print('median: ' + ssf(median, hlp))
    print('max:    ' + ssf(max, hlp))
    print('mean:   ' + ssf(mean, hlp))
    print('std:    ' + ssf(std, hlp))

def lineg(ttl='BloodData', ds=BLD):
    plot(w(hi,ds))
    plot(w(low,ds))
    plot(w(pls,ds))
    title(ttl)
    hlp = mk_hlp(ds)
    text(5, 90, 'mean:' + ssf(mean,hlp), fontname='monospace')
    text(5, 87, 'sd:  ' + ssf(std, hlp), fontname='monospace')
    grid(True)
    show()

def m(s):
    val = BLD
    for cmd in re.split('\s+', s):
        if re.search('\)', cmd):
            do_cmd = cmd.replace(')', ',val)')
        else:
            do_cmd = cmd + '(val)'
        val = eval(do_cmd)
    
def repl():
    cl = raw_input('repl> ')
    print(eval(cl))
    repl()

def Mrepl():
    cl = raw_input('Mrepl> ')
    if len(re.split('\s+', cl)) < 2:
        print(eval(cl))
    else:
        m(cl)
    Mrepl()

def lga():
    m("am tl(50) lineg('at_Wakeup')")

def lgp():
    m("pm tl(50) lineg('at_Night')")
    
def myhelp():
    print('filter:  am pm hd(n) tl(n) frm(ym) utl(ym)')
    print('control: myload mysave ire(ym) m(cmds) myhelp')
    print('stats:   pp ss lineg(ttl) lga lgp')
    
def main():
    myhelp()
    myload()
    Mrepl()
    
if __name__ == '__main__':
    main()

python 3では

出来たコードを万次郎Linuxに持って行ったら、エラーで落ちる。3秒考えて、Python3.4.1 だったと思い出した。えと、移行ツールが有ったな。

[sakae@manjaro newbld]$ 2to3-3.4 -w newbld.py
   :
 def repl():
-    cl = raw_input('repl> ')
-    print(eval(cl))
+    cl = input('repl> ')
+    print((eval(cl)))
     repl()
   :
RefactoringTool: Files that were modified:
RefactoringTool: newbld.py

新しい3用のコードが出来て、オリジナルはバックアップされた。で、新しいのでも 普通に動いた。(当たり前)早くPython3系にしろとな。

要改造

と、ここまで書いて、大事な機能を実装し忘れていた。たとえば、去年と今年で経年変化が 有るか、どうやって調べる? 血圧って年と供に上昇してくものだろう。だったらそれを 可視化しちゃれ。(恐い話ではありますが)

そうすると、去年のデータと今年のデータを同時にフィルターにかけて抽出って事に なるな。残念ながら、今のはパイプが一本しかないから、こういう欲求は満たせない。 CPUの高速化に複数本のパイプラインが大活躍してんのに! (オイラーはintelちゃうで)

一本しかないパイプで並列化なんて出来ないから、時分割で使えばいいとな。パイプに 流した結果を何処かに一時的に保持。次にパイプに新しいデータを流して、パイプの 終端で、流れてきたデータと、保持してあったデータを演算すればいいな。

保持機能を持った関数は、そーさな、月並みだけど、keepとかでいいか。保持したのを 開放するfreeとかも用意しとくと、Pythonが勝手にGCしてくれるだろう。メモリーに 優しい仕様が望まれますな。

老齢化確認関数は、どんな機能が要る? ここら辺は、プロの医者にでもご相談かな。

嗚呼、今ふと気が付いたんだけど、データの元が、myloadとかmの中で決め打ちになってるなあ。 これじゃ、複数ファイルの同時取り扱いができんわ。これらも要改造だな。

追記

keepするやつ、書いてみた。グローバル変数 KP に結果が残る。あらかじめ宣言しとく事。

def kp(ds=BLD):
    if len(KP) > 0:
        for x in range(len(KP)):
            KP.pop()
    for x in ds:
        KP.append(x)

この関数はパイプの最後に置く事。変数の配列を使い回すので、使用に先だって、配列を クリアしてる。よって、特にfreeとかは、用意する必要無いな。