nprolog, xml
ちょいとATMで振り込みしようとした。初画面に、振り込み詐欺注意の大きな図が出て来た。 次をめくると、カードか振り込み通帳を利用してください。現在、現金での振り込みは扱っておりませんですって。前は現金OKだったのに。。カードを持って出直しですよ。
年寄りのタンス貯金を使えないようにしてるんだな。振り込み詐欺の水際防止作戦の一環だろうね。まあ、これが公式見解。本当は銀行屋も現金を扱いたくない。だから真綿で首を絞める誘導作戦。新規口座開設で通帳を作ると金を取るご時世ですから。
それから数日前のTVでやってたんだけど、都会ではATMが続々と撤去されてるらしい。その一等地は争奪戦がすさまじいとか。跡地は、プリン屋さんとか、サンドイッチ屋さんが入居してるとか。ATMが無くなるって事は、スマホで決済しろって事か。
nprolog
とある方を経由して、 nprolog なんてのを知った。nprologのnは、ノスタルジアの意味を込めてあるそうだ。オイラーにもぴったりだな。この分野だとswi-prologとかgprologと言う巨大な奴が居るけど、それじゃ大変だからね。早速DLして、OpenBSD 6.8(i386)に入れてみる。
vbox$ make gcc -Wall -O3 -c main.c main.c:8:10: fatal error: stdio_ext.h: No such file or directory #include <stdio_ext.h> ^~~~~~~~~~~~~ compilation terminated. *** Error 1 in /tmp/nprolog (<sys.mk>:87 'main.o')
そんなヘッダー無いとな。無い袖は振れないので、コメントアウトして 続けてみると、
gcc -Wall -O3 -c main.c In file included from main.c:9: main.c: In function 'debugger': npl.h:196:29: warning: implicit declaration of function '__fpurge'; did you mean 'fpurge'? [-Wimplicit-function-declaration] #define FLUSH __fpurge(stdin); ^~~~~~~~ main.c:566:5: note: in expansion of macro 'FLUSH' FLUSH ^~~~~ gcc -Wall -O3 -c parser.c gcc -Wall -O3 -c function.c gcc -Wall -O3 -c builtin.c builtin.c:1:10: fatal error: stdio_ext.h: No such file or directory #include <stdio_ext.h> ^~~~~~~~~~~~~ compilation terminated. *** Error 1 in /tmp/nprolog (<sys.mk>:87 'builtin.o')
他にも無理強いしてる所はerror.cだったんで、コメントアウト。
gcc main.o parser.o function.o builtin.o extension.o link.o data.o gbc.o cell.o error.o bignum.o compute.o edit.o -o npl -lm -ldl ld: error: unable to find library -ldl collect2: error: ld returned 1 exit status *** Error 1 in /tmp/nprolog (makefile:27 'npl')
今度は -ldlで失敗。ggしてみると、リナのソースをOpenBSDに移植する人が多数悩んでいた。結論を書くと、ダイナミックロードなんて多用される技法だから、OpenBSDではlibcに吸収しちゃったよ。未だに別になってるのはリナだけだよとの事。よってmakefileから-ldlを削除。
gcc main.o parser.o function.o builtin.o extension.o link.o data.o gbc.o cell.o error.o bignum.o compute.o edit.o -o npl -lm ld: error: undefined symbol: __fpurge >>> referenced by main.c >>> main.o:(debugger.part.5) :
上で見逃していたエラーが復活してきた。 __fpurge
なんてのはOpenBSDには無いとな。ならばリナではどうなってる? 尼寺へ行けじゃなくて、manを引け。
SYNOPSIS /* unsupported */ #include <stdio.h> int fpurge(FILE *stream); /* supported */ #include <stdio.h> #include <stdio_ext.h> void __fpurge(FILE *stream); CONFORMING TO These functions are nonstandard and not portable. The function fpurge() was introduced in 4.4BSD and is not available under Linux. The function __fpurge() was introduced in Solaris, and is present in glibc 2.1.95 and later.
正統派なやつはfpurgeなのね。するってえとリナは、もどきなんだな。そんな事でnpl.hを修正。make cleanしてから再コンパイルしたら実行ファイルが無事に作成された。
vbox$ ./npl ksh: ./npl: Cannot allocate memory vbox$ ulimit -a : data(kbytes) 524288 : vbox$ ulimit -d 1524288 vbox$ ./npl N-Prolog Ver 1.2 ?- X is 2^100. X = 1267650600228229401496703205376 . yes ?- halt. - good bye -
メモリー不足で起動せず。OpenBSDの32Bit版では、ヒープエリアが控え目に設定されてるんで増やしてあげた。そしたら、動き始めた。
makefileではgccが使われいるけど、OpenBSDのデフォのコンパイラーはccだ。llvm由来のccに切り替えて再コンパイル。
bignum.c:1017:13: warning: absolute value function 'abs' given an argument of type 'long long' but has parameter of type 'int' which may cause truncation of value [-Wabsolute-value] j = abs(j); ^ bignum.c:1017:13: note: use function 'llabs' instead
こんな有り難い忠告を受けたよ。冷酒と親の小言は後で効く、かな?
後は探検の為に、etags *.[ch] だな。まて、お前prologの事良く知らないだろう。何か適当な資料は無いか? 探したら『7つの言語 7つの世界』とか『On Lisp』に資料が見つかった。 久しぶりに紐解いてみるかな。
ちょいとソースをブラウズしてたら
#elif __OpenBSD__ printf("\a"); #endif
こんなのに出くわしたけど、OpenBSDでの協力者が居るのかな? それとも作者さん自身が密かなOpenBSD信奉者なのかな。
xml
通奏低音のようにRをそしてe-Statに憑りついている。それじゃお化けだろう。
R上のxml editor?
> library(XML) > doc = xmlInternalTreeParse("po.xml") > getNodeSet(doc, '/GET_STATS_DATA/STATISTICAL_DATA/CLASS_INF') [[1]] <CLASS_INF> <CLASS_OBJ id="cat01" name="認知・検挙件数・検挙人員"> <CLASS code="100" name="認知件数" level="1" unit="件"/> <CLASS code="110" name="検挙件数" level="1" unit="件"/> <CLASS code="120" name="検挙率" level="1" unit="%"/> <CLASS code="130" name="検挙人員" level="1" unit="人"/> <CLASS code="150" name="検挙人員_うち少年" level="2" unit="人" parentCode="130"/> </CLASS_OBJ> <CLASS_OBJ id="cat02" name="罪種"> <CLASS code="100" name="刑法犯総数" level="1"/> <CLASS code="110" name="凶悪犯" level="2" parentCode="100"/> : <CLASS code="2007000000" name="2007年" level="1"/> <CLASS code="2006000000" name="2006年" level="1"/> </CLASS_OBJ> </CLASS_INF> attr(,"class") [1] "XMLNodeSet"
汎用的 down loader for xml
e-StatをRから使うってのでggしてると、皆さんjsonがお好きなようです。jsonは軽くていいんだけど、どうもR内で使うには便利だけどR外では、不便。
この際XML主体で行きたい。なんたってemacsで簡単に閲覧できるからね。いざとなったら直接データを切り貼り(それってデータのねつ造じゃん)出来るからね。
と言う事で、shellからダウンロード出来るように汎用化しとく。
#! /bin/sh # Usage: fromes statsDataId ;;ex. fromes 0003191320 MY=40byte-your-appID curl -o $1.xml "https://api.e-stat.go.jp/rest/2.1/app/getStatsData?appId=$MY&lang=J&statsDataId=$1&metaGetFlg=Y&cntGetFlg=N§ionHeaderFlg=1"
ダウンロードしたい表のidを指定するだけ。結果は、表id.xmlってファイルになる。これで、APIボタンをクリックして、statsDataIdを抜いて、上記スクリプトに喰わせるだけだ。バンバンDLしよう。
表のidを指定しないと、哀れな事に、.xml って言う普段は見えないエラーだよファイルが作成されちゃう。本来なら何とかしないといけないんだけど、個人使用って事で、引数チェックをさぼってます。使う人は注意めされ。
鉄板な処理
奥村先生のe-statページに、素晴らしい鉄板処理例が載ってた。
library(XML) foo = function(x) { n = getNodeSet(doc, paste0("//CLASS_OBJ[@id='", x, "']/CLASS")) tbl = sapply(n, xmlGetAttr, "name") names(tbl) = sapply(n, xmlGetAttr, "code") tbl[sapply(items, xmlGetAttr, x)] } doc = xmlParse("0003033021.xml") items = getNodeSet(doc, "//VALUE") cat01 = foo("cat01") cat02 = foo("cat02") cat03 = foo("cat03") area = foo("area") time = foo("time") value = as.numeric(sapply(items, xmlValue)) df = data.frame(cat01, cat02, cat03, area, time, value)
指定した列データを取り出して来て、フレームを組み立てる。味噌はfoo関数だな。
> df1 = subset(df, cat01=="男" & cat03=="日本人" & area=="全国") > df2 = subset(df, cat01=="女" & cat03=="日本人" & area=="全国") > df1$cat02 [1] "総数(年齢)" "0歳" "1歳" : [100] "98歳" "99歳" "100歳以上" > plot(0:100, df2$value[2:102], type="o", xlab="Age", ylab="Population") > points(0:100, df1$value[2:102], pch=16, type="o")
得られたフレームを元に男のフレームと女のフレームをsubsetで作り出す。年齢は総数から始まって、最後の方に余分なやつが付いている。 そこで、値の範囲を 2:102 として、0歳から100歳以上のデータを折れ線グラフで書く。
> head(df) cat01 cat02 cat03 area time value 1 総数(男女別) 総数(年齢) 総数(国籍) 全国 2010年 128056000 2 総数(男女別) 総数(年齢) 総数(国籍) 全国市部 2010年 116184500 3 総数(男女別) 総数(年齢) 総数(国籍) 全国郡部 2010年 11871500 4 総数(男女別) 総数(年齢) 日本人 全国 2010年 125691800 5 総数(男女別) 総数(年齢) 日本人 全国市部 2010年 113902100 6 総数(男女別) 総数(年齢) 日本人 全国郡部 2010年 11789700 > head(df1) cat01 cat02 cat03 area time value 664 男 総数(年齢) 日本人 全国 2010年 61339900 670 男 0歳 日本人 全国 2010年 544800 676 男 1歳 日本人 全国 2010年 542200 682 男 2歳 日本人 全国 2010年 551800 688 男 3歳 日本人 全国 2010年 546800 694 男 4歳 日本人 全国 2010年 541200 > tail(df2, n=10) cat01 cat02 cat03 area time value 1924 女 99歳 日本人 全国 2010年 2.210000e+04 1930 女 100歳以上 日本人 全国 2010年 3.980000e+04 1936 女 不詳 日本人 全国 2010年 2.135000e+05 1942 女 (再掲)15歳未満 日本人 全国 2010年 8.096200e+06 :
これが鉄板な処理プログラムなるな。グラフ描きもこれで自由自在だ。
この例の素晴らしいのは、使うライブラリーが一つだけって事。しかもそのライブラリーの根幹は、emacsでも使っているlibxmlってやつ。信頼おけるし、扱いが楽だ。 ともすればRでも、ごまんととライブラリーを使ってるのを見る事がある。そんなに沢山のライブラリィーの使い方を覚えるのは、年寄りに苦痛です。
そういう理由もありpythonで自由奔放にimportしてるのを見ると、ゾッとしますね。地獄が待っている気がするぞ。
RからのXMLをちゃんとやるなら、 An XML package for the S language を見ておけかな? 表題にはSって書いてあるけど、R == S と思うぞ。昔unix上の統計ソフトと言ったら有料のSしかなかった(その後、S pulsとかの派生が出たけど、相変わらず有料)。貧乏大学は、おいそれと導入出来なかった。そこで発奮して、無料かつソース公開版を作った。それがRだ。
最初は論文とか学会ではRを無視(と言うかいじめられていた。Rを使った論文はそれだけで受理されなかったらしい)されていた。まあ、何処の世界も一緒だわな。白人優越主義。黄色とか黒は下等とみなしていた、、、今もそうかな。
Rはどんどん発展。そしてやっと市民権を得た。先人の偉大な努力で天国に入る事が出来たんですよ。心して使えよ。>オイラー。
step 実行
上で出て来た鉄板な処理で中核は関数 foo(x) だ。その入出力が分かっているので、そんなものって、打っちゃってもいいんだけど、それじゃ奥村先生に失礼だ。よって、この関数をステップ実行して、やってる事を理解したい。そんなのトレースでいいじゃん。待て、emacs上からRしてるんで、簡単に切り貼り実行出来るから、今回はその方向でやってみる。
供試データは、犯罪統計のうちの下記の部分。
<CLASS_OBJ id="time" name="時間軸(年次)"> <CLASS code="2016000000" name="2016年" level="1"/> <CLASS code="2015000000" name="2015年" level="1"/> : <CLASS code="2006000000" name="2006年" level="1"/> </CLASS_OBJ>
これを肴にするんで、x = 'time' としておく。
> paste0("//CLASS_OBJ[@id='", x, "']/CLASS") [1] "//CLASS_OBJ[@id='time']/CLASS"
こんな検索用の文字列が生成される。
> n = getNodeSet(doc, paste0("//CLASS_OBJ[@id='", x, "']/CLASS")) > n [[1]] <CLASS code="2016000000" name="2016年" level="1"/> [[2]] <CLASS code="2015000000" name="2015年" level="1"/> : [[11]] <CLASS code="2006000000" name="2006年" level="1"/> attr(,"class") [1] "XMLNodeSet"
クラスオブジェクトのid=timeを検出して、その内容を得るんだな。
> tbl = sapply(n, xmlGetAttr, "name") > tbl [1] "2016年" "2015年" "2014年" "2013年" "2012年" "2011年" "2010年" "2009年" [9] "2008年" "2007年" "2006年"
次は、そこからnameのキーが付いたものを拾いだしてる。
> names(tbl) = sapply(n, xmlGetAttr, "code") > names(tbl) [1] "2016000000" "2015000000" "2014000000" "2013000000" "2012000000" [6] "2011000000" "2010000000" "2009000000" "2008000000" "2007000000" [11] "2006000000"
それのコードも拾い出してる。
!> tbl[sapply(items, xmlGetAttr, x)] 2016000000 2015000000 2014000000 2013000000 2012000000 2011000000 2010000000 "2016年" "2015年" "2014年" "2013年" "2012年" "2011年" "2010年" : 2012000000 2011000000 2010000000 2009000000 2008000000 2007000000 2006000000 "2012年" "2011年" "2010年" "2009年" "2008年" "2007年" "2006年"
これが、関数fooからの返値になる。対象は、グローバル変数items中の、timeだ。
> rv = tbl[sapply(items, xmlGetAttr, x)] > length(rv) [1] 2310 > length(items) [1] 2310 !> head(items, n=3) [[1]] <VALUE cat01="100" cat02="100" time="2016000000" unit="件">996120</VALUE> [[2]] <VALUE cat01="100" cat02="100" time="2015000000" unit="件">1098969</VALUE> [[3]] <VALUE cat01="100" cat02="100" time="2014000000" unit="件">1212163</VALUE>
返値を変数にバインドして、元データを比べてみた。
!> str(rv) Named chr [1:2310] "2016年" "2015年" "2014年" "2013年" "2012年" "2011年" ... - attr(*, "names")= chr [1:2310] "2016000000" "2015000000" "2014000000" "2013 000000" ...
fooな関数は、表名を指定すると、その値を得る関数なんだな。
ちょいと発展
先生の講義を追った後は、発展問題です。お第は実用的なものって事で、 罪種をカテゴリー分けしてるレベルを得てみる。
<CLASS_OBJ id="cat02" name="罪種"> <CLASS code="100" name="刑法犯総数" level="1"/> <CLASS code="110" name="凶悪犯" level="2" parentCode="100"/> <CLASS code="120" name="凶悪犯_殺人" level="3" parentCode="110"/> <CLASS code="130" name="凶悪犯_強盗" level="3" parentCode="110"/> : <CLASS code="270" name="知能犯" level="2" parentCode="100"/> <CLASS code="280" name="知能犯_詐欺" level="3" parentCode="270"/> <CLASS code="290" name="知能犯_横領" level="3" parentCode="270"/>
fooに変わる関数を作るか? そんな無駄はしない(少なくともlisperならばね)。高階関数もどきでいいじゃん。Rもそれを積極的に勧めている。
foo = function(x, what="name") { n = getNodeSet(doc, paste0("//CLASS_OBJ[@id='", x, "']/CLASS")) tbl = sapply(n, xmlGetAttr, what) names(tbl) = sapply(n, xmlGetAttr, "code") tbl[sapply(items, xmlGetAttr, x)] }
デフォルト引数を用意しただけ。whatを指定しなかった場合、nameから拾ってくる。whatを指定したら、それに従う。これだけの修正である。
使い方の要点は
doc = xmlParse("0003191320.xml") items = getNodeSet(doc, "//VALUE") lvl = foo("cat02", what="level") cat01 = foo("cat01") cat02 = foo("cat02") time = foo("time") value = as.numeric(sapply(items, xmlValue)) df = data.frame(lvl, cat01, cat02, time, value) df1 = subset(df, lvl=="2" & time=="2011年")
cat02からlevelを引いてくる。それらを元にフレームdfを作る。それを元に、例としてレベル2の犯種の大分類を2011年で絞り込んでみる。 その結果は、
> head(df) lvl cat01 cat02 time value 1 1 認知件数 刑法犯総数 2016年 996120 2 1 認知件数 刑法犯総数 2015年 1098969 3 1 認知件数 刑法犯総数 2014年 1212163 4 1 認知件数 刑法犯総数 2013年 1314140 5 1 認知件数 刑法犯総数 2012年 1403167 6 1 認知件数 刑法犯総数 2011年 1502951 > head(df1, n=10) lvl cat01 cat02 time value 17 2 認知件数 凶悪犯 2011年 7062 72 2 認知件数 粗暴犯 2011年 61897 149 2 認知件数 窃盗犯 2011年 1152492 193 2 認知件数 知能犯 2011年 40894 325 2 認知件数 風俗犯 2011年 10966 380 2 認知件数 その他の刑法犯 2011年 229640 479 2 検挙件数 凶悪犯 2011年 5287 534 2 検挙件数 粗暴犯 2011年 45095 611 2 検挙件数 窃盗犯 2011年 305922 655 2 検挙件数 知能犯 2011年 26433
元になる表であるdfを確認。そこから絞りこんだ結果を表示。
余談になるけど、DBのkeyとかに一見数値っぽいものが表れる。
<TABLE_INF id="0003191320"> <STAT_NAME code="00130001">犯罪統計</STAT_NAME> <GOV_ORG code="00130">警察庁</GOV_ORG>
例えばこんなの。普通に考えたら、codeなんて数値でいいじゃんと思ってしまう。そうすれば冒頭の無駄と思われる0が省けて嬉しいぞ。
でも、数字と決め打ちしちゃうと、文字通り数値しか表現出来なくなる。DB屋さんは、それを嫌うんだろうね。固定長の文字列なら、アルファベット文字も入れられる。飛躍的に表現力が高まる。そんな理由で、採用してるんだろうね。
まあ、オイラーは頭の0が気になる。頭はポリスのPで始めるとかしてくれませんか。そうすれば、オイラーも諦めがつくってもんです(あっ、BASE64表現とか言い出さないでね)。
nxml-mode.el
emacsでは、サフィックスにxmlって付いているファイルを開くと、xml-modeが有効になる。 ふとっちょ括弧と言うかHTMLのタグの親分みたいなやつでデータを囲んだ文書だ。
ならば、括弧を結んで開いてって出来ると思うんだ。余計な所は編集とか閲覧には目障りなんで、そういう機能が有ってもよさそう。
んか、そんな機能はなさそうだ。nxml-mode.elを開いてざっと見したんだけどね。
(defvar nxml-mode-map (let ((map (make-sparse-keymap))) (define-key map "\M-\C-u" 'nxml-backward-up-element) (define-key map "\M-\C-d" 'nxml-down-element) (define-key map "\M-\C-n" 'nxml-forward-element) (define-key map "\M-\C-p" 'nxml-backward-element)
閲覧に使えそうなキーバインドは、これぐらいしか無かった。中には、これにhtml-modeをかぶせて使ってる猛者もいるようですけど。