なんでPythonでGUIアプリを書かないの?
さて、前回はElectronを使ってでっち上げたアプリケーションを公開しました。
ElectronはJavaScriptベースのライブラリです。私が普段使っているのはPythonで、JavaScriptはロクに分かっていません。
が、いざデスクトップアプリを書く段になって、選択したのはElectronでした。
PythonのGUIライブラリを使わない理由は?
当初は、PythonのGUIライブラリを覚えるべきだ、と思っていました。代表的なものとして、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版は一時停止することになると思います。
理由は簡単で、PythonのGUIライブラリを使って、アプリケーションを作る自信が無いからです。
(これについては別エントリで詳述します)
PythonにはPyQt / Kivyといった有名なGUIライブラリがありますが、結論から言うとNode.js覚えた方が早いですね!(白目
本格的なウェブサイトや複雑なアプリケーションの構築ならいざ知らず、こういう軽量アプリなら、下手に複雑なライブラリを覚えるより、別言語で書いた方が早いと言うことは往々にしてあります。
ただ、フロントをElectronにして、バックエンドでPython走らせる方法もあるので、高度な分析機能を実装するならPython版を持ち出すかも・・・?
そこまで高度な機能なら、正直、ツールにしても仕方ない感じがしますが、まあ予定は未定ってことで・・・
「Electronではじめるアプリ開発 ~JavaScript/HTML/CSSでデスクトップアプリを作ろう」読んでます
年明けに「Webが全てにやってくるぞ!」と書いておきながら、何もしてなかったので本を買いました…
というのは冗談で、ずっとNode.jsいじってました。で、Electronでアプリ化するにあたり、適当にブログやStackOverflowの記事なんか読みつつ書いてたんですが、やっぱり英語は辛いですね!
素晴らしいタイミングで日本語の本が出たので飛びつきました。ああ、日本語だ…落ち着く…
Electronではじめるアプリ開発 ~JavaScript/HTML/CSSでデスクトップアプリを作ろう
- 作者: 野口将人,倉見洋輔
- 出版社/メーカー: 技術評論社
- 発売日: 2017/03/28
- メディア: 大型本
- この商品を含むブログを見る
が、あんまり「めでたし、めでたし」とゆー話ではありません。
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件しか出てきません。
Reactは604件。
そしてElectronは487件です!
みんな、どんだけデスクトップアプリが嫌いか分かりますね
様式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の連番になりました。 さて、ここからですが、
という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データそのものの加工を行ってみましょう。
様式1ファイルの項目マスタをアップしました
このブログでは2回ほど様式1について扱ってきました。
が、まあ、触りだけやって特に深掘りはしていません。
今回は深掘りをするための準備作業をしてみます。
何はともあれ、あの忌々しいペイロードの詳細を見ないといけませんね。
ここに資料がありますので見てみると・・・あれ、これってPDF?
うーん、おかしいですね。
厚生労働省のサイトを頑張って探しましたが、どこにもcsvやエクセルファイルが見当たりません・・・
ここでおさらさいをすると、様式1はこんな形をしています。
様式1全体像
大項目 | 必須 | 小項目 |
---|---|---|
1.ヘッダ部 | ○ | (1) 施設コード |
1.ヘッダ部 | ○ | (2) データ識別番号 |
1.ヘッダ部 | ○ | (3) 入院年月日 |
1.ヘッダ部 | ○ | (4) 回数管理番号 |
1.ヘッダ部 | ○ | (5) 統括診療情報番号 |
2.ペイロード部 | ○ | (1) コード |
2.ペイロード部 | ○ | (2) バージョン |
2.ペイロード部 | ○ | (3) 連番 |
2.ペイロード部 | ※ | (4) ペイロード 1(日付等) |
2.ペイロード部 | ※ | (5) ペイロード 2(コード等) |
2.ペイロード部 | ※ | (6) ペイロード 3 |
2.ペイロード部 | ※ | (7) ペイロード 4 |
2.ペイロード部 | ※ | (8) ペイロード 5 |
2.ペイロード部 | ※ | (9) ペイロード 6 |
2.ペイロード部 | ※ | (10) ペイロード 7 |
2.ペイロード部 | ※ | (11) ペイロード 8 |
2.ペイロード部 | ※ | (12) ペイロード 9(可変長文字列) |
ペイロード部のコード
とペイロード1〜9の組み合わせで、何のデータが入るか決まる、という寸法です。ではどんなデータが入るのでしょうか。
ペイロード部分項目詳細
コード | ペイロー ド種別 | レコード必須 条件等 有 | 連番 | ペイ ロード番号 | 項目 必須 条件 有 | 項目名 |
---|---|---|---|---|---|---|
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 | 自傷行為・自殺企図の有無 |
こんな感じのが、だいたい150個くらいあります。で、これを手作業でPDFから書き起こせと?
またまたご冗談を。
困ったので、PDF→Excelへの変換サービスを使い、その後手作業で整形してマスタを作りました。
こちらからダウンロード出来ます。
様式1全体像は様式1マスタ.csv
に、ペイロード詳細はペイロードマスタ.csv
に入っています。
なお、GitHubでは文字コードがUTF8でないと怒られるので、UTF8にしてあります。Excelで開くとたぶん文字化けしますので、様式1マスタ.xlsx
も用意しておきました。
あとはDBに取り込むなり、Pandasで利用するなり、好きなように使えます。
次回は実際にこのファイルを使って、様式1データとマスタを組み合わせて好きなデータを抽出してみましょう。
なお、今回の記事作成にあたってはこちらのサービスに大いに助けられました。
こんなでかいテーブルをMarkdownで手打ちするのは苦行ですので・・・ とても素晴らしいサービスです。Markdownでブログを書いている方は是非どうぞ。
Postgres用SQLクエリまとめに追加をしました
このブログでは今まで色々なSQLクエリを紹介してきましたが、殆どがPostgreSQL用でした。
SQLite向けのクエリはDPCチェッカーに突っ込んでいますが、Postgres用のクエリはどこにもまとまっていません。
流石に不味い、ということでこちらに随時アップロードしています。
ご利用ください。
Postgresを使った分析について少し
当ブログでは一貫してDPCデータの分析にはPostgres + Python環境がよいと主張し続けています。
配布の関係上、DPCチェッカーではSQLiteを使っていますが、通常、自院でその時それぞれな分析をするなら、Postgresの方が柔軟で多くの状況に対応できるのは間違いありません。
特に、2つ以上のパラメータを使った条件指定は、DPCデータ分析では頻発します。
(データ識別番号と入院年月日をセットにした条件指定、データ識別番号と実施年月日をセットにした条件指定は非常に多用されます)
その際、Postgresなら(データ識別番号,入院年月日) IN (SELECT ...)
のような書き方をして、通常のIN
構文の延長線で書けますが、この書き方に対応していないSQLでは面倒くさいことになります。
この記事でも書きましたが、極力Postgresを利用するのがいいでしょう。
ただ、環境構築や細かい注意点は明文化して来ませんでしたので、そろそろ記事を書こうと思います。
そんなに難しいことはありませんが、場合により詰まるところもありますので・・・