読者です 読者をやめる 読者になる 読者になる

DPCデータの分析とかやるブログ

DPCデータの分析なんかをテキトーにやってます。

Bokehで動的なグラフを描画する

そもそもBokehって?

大分前に一度紹介しましたが、PythonのグラフライブラリにBokehというものがあります。
Pythonのデータ可視化というと、matplotlibが圧倒的なデファクトになってます。
そのラッパーであるseabornも有名で、最近のPython本を見るとseabornを使う例も多いですね。
今回紹介するBokehはかなり違った出自で、HTMLを吐き出すのが特徴です。ライブラリはPython側とJavaScript側に分かれており、実際の描画はJavaScript側が行います。
また、グラフの描画レベルが2つに分かれているのも特徴です。

ローレベルのbokeh.plotting

  • 折れ線グラフ、単純な散布図、棒や円などを描画できる
  • D3.jsほどローレベルではないが、割りと近い感じ
  • 当然自由度は高いが、これでグラフを組み上げるのは大変そう…

ハイレベルのbokeh.charts

  • 既に用意された形式で、棒グラフやヒストグラム、散布図など一通り描画できる
  • ユーザーサイドから見ると、seabornと似たような感覚で使える

Bokehのいいところ

グラフは動的にいじれるし、見栄えも良い。HTMLとJavaScriptなので、Webアプリにも簡単に組み込める。

Bokehの悪いところ

ライブラリの性質上、データはPython側で用意し、描画のコードを書くのだが、実際にはJavaScriptにコンバートされることになる。 Python側のデータとJavaScript側のデータは必ずしも同一ではない(!)のに注意しないといけない。
400列の巨大データから、2行だけ使ってグラフを描画するケースなどもあるので、DataFrameを全てJavaScript側に流し込むわけにはいかない、という話だけど…



こんな感じでしょうか。 後は下にJupiter Notebookを貼っておきますので、マウスでいじってみて下さい。 (前回みたいにGistも用意したんですが、JavaScriptのロードでコケました…nbviewerだと動くのは、何でなんでしょうね?)

Jupyter Notebook Viewer

DPC入院料を月別/科別に可視化する

さて今日も元気にJavaScriptでガンガンフロントを書いていきましょう!
…という趣旨の記事を書きそうになって正気に返ったスタゲイラです。こんばんは。
えーと。ここしばらくはこんな本を読んでました。

Reactビギナーズガイド ―コンポーネントベースのフロントエンド開発入門

Reactビギナーズガイド ―コンポーネントベースのフロントエンド開発入門

このままだと、ブログが「日曜プログラマのWebサイト作り」になってしまうので、路線を戻します。
今回はSQLからは離れて、既に「DPC入院料」「科名」「診療月」が入ったデータセットがあるものと仮定し、簡単な可視化をやってみましょう。
使うのはpandasのプロット機能と、seabornです。
Jupyter Notebookをそのまま載せてみました。実務でも、大体こんな感じでやってます。

DPC入院料のグラフ化テスト

実務では月別の推移を見ることが多いです。
そして、大雑把に「今月は入院料が高い」「今月はなんか低い」と分かったら、そこから細かく見ていくわけですが、真っ先に切り分けるのは診療科であることが殆どです。
このように時系列を折れ線グラフで描いたり、分布をヒストグラムや箱ひげ図で確認するのはとても重要です。 分布に異常がないかヒストグラムでチェックして、箱ひげ図で異常値がないか見る感じですね。
最後に外れ値除去を行って再び箱ひげ図を見る、なんてこともやってます。

なんでPythonでGUIアプリを書かないの?

さて、前回はElectronを使ってでっち上げたアプリケーションを公開しました。
ElectronはJavaScriptベースのライブラリです。私が普段使っているのはPythonで、JavaScriptはロクに分かっていません。
が、いざデスクトップアプリを書く段になって、選択したのはElectronでした。

PythonGUIライブラリを使わない理由は?

当初は、PythonGUIライブラリを覚えるべきだ、と思っていました。代表的なものとして、PyQtとKivyがあります。
早速いじってみたのですが、どちらも「テーブルを表示する」というだけのことが、妙に複雑になっていて好きになれませんでした。
例えば、テーブルのデータを変数dfに格納したとします。
Pandasでhtmlテーブルを作るのは、df.to_html()というメソッド1行で事足りてしまいます。
PyQtでの実装は、えーと、StackOverFlowによるとこうみたいですね。
???うーん…???
QtCore.QAbstractTableModelを継承したPandas表示用のクラスを作成し、そこにデータを流し込むみたいです。ほ、ほほう・・・
もちろん、この辺の情報は全て公式の英語ドキュメントをきっちり読み込まないといけません。
Kivyだと、うーん、recycleviewなるものを使いみたいですね。英語のドキュメントを読みましょう。

他の言語を見てみると…

ちなみに同じことをScala-swingで実装すると、まあ、最初からテーブル用のモデルがあるので、大して苦労せず実装できます。
まー、こっちはこっちで英語のドキュメントなのは変わらないんですが、swingそのものが用途に向いているんでしょう。
本来このような用途に最も向いているのは、間違いなくC#ですが、今更新たな言語と格闘する時間もありませんでした。

結局HTMLが簡単だ

まー、UI部品を組み立てていくなら、HTMLが断然簡単ですね。
見た目はCSSで幾らでも変えられるし、プレビューはブラウザですぐ見れるし… Pythonでも、HTMLベースのGUIライブラリがあるといいんですけど。

DPCチェッカーElectron版を公開

今回はこちらです。 ZIPをダウンロードして解凍して、dpc.exeを叩いて下さい。ブラウザではなく、ちゃんとアプリケーションが走る筈です。 何だか一ヶ月ほど前に、「Windows版を作りました!」と自慢げにエントリ書いてますが、ええと、忘れて下さい。
前回のはかなり試験的なリリースで、何せファイルサイズは展開後で400MB超、exeを叩くとまずDOS窓が開き、続いてブラウザが開くという「すわ、マルウェアか!?」という挙動をしてました。
幾ら私がいい加減でも、「うーん、これアプリケーションの体を成してないよね・・・」というくらいには思います。

Electronを使ったWindowsアプリの開発について

Electronはウェブサイトをそのまんまデスクトップアプリにしてしまうライブラリです。
最近、とうとう日本語で書かれた解説書も出版され、これから盛り上がることは間違い・・・ないんじゃ・・・ないかなあ・・・(尻すぼみ
今回はズルをしていて、PythonのFlaskとよく似たライブラリ、expressでベタ移植をやってます。なのであんまり、本来のElectronの書き方ではないです。実装方法についてはあんまり参考にしないで下さい。

これからの課題は?

えーと、Python版は一時停止することになると思います。
理由は簡単で、PythonGUIライブラリを使って、アプリケーションを作る自信が無いからです。 (これについては別エントリで詳述します)
PythonにはPyQt / Kivyといった有名なGUIライブラリがありますが、結論から言うとNode.js覚えた方が早いですね!(白目
本格的なウェブサイトや複雑なアプリケーションの構築ならいざ知らず、こういう軽量アプリなら、下手に複雑なライブラリを覚えるより、別言語で書いた方が早いと言うことは往々にしてあります。
ただ、フロントをElectronにして、バックエンドでPython走らせる方法もあるので、高度な分析機能を実装するならPython版を持ち出すかも・・・?
そこまで高度な機能なら、正直、ツールにしても仕方ない感じがしますが、まあ予定は未定ってことで・・・

「Electronではじめるアプリ開発 ~JavaScript/HTML/CSSでデスクトップアプリを作ろう」読んでます

年明けに「Webが全てにやってくるぞ!」と書いておきながら、何もしてなかったので本を買いました… というのは冗談で、ずっとNode.jsいじってました。で、Electronでアプリ化するにあたり、適当にブログやStackOverflowの記事なんか読みつつ書いてたんですが、やっぱり英語は辛いですね!
素晴らしいタイミングで日本語の本が出たので飛びつきました。ああ、日本語だ…落ち着く…

が、あんまり「めでたし、めでたし」とゆー話ではありません。

JavaScriptの世界の恐ろしさ

第三章からアプリケーションを作り始めますが、いきなりReactが登場します。え、Reactってなに?という向きはここで篩い落とされるか、公式のチュートリアルを読みに行くことになるでしょう。
そうそう、ReactはJSXっていう、HTMLをJavaScriptに埋め込んだみたいなシンタックスを使うらしいですよ!当然素の状態ではブラウザで読み込めないので、トランスパイラを噛ませる必要があります。
え、トランスパイラって何ですか、って?それは、JavaScriptでないものJavaScriptに変換するか、或いは特定のバージョンのJavaScriptを、別のバージョンのJavaScriptに変換するものです。
10年前に、JavaScriptスクリプト言語だと学んだ気がしますが、今はコンパイラ言語になってたんですね!

はいはい、意地悪を書きましたよ。
別に今書いたようなことは、フロントエンドの世界では当然でしょう。CoffeeScriptは2010年にはもうありましたし、babelは2015年には話題になってましたからね。
しかしまあ、それでも泣き言を言うなら、JavaScriptは言語の外側が複雑過ぎる気がします。
貧弱な標準ライブラリの裏返しなのか、欠陥の多い言語設計をカバーしようとした結果なのかは分かりませんが、うーん、すごいカルチャーではあります。
ジャングル、というのがこの状況を表す最もよい言葉でしょう。

そもそも、デスクトップアプリケーションを取り巻く状況が…

とまあ、恨み言を書きましたが、別にこの本に問題があるわけではありません。
本自体は、読んでいくと今のWeb開発の景色が見えるようで、とても面白いです。日本語で書かれた唯一のElectron入門本ですし、これでReactの説明までしてたらボリュームが倍になったでしょうから、仕方ないと思います。
が、うーん…例えばC#WPFアプリケーションを書くより、Electronの方が絶対楽かと言われると、うーん…
恐らくアプリケーションのパーツ周りを書くのはElectronの方が絶対楽ですが(だってHTMLですし)、環境作りという点ではJavaScriptの負荷は高いなあ、という感じ。
まあそれでも、C#を一から覚えるよりはJavaScriptの環境作りに苦労するほうが楽そうですけど。

ちなみに、C#GUIアプリケーションを書くとすると、WPFが真っ先に候補に上がると思いますが、qiitaで検索すると268件しか出てきません。

qiita.com

JavaならJavaFXですが、こちらは何と111件。

qiita.com

Reactは604件。

qiita.com

そしてElectronは487件です!

qiita.com

みんな、どんだけデスクトップアプリが嫌いか分かりますね

様式1データを扱いやすい形に変形する

前回に引き続き様式1の操作を行ってみます。
今回は実際に様式1ファイルをいじりますが、まずは前回のおさらいから。

{('A000010', 1): '生年月日',
 ('A000010', 2): '性別',
 ('A000010', 3): '患者住所地域の郵便番号',
 ('A000020', 1): '入院年月日',
 ('A000020', 2): '入院経路',
 ('A000020', 3): '他院よりの紹介の有無',
 ('A000020', 4): '自院の外来からの入院',
 ('A000020', 5): '予定・救急医療入院',

前回はペイロードを扱いやすくするため、こんな感じでタプルをキーにしたディクショナリ、paydictを作りました。
さて、では様式1のダミーデータを読み込んでみましょう。

ff1 = pd.read_csv('ff1dummy.csv')

こんな感じのデータです。

施設コード データ識別番号 入院年月日 回数管理番号 統括診療情報番号 コード バージョン 連番 ペイロード1 ペイロード2 ペイロード3 ペイロード4 ペイロード5 ペイロード6 ペイロード7 ペイロード8 ペイロード9
999999999 1111 2017/1/1 1 0 A000010 2014/4/1 0 19800101 1 9999999 NaN NaN NaN NaN NaN NaN
999999999 1111 2017/1/1 1 0 A000020 2014/4/1 0 20170101 1 0 0 101 1 0 9 NaN

日本語で「ペイロード1〜9」となっているのが面倒なので、名称を変更します。

ff1.columns = ['施設コード', 'データ識別番号', '入院年月日', '回数管理番号', '統括診療情報番号', 'コード', 'バージョン', '連番',
       '1', '2', '3', '4', '5', '6', '7',
       '8', '9']

これで単なる1~9の連番になりました。 さて、ここからですが、

  1. ペイロードの全項目を独立のカラムにする(横に200列くらいの巨大テーブルになる)
  2. 横に伸びているペイロードを縦に並び替え、項目名をつけられるようにする

という2つの方法があります。1番は以前書いたことがありますし、あんまりいじりやすい形になりませんので、今回は2番で行ってみましょう。

pd.meltを使う

データ識別番号 ペイロード1 ペイロード2 ペイロード3 ペイロード4 …
ダミー ダミー ダミー ダミー ダミー

となっているデータを、

データ識別番号 ペイロード1
データ識別番号 ペイロード2
データ識別番号 ペイロード3
データ識別番号 ペイロード4 …

こんな形に変形させます。 pandasには様々なテーブル変形方法がありますが、今回のようなケースではpd.meltを使用します。

melted = pd.melt(ff1,id_vars = ['データ識別番号','入院年月日','コード'],
        value_vars=['1','2','3','4','5','6','7','8','9'],var_name = 'ペイロード',value_name = 'データ')

こうすると出来上がるのは

データ識別番号 入院年月日 コード ペイロード データ
1111 2017/1/1 A000010 1 19800101
1111 2017/1/1 A000020 1 20170101
1111 2017/1/1 A000010 2 1
1111 2017/1/1 A000020 2 1

こんなデータです。

これで、「コード」と「ペイロード番号」のペアを並べることが出来ました! あとはこれを使って項目名を追加するだけです。

項目名カラムを追加する

melted['項目名'] = [paydict.get((k,int(y)),'') for k,y in zip(melted['コード'],melted['ペイロード'])]

新たなカラム「項目名」を作成します。「コード」と「ペイロード」のペアのキーをzipでまとめてループし、paydictから該当する項目を抜き出します。
なお、ここでpaydict[(k,y)]とやるとエラーになるので気をつけましょう。なんでかというと…

構造上、ペイロード項目はスカスカになっていて、例えばA000010はペイロード1~3のみに情報があり、4~9は空白となります。 paydictには存在する項目のキーしか入っていないので、例えば(A000010,4)をキーにした問い合わせをやると例外が返ります。 ディクショナリの`.get'メソッドを使うと、存在しないキーが指定された場合、例外を返さずにデフォルト値を返すことが出来るので、今回はそれで逃げを打ちました。
さて、出来上がりはこんな感じです。

データ識別番号 入院年月日 コード ペイロード データ 項目名
1111 2017/1/1 A000010 1 19800101 生年月日
1111 2017/1/1 A000020 1 20170101 入院年月日
1111 2017/1/1 A000010 2 1 性別
1111 2017/1/1 A000020 2 1 入院経路

あとは出来たデータを利用していけば良いでしょう。
様式1ファイルは曲者です。他にも色々方法がある筈ですので、試してみて下さい。

様式1のペイロード項目詳細を扱いやすい形に変換する

前回に引き続き、様式1データを扱います。 まず、前回紹介したペイロードマスタを読み込みましょう。

payload = pd.read_csv('ペイロードマスタ.csv')

payload.head()

コード ペイロー ド種別 レコード必須 条件等 有 連番 ペイ ロード番号 項目 必須 条件 有 項目名
A000010 患者属性 - 1 生年月日
A000010 患者属性 - 2 性別
A000010 患者属性 - 3 患者住所地域の郵便番号
A000020 入院情報 - 1 入院年月日
A000020 入院情報 - 2 入院経路
A000020 入院情報 - 3 ※A 他院よりの紹介の有無
A000020 入院情報 - 4 ※A 自院の外来からの入院
A000020 入院情報 - 5 ※A 予定・救急医療入院
A000020 入院情報 - 6 ※A 救急車による搬送の有無
A000020 入院情報 - 7 ※A 入院前の在宅医療の有無
A000020 入院情報 - 8 ※A 自傷行為・自殺企図の有無

こんな感じのデータが出てくるはずです。 さて、このまんまでは煮ても焼いても食えません。
まずやるべきことは、コードペイロード番号のセットを作り、項目名と紐付けることです。 いかにも辞書型を使うケースですよね。
さて、Pythonの辞書型は{key : value}のようにキーと値を紐付けるものです。
keyの部分は変更不可能なものなら何でも使うことが出来ます。例えば、リストは変更可能なのでキーに使えませんが、タプルは使えます。
(どんな用途があるか思いつきませんが、関数も変更不可能なのでキーに使えます)
なので、今回はコードペイロード番号のタプルをキーにした辞書を作ってみましょう。
辞書内包表記を使うのが簡単です。

paydict = {(k1,k2):v for k1,k2,v in zip(payload['コード'],payload['ペイロード番号'],payload['項目名'])}

とすれば、

{('A000010', 1): '生年月日',
 ('A000010', 2): '性別',
 ('A000010', 3): '患者住所地域の郵便番号',
 ('A000020', 1): '入院年月日',
 ('A000020', 2): '入院経路',
 ('A000020', 3): '他院よりの紹介の有無',
 ('A000020', 4): '自院の外来からの入院',
 ('A000020', 5): '予定・救急医療入院',

このようにコードペイロードのタプルをキーにした、項目リストが作れます。 逆転したものが欲しければ、

reverse = {v:k for k,v in paydict2.items()}

としてやるだけで、

{'24 時間以内の死亡の有無': ('A000030', 4),
 'BurnIndex': ('M160010', 2),
 'Hugh-Jones 分類': ('M040010', 2),
 'ICD10 コード': ('A006040', 2),
 'NYHA 心機能分類': ('M050010', 2),
 'UICC 病期分類(M)': ('CAN0020', 5),
 'UICC 病期分類(N)': ('CAN0020', 4),
 'UICC 病期分類(T)': ('CAN0020', 3),
 'UICC 病期分類(版)': ('CAN0020', 6),
 'がんの初発、再発': ('CAN0010', 3),

このように、キーと値を逆転させた辞書が手に入ります。

「うーん、タプルの順番だけでコードとペイロード番号を管理するのは引っかかるぞ…」という向きには、名前付きタプルが使えます。

from collections import namedtuple
Paykey = namedtuple('payload_key',['code','number'])

名前付きタプルは、その名の通り、タプルの要素に名前を付けて管理できます。ちょっと使い捨てクラスっぽいですね。

paydict = {Paykey(k1,k2):v for k1,k2,v in zip(payload['コード'],payload['ペイロード番号'],payload['項目名'])}

としてやれば、

{payload_key(code='A000010', number=1): '生年月日',
 payload_key(code='A000010', number=2): '性別',
 payload_key(code='A000010', number=3): '患者住所地域の郵便番号',
 payload_key(code='A000020', number=1): '入院年月日',
 payload_key(code='A000020', number=2): '入院経路',
 payload_key(code='A000020', number=3): '他院よりの紹介の有無',
 payload_key(code='A000020', number=4): '自院の外来からの入院',
 payload_key(code='A000020', number=5): '予定・救急医療入院',
 payload_key(code='A000020', number=6): '救急車による搬送の有無',
 payload_key(code='A000020', number=7): '入院前の在宅医療の有無',

このように要素に名前をつけて管理できます。
さて次回は、様式1データそのものの加工を行ってみましょう。