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

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

ICD10を正規表現でパースする

またもやお久しぶりとなってしまいました… さて、今回のお題はICD10のパースです。以前、「I20$のような、I200~209を指定するケースはどうしよう?」という問題を記事にしました。その時は、ゴリ押しでI200~I209までを連番生成して済ませてます。
が、今回(実際にあった話ですが)、「I20$もしくは、I50$もしくは、I1$のどれかに当てはまる疾患を探してね!」という案件が参りました。うーん、どうしたもんでしょうか。

正規表現を使わざるを得ないとき

問題に直面したとき、ある人々はこう考える……「そうか、正規表現を使うんだ!」そして問題が2つに増える

インターネットには賢者の箴言がありますが、そうも言っていられません。
上記のような問題に出くわしたとき、正規表現ではI20[0-9]|I50[0-9]|I1[0-9]のように表記します。 |はor条件を意味しており、[0-9]は0から9の数字に当てはまるもの、という意味です。 上記ですと、正に「I20$もしくは、I50$もしくは、I1$のどれか」という分けですね。 まあ、実際にはI500だけではなく、I5000にも当てはまっちゃうので、本当にそれでいいのかと言われるとアレですが…(この辺は、ICDのコード体系におんぶに抱っこしてます)

例としてPythonで書くとこんな感じですね。

import re
pattern = r"I20[0-9]|I50[0-9]|I1[0-9]"
re.findall(pattern,"I209,I11,I300")

out: ['I209,'I11']

実務でICD10の抽出をするときは、患者単位であることが殆どです。
その場合、患者別にICDのリストを作るのか(例:['I209','I11','I300'])、複数のICDを連結して文字列にしてしまうか(例:"I209,I11,I300")は、ケースバイケースでしょう。
私が関わった案件では単純に連結してしまいましたが、リストのほうが扱いやすいケースもある筈です。

pandasで移動平均を出してみる

お久しぶりです。
ブログはサボってましたが、仕事はサボれず、色々やっていました。
現在、時系列データに取り組んでいますが、全く経験がないので四苦八苦してます。
単に推移を見るだけならプロットすればオシマイですが、突発的な変動は無視して、傾向を見たいとなると、移動平均というものを扱う必要が出てきます。

エクセルでどうやるのかは知りませんが、pandasなら簡単!

ということで、以下の通りすぐに出来ました。
移動平均は一定期間(例えば3ヶ月毎)の平均を、ずらしながら取っていくという数字です。 統計WEBに記事があります
wikipediaにも記事がありますが……
うーん、これ読むよりはいきなりグラフ描いたほうが分かりやすいかも…
なお、wikipediaで紹介されている指数加重移動平均もpandasに実装されています。
数式の理解はともかく、実際にはどっちも使ってみればいいんじゃないでしょうか。使うのは簡単だし。

Pandas rolling_mean & emma example

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に移行するのがオススメですが…)