1. 概要
レセプトデータの分析では、データの特性を理解しないまま処理を進めると、思わぬエラーや分析結果の誤りにつながります。本記事では、数多くある注意点の中から代表的な3つの例(結合キーの重複、傷病名コードの先頭ゼロ欠落、ICD-10-2の見落とし)を取り上げ、具体的な対策とコード例を紹介します。
2. はじめに
こんにちは。アナリティクスエンジニアの横田です。
ホワイトヘルスケアでは、リアルワールドデータの一つであるレセプトデータを分析しています。DWHとしてはBigQueryを用い、dbtやPythonを使ったデータの前処理、分析を行っています。
データ分析結果の品質担保は難しい側面があるものの、例えばdbtではデータに対するテストを記述でき、これにより一定の品質担保に貢献できます。
この記事では、私たちがレセプトデータの分析において気をつけている数多くのポイントの中から、特に重要な例をいくつか共有し、同様の課題に取り組む方々の分析品質向上に貢献することを目的とします。
3. 注意点(代表例)
a)結合キーの重複によるレコード重複
データの前処理・分析において、さまざまなテーブルを結合しますが、その結合キーが重複していると意図しない重複行が発生してしまいます。 例えば、レセプトデータの分析では、さまざまなマスタテーブルを用意します(例:医薬品マスタ、疾患マスタ、検査值クレンジングマスタ)。これらは医療専門職の方に作成いただくことも多く、クレンジング用のマスタファイルは継ぎ足し継ぎ足しで作成されがちです。それらを使用する際は、必ずキーカラムがユニークであることを確認しましょう。
対策
- Python (pandas/polars): merge/joinの際にvalidate引数を必ず指定します。下記の例では、drug_masterのdrug_codeがユニークでない場合、エラーが発生します。
# pandas result = df_drug.merge(drug_master, on="drug_code", validate="m:1") # polars result = df_drug.join(drug_master, on="drug_code", validate="m:1")
- dbt (SQL): schema.ymlでuniqueテストを定義し、キーカラムのユニーク性を担保します。
# schema.yml models: - name: stg_drug_master columns: - name: drug_code tests: - unique - not_null
- CTEの途中はテストできないため、QUALIFYを使って強制的にユニークにするという手もあります。
select * from {{ ref('drug_master') }} qualify row_number() over (partition by drug_code order by updated_at desc) = 1
b)傷病名コードの先頭ゼロ欠落
傷病名コードは7桁ですが、先頭が0のコードがあります(例:胃腸炎:0091005)。 CSV形式のマスタファイルを取り扱う際は、特に注意が必要です(医薬品や診療行為のレセプト電算コードでは先頭0がないため、傷病名コード特有の問題です)。
対策
傷病名コードを取り扱う場合は、7桁であることを確認し、必要に応じて0埋めを行います。
- Python (pandas/polars)
# pandas df['disease_code'] = df['disease_code'].astype(str).str.zfill(7) # 7桁であることをテスト assert df['disease_code'].str.len().eq(7).all(), "全ての傷病名コードが7桁である必要があります" # polars df = df.with_columns( pl.col('disease_code').cast(pl.Utf8).str.zfill(7) ) # 7桁であることをテスト assert df.select(pl.col('disease_code').str.len_chars().eq(7)).to_series(0).all(), "全ての傷病名コードが7桁である必要があります"
- dbt (SQL)
select lpad(cast(disease_code as string), 7, '0') as disease_code from {{ ref('disease_data') }}
7桁のテスト
# schema.yml models: - name: disease_data_cleaned columns: - name: disease_code tests: - dbt_utils.expression_is_true: expression: "length(disease_code) = 7" description: "傷病名コード"
c)ICD-10-2の見落とし
傷病名の分類でよく使うICD-10ですが、実は1と2があります。ICD-10-1は基礎疾患の分類、ICD-10-2は症状発現の分類になります。 ICD-10-1のみを対象とすると、ICD-10-2に分類される傷病名が抽出できないため、注意が必要です。
対策
ICD-10-1、ICD-10-2両方のカラムを対象に抽出します。マスターテーブルを使ってdisease_codeに変換してから抽出すると便利です。
- Python (pandas/polars)
# pandas icd_pattern = r'^E1[14]' # 2型糖尿病関連コードの例(E11, E14) # マスターから対象のdisease_codeを取得 target_disease_codes = disease_master[ (disease_master['icd10_1'].str.contains(icd_pattern, na=False)) | (disease_master['icd10_2'].str.contains(icd_pattern, na=False)) ]['disease_code'].tolist() # 実データから抽出 df_filtered = df[df['disease_code'].isin(target_disease_codes)] # polars # マスターから対象のdisease_codeを取得 target_disease_codes = disease_master.filter( (pl.col('icd10_1').str.contains(icd_pattern)) | (pl.col('icd10_2').str.contains(icd_pattern)) ).select('disease_code').to_series().to_list() # 実データから抽出 df_filtered = df.filter(pl.col('disease_code').is_in(target_disease_codes))
- dbt(SQL)
with target_disease_codes as ( select distinct disease_code from {{ ref('disease_master') }} -- 2型糖尿病関連コードの例(E11, E14) where regexp_contains(icd10_1, r'^E1[14]') or regexp_contains(icd10_2, r'^E1[14]') ) select * from {{ ref('disease_data') }} as d inner join target_disease_codes as t on d.disease_code = t.disease_code
4. まとめ
レセプトデータ分析で注意すべき点は数多くありますが、本記事で取り上げた代表的な3つの例は以下の通りです:
- 結合キーの重複対策: merge/joinの際は必ずvalidate引数を指定し、dbtではuniqueテストを実装
- 傷病名コードの先頭ゼロ欠落対策: zfill()やlpad()で7桁に0埋めし、assertやdbt testsで検証
- ICD-10-2の見落とし対策: icd10_1, icd10_2両方のカラムを対象に正規表現で抽出
これらの対策例を参考に、各プロジェクトの特性に応じた品質担保の仕組みを構築することで、信頼性の高い分析結果を得ることができます。
なお、レセプトデータ分析における注意点は上記以外にも多数存在します。継続的にデータ品質の向上に取り組むことが重要です。
5. 採用情報
医療データ分析、ホワイトヘルスケアに興味を持たれた方はこちらへご連絡ください!