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

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

前月からDPC請求方法が変化した患者を探す

今年度の改定で、DPC請求/出来高請求は一入院で統一することになりました。

つまり、DPCから出来高分岐になれば、前月以前分は返戻処理することになります。

また、前月でDPCが終わったものの、今月になって手術などを行いDPC期間が延長になれば、前月分を返戻しなければいけません。

面倒ですね。

さて、対象患者を探すにはどうすればいいでしょうか。

まず考えられるのは、入院料に注目する方法です。

具体的には、前月末日の入院料と、当月初日の入院料の区分が違えば、その患者は返戻対象の可能性が高いです。

まあ、たまたま前月末日がDPC最終日だった、というケースもありますので100%確実ではありませんが、探す手がかりにはなります。
早速そのためのスクリプトを書いてみましょう。

import pandas.io.sql as psql
import psycopg2 as pg
import pandas as pd

connection = pg.connect(database='データベースの名前',
                user='ユーザー名',
                host='localhost',
                password='パスワード',
                port=5432)

data1 = psql.read_sql("select データ識別番号,入院年月日,診療科区分,病棟コード,データ区分,診療行為名称 \
from d1606 \
where レセ電算処理コード in (930000000,190077410) \
AND 実施年月日 = '2016-06-30'",connection)

data2 = psql.read_sql("select データ識別番号,入院年月日,診療科区分,病棟コード,データ区分,診療行為名称 \
from d1607 \
where レセ電算処理コード in (930000000,190077410) \
AND 実施年月日 = '2016-07-01'",connection)


kakunin = pd.merge(data1,data2,how='inner',on = ['データ識別番号','入院年月日'])
kakunin['bool'] = kakunin['データ区分_x'] == kakunin['データ区分_y']
kakuninkekka = kakunin[kakunin['bool']==False]
kakuninkekka.to_csv('dpc_changed.csv')
print(kakuninkekka)

まず2つの変数に6月末日/7月1日のデータを投げ込んでいます。 レセ電算処理コードに930000000,190077410が指定されていますが、
930000000がDPC入院料
190077410が10対1入院基本料
です。入院基本料はご自身の病院の設定に合わせてください。
2つのデータをpd.mergeで結合し(how='inner'SQLのINNER JOINと同じ動きになります)、データ区分が一致するか確認します。
出来高入院基本料はデータ区分90番、DPC入院料は93番ですので、これが不一致であれば確認対象者です。 最後の2行でCSVに書き出し、コンソールに対象者を表示します。

入院日の点滴量を調べるSQL(正規表現を使用)

脱水の多い季節です。

この仕事をしていると、夏と言えば脱水で救急医療加算を算定しようとなるのですが、さてEFファイルから対象患者を探すことは出来るでしょうか?

結論から言えば、出来るけど面倒です。

記録されている点滴の内容はこんな感じですね。

診療明細名称 行為回数 使用量
ラクテック注 500mL 1 3

嫌がらせのように全角文字で記録されているのがポイントです。まずこれを数値データにしてやらないといけません。

ステップとしては

  1. 全角英数字を半角英数字にする

  2. mlという文字の直前にある数字のみを抽出する

  3. 2で得られた数字に行為回数と使用量をかけて合計する

さて、この中で一番困難なのは2番です。

単純なテキスト処理では用を成しません。とうとう、正規表現を使うときがやってきました。

正規表現とは

残念ながら正規表現の説明を出来るほど私は詳しくありませんので、サルでもわかる正規表現さんをどうぞ。たぶん一番わかりやすい説明がされたサイトさんです。

では早速SQLを書いてみます。

WITH eng AS (select データ識別番号,入院年月日,実施年月日,病棟コード,診療明細名称,行為回数,使用量,translate(診療明細名称,
'-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrsuvwxyz'
,
'-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz') AS 診療明細名称2
from ef1607
where データ区分 = 33
AND 入院年月日 = 実施年月日)
select 病棟コード,データ識別番号-10 AS ID,実施年月日,SUM(CAST(substring(診療明細名称2 from '([0-9]*)mL') as Numeric)*行為回数*使用量)
from eng
Where 診療明細名称2 ~ '[0-9]*mL'
AND 診療明細名称2 !~ '\DmL' 
group by 病棟コード,データ識別番号,実施年月日

まず最初のWITH句で全角英数字を変換しています。これにはtranslate関数を使います。
続いてSUM(CAST(substring(診療明細名称2 from '([0-9]*)mL') as Numeric)*行為回数*使用量) という見るもおぞましい箇所が出てきますが、これがステップ2と3を行う部分です。

substring(診療明細名称2 from '([0-9]*)mL'正規表現を使用したテキスト処理です。
substing関数は、最初の引数で指定したレコードから、from 'なんとか'で指定したパターンに従い文字列を抽出する関数になります。では、肝心の正規表現部分を説明しましょう。

[0-9]が任意の数字を表し、はそれが任意の回数繰り返される、という意味です。
合わせて[0-9]
とすることで、「何か数字が入っている部分を抜き出す」効果が得られます。
[0-9]*mLとすれば、500mLが抜き出されます。
しかし今回抜き出したいのは数字だけですので、その部分を()で囲んでいます。

これで数字データが得られた・・・と思いきや、ここで得られるのは文字列ですので、CASTで数字型に変換し、更にそれをSUMで合計します。我ながらひどいSQL文ですね。

ふう。どうにか点滴の使用量を抽出することが出来ました。 と思いきや、実はEFデータには「そもそもmL単位が入っていない(留置針など)」、「50mg/mL」のように点滴量とは関係ない形でmLが入っているデータが混在しています。 仕方がないのでWhere 診療明細名称2 ~ '[0-9]*mL' AND 診療明細名称2 !~ '\DmL' でそれらは除外しました。 診療明細名称2 ~ '[0-9]*mL'は奇妙に見えるかも知れません。~はLIKE文の正規表現版です。
LIKE文のように、特定のパターンにマッチしたデータを指定することが出来ます。
!~NOT LIKEと同じ働きをします。\Dは「数字以外」を意味しますので、この2行で「○○○mLという表記があり、かつmLの直前が数字であるもの」のみを抽出することになります。

やれやれ。酷い目に遭いました。
しかし、正規表現は極めて強力な手段です。 コードが呪文めいて見えてしまい、後で修正するとき酷く苦労しそうですが、慎重に使えば大きな助けになるでしょう。

グラフライブラリBokehの紹介(動く例つき)

以前、Bokehというグラフライブラリの紹介をしましたが、その時は「Webベースのグラフが出来上がる」と触れ込んだくせに例は画像ファイルという体たらくでした。
今回は反省して実際に動く例を作りましたので、リンクを張ります。

Bokehの実例

ぐりぐり動かしたり、拡大縮小したり出来ますので、いじってみてください。
なお、サンプルデータは以下のような形式です。 dpc点数の数値データを、科名・dpc期間というカテゴリーで切り分けてグラフ化しています。

dpc期間 dpc点数 科名
2 1891.498393 1
1 1933.910512 8
2 1859.893487 7
1 1980.984604 2
3 1966.610687 5

NULLの問題について(テーブル結合の落とし穴)

最近あった問題点のお話です。 このブログでもDPC出来高対比の出し方は何度か記事にしてきました。
要は「データ識別番号」「入院年月日」の二つをキーにしてEFファイル/Dファイルを集計し、二つのテーブルを結合するという手順を踏むのですが、では日別に集計したらどうなるのでしょうか?
具体的には「データ識別番号」「実施年月日」をキーにして集計し結合すれば、日別の対比が出せます。
応用すればDPC期間別のデータなども作れて便利ですので、実際にやってみたところ、ものの見事に失敗しました。
原因は短期滞在入院料が含まれたデータを、何も考えずに集計したせいです。 何が起こるかというと…  
- 当初のイメージ  

データ識別番号 入院年月日 実施年月日 出来高点数 DPC点数
001 2016-07-01 2016-07-01 9000 12000
001 2016-07-01 2016-07-02 3000 0
001 2016-07-01 2016-07-03 3000 0

 
- 実際のデータ  

データ識別番号 入院年月日 実施年月日 出来高点数 DPC点数
001 2016-07-01 2016-07-01 9000 12000

おや?7月2日と3日のデータが消えていますね? 実際の短期滞在入院料のデータは、Dファイル上では短期滞在入院料のみが記録され、他の日のデータは綺麗さっぱり消えています。Dファイルのデータはレセプトと同一のものになるよう設計されているので、ある意味では当然のことですが…
なので、短期滞在のデータを集計すると、こんな感じになります。
 
- Dファイル

データ識別番号 入院年月日 実施年月日 出来高点数 DPC点数
001 2016-07-01 2016-07-01 9000 12000

 
- EFファイル

データ識別番号 入院年月日 実施年月日 出来高点数
001 2016-07-01 2016-07-01 9000
001 2016-07-01 2016-07-02 3000
001 2016-07-01 2016-07-03 3000

7月2日、7月3日にも入院はしていますが、データは存在しないので0点にもなりません。
これはとても重要なことなので、集計時に注意しておいて下さい。
さて、この二つを突合するとき、SQLやPandasでは「外部結合」(OUTER JOIN)と「内部結合」(INNER JOIN)を選べます。 「内部結合」では、二つのデータのキーを比べ、「一致するもの」だけが残り、片方にしか存在しないレコードはどこかへ消えます。 冒頭のデータ、

データ識別番号 入院年月日 実施年月日 出来高点数 DPC点数
001 2016-07-01 2016-07-01 9000 12000

は、DファイルとEFファイルの集計結果を「データ識別番号」「実施年月日」をキーに何も考えず結合した結果です。
さて、それではどうすれば正しいデータを得られるでしょうか?
内部結合で失敗したのだから、次は外部結合です。こちらは二つのデータのキーを比べ、存在しないものにはNULLを返します。 結果はどうなるかといいますと…

データ識別番号 入院年月日 実施年月日 出来高点数 DPC点数
001 2016-07-01 2016-07-01 9000 12000
001 2016-07-01 2016-07-02 3000 NULL
001 2016-07-01 2016-07-03 3000 NULL

本当は0であって欲しいところに、NULLが入っています。
NULLってなんだよと言うと、レコードが存在しない状態を指すステータスです。
0は数値データであり、きちんと0として存在していますが、NULLはそうではありません。従って、
0 - 3000 = -3000
ですが、
NULL - 3000
はNULLを返します。 つまり、このNULLはどうにかして0に変換しなくてはいけないのです。 方法としては、

  1. COALESCE関数を使う。COALESCE(DPC点数,0)とするとDPC点数がNULLになる場合、自動的に0に変換してくれます。

  2. SQLでの作業は諦めてスクリプト上で処理する。PandasならfillnaメソッドでNULLレコードを一斉置換出来ます。

どちらを使うかはケース次第ですが、原因がわかってしまえば対処の難しい問題ではありません。

Bokehを使ったグラフ描画

今までPythonでのグラフ描画といえばmatplotlibやSeabornがメジャーでしたが、ここにきてBokehというライブラリが完成度を上げてきました。 matplotlibが最終的に静的なイメージファイルを吐き出すのと違い、Bokehはhtmlファイルを書き出します。 縮小・拡大・移動のようなユーザー操作を簡単に付加出来るので、Webで公開するような用途でも使えるでしょう。
例として、以下の様なテーブルの可視化を考えてみます。 なお、AnacondaをインストールするとBokehも入ってきますが、バージョン0.11では「外れ値があるとヒストグラムが壊れる」という割と重大なバグがあったりするので注意しましょう。conda update bokehで0.12に更新するのがオススメです。

dpc点数 科番号 科名
1919.756733 3 整形
2037.609047 3 整形
2033.869387 3 整形
1940.643659 1 内科
2018.686431 2 外科
import pandas as pd
import datetime
import numpy as np
from bokeh.charts import Histogram, output_file, show
from bokeh.layouts import row

#サンプルデータを作成
dpc = np.random.normal(2000,100,5000)
ka = np.random.randint(1,4,5000)
kadict = {1:'内科',2:'外科',3:'整形'}
df = pd.DataFrame({'dpc点数':dpc,'科番号':ka})
df['科名'] = [kadict[x] for x in df['科番号']]

#グラフをプロット
hist = Histogram(df, values='dpc点数', title="dpc点数", plot_width=400,bins = 20)
hist2 = Histogram(df, values='dpc点数', label='科名', color='科名', legend='top_right',
                  title="DPC点数科別", plot_width=400,bins = 20)

output_file('hist_test.html')
show(row(hist, hist2))

結果はhmtlファイルとして書きだされます。

f:id:stagira:20160711192614p:plainf:id:stagira:20160711192620p:plain

はてなブログでhtmlを置く方法がわからなかったので画像を貼り付けましたが、実際には拡大したり移動したりが出来ます。 記法もシンプルですので、ぜひ活用してみてください。

DPC出来高対比を可視化する

前々回はSeabornを使ったグラフ描画を紹介しました。
今回は具体的にデータの可視化を行ってみましょう。試しに、一ヶ月の患者別DPC出来高対比点数をグラフにしてみます。

import pandas.io.sql as psql
import psycopg2 as pg
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

connection = pg.connect(database='ここにデータベース名を入れます',
                user='ユーザー名、postgresならデフォルトはpostgres',
                host='localhost',
                password='パスワードを入れましょう',
                port=5432)


data = psql.read_sql("WITH dsum AS ( \
select データ識別番号,入院年月日,sum(行為点数*行為回数) AS DPC総点数 \
from d1606 \
where データ区分 <> 97 \
group by データ識別番号,入院年月日), \
efsum AS( \
SELECT データ識別番号,入院年月日,SUM(出来高実績点数*行為回数) AS 出来高総点数 \
FROM ef1606 \
WHERE データ区分 <> 92 \
AND データ区分 <> 97 \
group by データ識別番号,入院年月日) \
select distinct d.データ識別番号,d.入院年月日, \
d.DPC総点数,e.出来高総点数,d.DPC総点数-e.出来高総点数 AS 点数差異 \
from dsum AS d \
INNER JOIN efsum AS e \
USING(データ識別番号,入院年月日)",connection)

    
sns.set(style="darkgrid")
sns.set_context("talk",1.5)

ax1 = sns.distplot(data['点数差異'],kde=0,norm_hist=0)
plt.show()
data.describe()

結果は「DPC点数-出来高点数」をヒストグラムにしたものです。 大きなマイナス、大きなプラスがグラフの左右に離れ小島のように存在するはずです。 f:id:stagira:20160704200831p:plain これに目をつけて「極端にマイナス(もしくはプラス)になるのはどんな例か見てもいいですし、逆に全体としての傾向に注目してもよいでしょう。

副傷病分岐の無いDPCを探すSQL

DPC分類番号はご存知の通り14桁の英数字で構成されています。 それぞれの番号にはルールが設定されていますので、それに従うことで特定の条件に当てはまる患者データを抽出することが可能です。
例えば、手術をしている、処置1で分岐している等ですね。
今回は「副傷病分岐の無いDPC」を探してみます。ありがちな話ですが、コーディング担当者の見落としで、実は副傷病分岐がある、というケースもあるからです。
それを全患者疑ってかかるのは非常にめんどいので、あらかじめ「誰を」疑うのか決めてしまいましょう。

select distinct 病棟コード,データ識別番号,診療行為名称,分類番号
from d1606
Where 分類番号 LIKE '____________0_'
AND データ区分 = 93

Where 分類番号 LIKE '____________0_' がミソです。 分かりづらいですが、12桁のアンダーバーと0,そして1桁のアンダーバーです。
PostgreのLIKE構文は、アンダーバーを「任意の1文字」と見なします。
この条件では、第13桁目が「0」のものが検索されます。
そもそも副傷病分岐がないものは「x」が入りますし、既に分岐されているものは「1」が入っていますので、得られる結果は「副傷病分岐はあるが、分岐されていない患者」のリストです。

ちなみに%は「任意の何か、有無は問わない」と見なされます。 なので「何でもいいから脳梗塞という文言が入ったもの」という条件で検索したいときは%脳梗塞%としてやれば、「急性脳梗塞」も「脳梗塞後遺症」も「脳梗塞」も全て引っかかります。

今回は極めて単純なパターンマッチなのでSQLで済ませましたが、これ以上複雑なことをやるのであればスクリプト側で処理するのが良いでしょう。