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

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

[読書] 「達人に学ぶDB設計」「The Hitchhiker's Guide to Python」

特にネタもないので、最近読んだ本の話でも。

達人に学ぶDB設計

達人に学ぶDB設計 徹底指南書

達人に学ぶDB設計 徹底指南書

SQL解説本でご存じ、ミック先生のDB設計本です。
ひとまずSQL文を書けるようになる、くらいになった後で、「さてDBはどう設計したらいんだろ?」と思うことは、割とあります。
「そういえば正規化ってどうすればいいんだろ?」とか。
この本には、今まで読んだ中で一番わかりやすい正規化の説明があります。
そもそもデータをどのように保存すべきか、という基本中の基本から話が始まるので、SQLを始めるときにもいい本でしょう。

  • コンピュータが扱いやすい表とは何か

というのは、非常に重要な問題ですが、特に義務教育で教えることもなく、実務で学ぶしかありません。
こういう本で学べるなら、それに越したことはないでしょう。

The Hitchhiker’s Guide to Python

The Hitchhiker's Guide to Python: Best Practices for Development

The Hitchhiker's Guide to Python: Best Practices for Development

突然の洋書。
実際のところ、通読する気は更々無くて、Chapter 5 “Reading Great Code"のために買いました。
勉強がてらhowdoiのコードを読んだりしてたんですが、やっぱり解説書欲しいなーと思ったので・・・
本書のChapter5 では、howdoi, Diamond, Tablib, Requests, Werkzeug, Flaskのコードが解説されてます。
ちなみに、Chapter5以外の部分は、翻訳が無償公開されています。
「洋書なんて読んでられっか!」という向きは、こちらをどうぞ。

ICD→MDC6の変換表を作成する

色々あって、ICD10→DPCの頭6桁の変換を行う必要に迫られました。 とはいえ、電子点数表がありますから、楽勝でしょう。
そう思ってデータを見ると…ん…あれ…?

I20$ → 050050

とかありますね。 困りました。実データは、I209とかなのです。これを050050に変換するには、どうすればいいのでしょうか?

ICD表を拡張する

しょうがないので表を拡張することにします。 要は I200 I201 I202 ~以下省略

と、0~9まで増やしてやればよいのです。
ということで、以下のように作業してみました。
もっとスマートな方法がありそうなもんですが、とりあえずはこれで凌ぐことにします。

ICD10 => MDC6変換マスタ作成

EF/Dファイルのテーブル定義について

今更ですが、EFファイル/DファイルをDBに読み込む際のテーブル定義について、少し見直しをしていました。

今まで、「主キーとすべき項目が見当たらない」という理由で、id列を連番で振っていましたが、冷静になってみると複合主キー使えばいいんですね…

Postgresのドキュメントを見てみましょう。

5.3. 制約

主キーも複数の列に渡ることがあり、その構文は一意性制約に似ています。
CREATE TABLE example ( a integer, b integer, c integer, PRIMARY KEY (a, c) );

と、はっきり例が載ってます。
ということで、自動付番のid列を作るのを止めたのが以下の例です。

  • EFファイル
CREATE TABLE ETABLE
(施設コード VARCHAR(255),
データ識別番号 INTEGER,
退院年月日 DATE,
入院年月日 DATE,
データ区分 DOUBLE PRECISION, 
順序番号 DOUBLE PRECISION,
行為明細番号 DOUBLE PRECISION,
病院点数マスタコード DOUBLE PRECISION,
レセプト電算コード DOUBLE PRECISION,
解釈番号 VARCHAR(255),
診療明細名称 VARCHAR(255),
使用量 DOUBLE PRECISION,
基準単位 DOUBLE PRECISION,
明細点数・金額 DOUBLE PRECISION,
円点区分 DOUBLE PRECISION,
出来高実績点数 DOUBLE PRECISION,
行為明細区分情報 CHAR(12),
行為点数 DOUBLE PRECISION,
行為薬剤料 DOUBLE PRECISION,
行為材料料 DOUBLE PRECISION,
行為回数 DOUBLE PRECISION,
保険者番号 DOUBLE PRECISION,
レセプト種別コード DOUBLE PRECISION,
実施年月日 DATE,
レセプト科区分 DOUBLE PRECISION,
診療科区分 DOUBLE PRECISION,
医師コード VARCHAR(255),
病棟コード VARCHAR(255),
病棟区分 DOUBLE PRECISION,
入外区分 VARCHAR(8),
施設タイプ VARCHAR(8),
PRIMARY KEY(データ識別番号,データ区分,順序番号,行為明細番号,実施年月日));
  • Dファイル
CREATE TABLE DTABLE
(施設番号 VARCHAR(255),
データ識別番号 INTEGER,
退院年月日 DATE,
入院年月日 DATE,
データ区分 DOUBLE PRECISION, 
順序番号 DOUBLE PRECISION,
点数マスタコード DOUBLE PRECISION,
レセ電算処理コード DOUBLE PRECISION,
解釈番号 VARCHAR(255),
診療行為名称 VARCHAR(255),
行為点数 DOUBLE PRECISION,
行為薬剤料 DOUBLE PRECISION,
行為材料料 DOUBLE PRECISION,
円点区分 DOUBLE PRECISION,
行為回数 DOUBLE PRECISION,
保険者番号 DOUBLE PRECISION,
レセプト種別コード DOUBLE PRECISION,
実施年月日 DATE,
レセプト科区分 DOUBLE PRECISION,
診療科区分 DOUBLE PRECISION,
医師コード VARCHAR(255),
病棟コード VARCHAR(255),
病棟区分 DOUBLE PRECISION,
入外区分 DOUBLE PRECISION,
施設タイプ DOUBLE PRECISION,
算定開始日 VARCHAR(8),
算定終了日 VARCHAR(8),
算定起算日 VARCHAR(8),
分類番号 VARCHAR(255),
医療機関係数 VARCHAR(255),
PRIMARY KEY(データ識別番号,データ区分,順序番号,実施年月日));

ご覧のとおり、

  • EFファイルはデータ識別番号,データ区分,順序番号,行為明細番号,実施年月日

  • Dファイルはータ識別番号,データ区分,順序番号,実施年月日

の組み合わせを主キーとしています。仕様書を読む限り、この組み合わせでデータが一意に定まる…はず…
もし実際に使って問題が起きたら、ご連絡頂けると助かります。
2,3ヶ月分これで登録出来たので、多分大丈夫だとは思うんですが…

「病床の機能別分類」を境界点に従って区分する

いきなり何のこと?

病床の機能別分類は、国が(かなりいい加減に使っていた)高度急性期・急性期・回復期・慢性期の定義付けです。
ちゃんとした数字で指標を作らないと、統計も取れませんからね。
紹介記事はこちらを参考にしてください。

該当記事によれば、日別の出来高点数を見て、

  • C1 3000点以上 高度急性期
  • C2 600点以上 急性期
  • C3 225点以上 回復期
  • それ以下 慢性期

と分けるようです。 「リハビリテーション料の一部を除く」とあるのが気になりますが、ひとまず大雑把にリハビリを除外してしまいましょう。
さくっとSQLを書くとこんな感じです。

一日あたり点数を出すSQL

SELECT データ識別番号,入院年月日,実施年月日,SUM(出来高実績点数*行為回数) AS 出来高総点数
FROM efile
WHERE データ区分 < 90
AND データ区分 <> 70
group by データ識別番号,入院年月日,実施年月日

以上終了です、ありがとうございました…
というのはあんまりですね。これからエクセルで集計しろ、というのでしょうか。
もう少しSQL側で何とか出来ないか考えてみましょう。

CASE式で区分を組み込む

SELECT データ識別番号,入院年月日,実施年月日,SUM(出来高実績点数*行為回数) AS 出来高総点数,
CASE WHEN SUM(出来高実績点数*行為回数) > 2999 THEN '高度急性期'
WHEN SUM(出来高実績点数*行為回数) > 599 AND SUM(出来高実績点数*行為回数) < 3000 THEN '急性期'
WHEN SUM(出来高実績点数*行為回数) > 224 AND SUM(出来高実績点数*行為回数) < 600 THEN '回復期'
ELSE '慢性期' END AS class
FROM efile
WHERE データ区分 < 90
AND データ区分 <> 70
group by データ識別番号,入院年月日,実施年月日

いきなり面倒になりました。
とはいえ、やっていることはそんなに複雑ではありません。
CASE式は、この場合ですとclassというカラムを定義します。
(END AS classのところで名前を決めています)
あとは、わりと見ての通りです。出来高点数の合計と、最初に上げたC1~C3の区分を比較して、分類の振り分けを行っているだけです。
さて、これで区分がつきましたので、あとはピボットテーブルで…

クエリ全体をSELECTする

それも面倒くさいわ!そもそもエクセル触りたくない!
という方のためのクエリです。 クエリ全体をAS fooという感じで名付けてやり、それに大してSELECTしてみましょう。
こんな感じになります。

SELECT count(sum.データ識別番号),sum.class
FROM (
SELECT データ識別番号,入院年月日,実施年月日,SUM(出来高実績点数*行為回数) AS 出来高総点数,
CASE WHEN SUM(出来高実績点数*行為回数) > 2999 THEN '高度急性期'
WHEN SUM(出来高実績点数*行為回数) > 599 AND SUM(出来高実績点数*行為回数) < 3000 THEN '急性期'
WHEN SUM(出来高実績点数*行為回数) > 224 AND SUM(出来高実績点数*行為回数) < 600 THEN '回復期'
ELSE '慢性期' END AS class
FROM efile
WHERE データ区分 < 90
AND データ区分 <> 70
group by データ識別番号,入院年月日,実施年月日) AS sum
group by sum.class

ここまで来ると、正直おぞましい気がしますが、追加したのはSELECT count(sum.データ識別番号),sum.classFROM ( 〜 group by sum.classの部分だけです。
結果は、区分別の患者延件数になります。
お好みで、診療科や病棟を追加してやれば、診療科別・病棟別のデータもすぐに作れて便利です。
このようにCASE式は便利ですので、ちょっとした分析にどうぞ。
(ある程度複雑になったら、さっさとPandasに移行するのがオススメですが…)

DPCチェッカーの32bit版を公開…してみるが…

例によってこちらからどうぞ。
が、残念ながら既知の問題があります。
ある大きさ以上のファイルを読み込もうとすると、メモリ不足で落ちるようです。
(実際、検証したらエラーを吐きました)
32bit版WIndowsで、メモリ4GB(うちOSが3GB使用)で落ちたので、う、うーん…
EF/Dファイルのサイズによっては動きますが、あんまりアテになりません。

今のところ考えている対策

現在、

  1. EF/Dファイルをファイルアップロードの方法でローカルにコピーする
  2. tmpフォルダに移動する
  3. SQLiteコマンドを走らせてDBに登録する

というステップを踏んでいますが、この辺りの設計を見直そうかなーと思ってます。
今回の問題は2のステップでコケてるので、2のステップを無くすのも手かな…
解決できたらまた記事にします。

scikit-learn 勉強ノート(1)

まえがきと目標

scikit-learnは最も有名なPython機械学習ライブラリです。
私も去年、「そろそろ機械学習をやるぞ!」と思い立って公式サイトを覗き、早々に諦めた覚えがあります。
だって恐ろしい数式が乱舞してますし、そもそも何を説明しているのかも分からないんですからね!

しかし、最近になって素晴らしい解説書が続いています。

Pythonではじめる機械学習 ―scikit-learnで学ぶ特徴量エンジニアリングと機械学習の基礎

Pythonではじめる機械学習 ―scikit-learnで学ぶ特徴量エンジニアリングと機械学習の基礎

特に「Pythonではじめる機械学習」は優れた入門書です。まだ読んでいる途中ですが、最初の数章を読むだけでライブラリを使い始めることが出来ます。
多くのPythonライブラリと違い、scikit-learnはそもそも使い始めるのにハードルが高い印象だったので、とても助かりました。

ちなみに、この勉強ノートでは

  • ライブラリを(お試し程度に)使えるようになること

を目標とし、数学的原理には踏み込みません。

基本準備

前提条件としてnumpyが必須です。import numpy as npでインポートしておきます。

データを用意し、分割する

なにはともあれデータを用意しましょう。
都合よく、頻用されるirisデータセットを読み込むための関数があります。

from sklearn.datasets import load_iris
iris = load_iris()

さて、load_iris()の返り値となるデータセットには、'target', ‘data’, ‘feature_names’, ‘target_names’, ‘DESCR’ の要素が含まれています。

  • target 各々のデータが何の品種だったかが数字で表されている。回答ラベル
  • data 花の萼片と花弁のサイズが記録されている。これを元にtargetを推測するのが練習問題
  • feature_names dataの各カラムに何の情報が入っているかの説明
  • target_names targetは数字で記録されているので、それに対応する品種名
  • DESCR このデータセットの解説

さて実際に学習に使うデータはdataで、回答ラベルはtargetです。まずはこの2つを、学習用とテスト用に分割してやらないといけません。
そんなことを手作業ではやってられないので、ちゃんと方法が用意されています。

from sklearn.model_selection import train_test_split
(旧バージョンのscikit-learnではfrom sklearn.cross_validation import train_test_split)

train_test_splitはデータと回答ラベルを引数に取り、それを学習用とテスト用に自動分割します。
慣例でデータはX_train, X_testに、回答ラベルはy_train,y_testに分割することとなっているようです。

X_train,X_test,y_train,y_test = train_test_split(
iris['data'],iris['target'],random_state=0)

これで4つの変数に学習用とテスト用データが保存されました。

モデルのインスタンスを作成する

from sklearn.ensemble import RandomForestClassifier
forest = RandomForestClassifier()

scikit-learnには様々なモデルが含まれています。モデルは特定のやり方でデータを読み込み、予測または分類を行います。
Classifierとなっていれば分類用のモデルですし、Regressorとなっていれば回帰用のモデルです。
今回例にしたランダムフォレストというモデルは、ClassifierとRegressorを両方持っています。
(つまりRandomForestRegressorもあります)

最初にすることはモデルのインスタンスを作成することで、まあ、forest = RandomForestClassifier()とあるように適当な変数に格納しておきます。
この時点ではデータを読み込みません。

モデルにデータを読み込ませて、予測や正答率を確かめる

モデルにデータを読み込ませるのは簡単です。.fitメソッドを使います。

forest.fit(X_train,y_train) 

これでforest上書きされて、学習データと回答ラベルから予測モデルを作ります。
X_trainはnumpyの多次元配列、y_trainはnumpyの一次元配列である必要があります。

最後に、予測と正答率を見てみましょう。

テストデータに対する予測をするには、

forest.predict(X_test)

とするだけで、予測したラベルを出してくれます。これを元に手作業で正解ラベルと比べてもいいのですが、もちろんもっと良い方法が用意されています。

forest.score(X_test,y_test)

で、モデルをテストデータに適用した場合の正答率を出してくれます。
今回は、まずIrisデータセットを使った分類問題をやってみました。いざやってみると、scikit-learnは本当に使いやすいライブラリですね。

エクセルは事務職の生産性を本当に上げたのか?

事務職の生産性を上げるために

えー、歴史を鑑みると、近代というのは機械化の時代です。
人間は逆立ちしてもトラクターには勝てませんし、馬ではトラックと競争出来ません。
事務職にとってコンピュータは、運送業におけるトラックと同じくらい大きな意味があります。
トラックは運送業の生産性を飛躍させました。 コンピュータも、同じように事務職の生産性を…うーん、あれ、あんまり上がってないような…

神エクセル問題

エクセルを使うと、まずいことに紙の書類をそのまんま再現出来てしまいます。
これはエクセルというツールの功罪です。紙の書類をディスプレイで再現する時、我々はコンピュータを「消しゴムの要らない紙」として使っているのです。

消しゴムの要らない紙は、確かに便利ですが、生産性を上げることはありません。

そして本来なら、それはワードの仕事でした。ワードが「紙の代替品」として余りにも非力なため、何故かエクセルで方眼紙を作る羽目になっていますが、本来エクセルは表計算ツールです!

エクセルの功罪

マイクロソフトは「機能を削る」ことをしない会社です。
そのため、エクセルはユーザーの要求のまま肥大化を重ね、ほとんど何でも出来る万能ツールになってしまいました。
一見いいことに見えますよね?
ですが、平凡なドライバーにはマニュアル車よりオートマ車の方が安全なように、機能を削ったほうが「正しく」使える道具というのは存外多いものです。

例えば、セルの結合を使うと、見た目は綺麗な表が出来上がりますが、データの再利用は困難になります。ちょっと考えてみて下さい。
エクセルは表計算ツールです。データの再利用を困難にしたら、これは一体何のためのツールなんでしょうか?
エクセルの落とし穴は、表計算ツールなのに、体裁を整える機能がやたらと多い点に尽きるでしょう。

データと見た目は分割すべき

理想的なツールは恐らく、データの保存部分と表の整形部分が完全に分割されたものです。
これなら上記の問題は無くなります。
ウェブサイトは正にそういう構造をしていて、データはDBに、デザインはHTML/CSSに、と完全に分割されていますね。
サイトデザインに致命的な変更を行って、大失敗に終わったとしても、DBのデータが消し飛んだりはしません。
ですが、エクセルではそんなことが日常茶飯事です。これはとんでもない欠陥です。

これからは、事務職も生産性を上げることが求められるでしょう。
その手段は、ほぼ間違いなくスクリプトによる自動化です。
しかし、紙の書類を再現したエクセルは、自動化を大いに妨げてしまいます。
「元データを保存するシート」「見た目をいじるシート」のように完全に分離して作ると、大分マシにはなるんですが…