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

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

scikit-learn 勉強ノート(2)

前回から大分間が空きましたが、scikit-learnの勉強メモです。
おさらいすると、サンプルとしてIrisデータセットを使い、アヤメの品種を分類していたのでした。
データセットは、がく片や花びらの長さが記録されたXと正解ラベルが記載されたyに分かれています。
これを分割して、

  • X_train, y_train

  • X_test, y_test

の2ペアにし、trainデータで学習して、testデータで正解率を見る、というのが流れです。

前回はRandom Forestモデルを使いましたが、今回はsvmモデルを使用することにします。

from sklearn.svm import LinearSVC
svc = LinearSVC()

さて早速学習しましょう…と言いたいところですが、ここで注意点。
Random Forestは特例で、データが数値データであろうがカテゴリデータであろうが、また桁違いのデータが混在していようが、一切構わず、そのまま投げ込んでも結果が出てしまいます。
機械学習のモデルの中では例外中の例外と言ってよく、他のモデルは

  • そもそもカテゴリデータは(そのままでは)扱えない

  • データのスケールが違う場合は、同じスケールになるよう変換する必要がある

ことが殆どです。
今回使うSVMも、これに該当します。手作業で変換するのは御免なので、ライブラリを使います。
SVMではMinMaxScalerを使うそうなので、早速インポートしましょう。

from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
scaler.fit(X_train)

学習のときと同じで、.fitメソッドを訓練データに適用します。
後の流れもよく似ています。

X_train_scaled = scaler.transform(X_train)
X_test_scaled = scaler.transform(X_test)

これで、同サイズに変換されたデータが手に入りました。 あとは

svc.fit(X_train_scaled,y_train)
svc.score(X_test_scaled,y_test)

とするだけです。scikit-learnは異なるモデルでも同じようなインターフェースを持っているので、一度基本を学ぶと色んなモデルを試してみることが出来ます。

例えばロジスティック回帰なら、

from sklearn.linear_model import LogisticRegression
logreg = LogisticRegression()
logreg.fit(X_train_scaled, y_train)

logreg.score(X_test_scaled, y_test)

と、殆ど同じコードで比較が出来ます。
実際にはもちろん、モデル毎に意味合いの異なるパラメータをいじる必要がありますが、モデルの比較が簡単に出来ていいですね。

[読書] 「達人に学ぶ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は本当に使いやすいライブラリですね。