Pymetamapで英語電子カルテからUMLS conceptを抽出する
はじめに
NIHが提供しているMetamapというツールを使うと英語テキストから病名,薬剤名,解剖学的部位などを抽出することができます.
Metamapは本来Javaで書かれており,Pythonでデータ分析をする際に扱いづらかったのですが,
世の中には親切な方がいるもので,PymetamapというPythonラッパーが作られていました.
そこで今回はPymetamapを使って英語電子カルテから病名抽出を行いたいと思います.
動作環境
- Python v3.6.4
- nltk v3.6.2
準備
Metamapはすでにインストールされているものとします.詳しくはこちらを参照してください.
PymetamapはこちらのGitHubリポジトリをCloneして入手します.
cd ~ git clone git@github.com:AnthonyMRios/pymetamap.git
注: pip install pymetamap
でインストールしないでください.PyPIで提供されているものはバージョンが古く,Metamapの一部のオプションに対応していません.
それでも普通に使うぶんには問題ないのですが,高度な使い方をしようとするとエラーが生じる場合があります.
本題
まずはMetamapを動かすための仮想サーバーを起動しておきます.
cd /dir/to/metamap ./bin/skrmedpostctl start >>> Starting skrmedpostctl: >>> started.
次に対象とする英語電子カルテを用意し,文単位に区切っておきます.
import nltk import re import pymetamap from typing import Dict, Tuple, List from pymetamap import MetaMap text = 'This is a 62 year-old woman with a history of with significant past medical history of diabtes mellitus type 2, hypertension, hyperlipidemia, CAD s/p CABG who comes with three weeks of shortness of breath and dyspnea on exertion.' tokenizer = nltk.tokenize.punkt.PunktSentenceTokenizer() sentences = tokenizer.sentences_from_text(text)
Metamapのインスタンスを読み込みます.
# XX にはMetamapのバージョン番号を入れる.2020年版であれば path = '(中略)/bin/metamap20' path = '/path/to/metamap/bin/metamapXX' mm = MetaMap.get_instance(path)
すると簡単にUMLS conceptが抽出できます.
concepts, error = mm.extract_concepts(sentences, range(len(sentences))) print(concepts) >>> [ConceptMMI(index='0', mm='MMI', score='7.02', preferred_name='Medical History', cui='C0262926', semtypes='[fndg]', trigger='["History"-tx-1-"history"-noun-0,"History of MEDICAL"-tx-1-"medical history of"-noun-0]', location='TX', pos_info='36/7;69/18', tree_codes=''), ConceptMMI(index='0', mm='MMI', score='5.18', preferred_name='Dyspnea on exertion', cui='C0231807', semtypes='[sosy]', trigger='["Dyspnoea on exertion"-tx-1-"dyspnea on exertion"-noun-0]', location='TX', pos_info='210/19', tree_codes=''), ConceptMMI(index='0', mm='MMI', score='5.18', preferred_name='Hyperlipidemia', cui='C0020473', semtypes='[dsyn]', trigger='["Hyperlipidaemia, NOS"-tx-1-"hyperlipidemia"-noun-0]', location='TX', pos_info='127/14', tree_codes=''), ConceptMMI(index='0', mm='MMI', score='5.18', preferred_name='Hyperlipidemia, CTCAE', cui='C4555212', semtypes='[fndg]', trigger='["Hyperlipidemia"-tx-1-"hyperlipidemia"-noun-0]', location='TX', pos_info='127/14', tree_codes=''), ...
この出力結果からでは,テキスト中のどの語がUMLS conceptとして認識されたのか直接はわかりません. そこでもう少し分かりやすく見ていきましょう.
def convert_concepts_to_superficial_concept_pair( concepts: List[pymetamap.Concept.ConceptMMI], sentences: List[str] ) -> List[Dict]: entities = [] for concept in concepts: sentence_id = int(concept.index) # テキスト中の位置が文字数ベースで示されている (1-indexedであることに注意) raw_pos_info: List[Tuple[str, str]] = re.findall(r'(\d+)/(\d+)', concept.pos_info) positions = [{'start' : int(pos_info[0]) - 1, 'end' : int(pos_info[0]) + int(pos_info[1]) - 1} for pos_info in raw_pos_info] superficials = [sentences[sentence_id][pos['start']:pos['end']] for pos in positions] # それぞれの UMLS concept が参照している表出形を "superficials" キーに入れる entities.append({'superficials':superficials, 'concept':concept}) return entities entities = convert_concepts_to_superficial_concept_pair( concepts, sentences )
それぞれの UMLS concept が文中のどの単語に該当しているかを辞書のリストとして格納しました.
print(entities[0]) >>> {'superficials': ['history', 'medical history of'], 'concept': ConceptMMI(index='0', mm='MMI', score='7.02', preferred_name='Medical History', cui='C0262926', semtypes='[fndg]', trigger='["History"-tx-1-"history"-noun-0,"History of MEDICAL"-tx-1-"medical history of"-noun-0]', location='TX', pos_info='36/7;69/18', tree_codes='')}
ちょっと情報量が多いので,表出形,semantic type,標準形,CUI (Concept Unique Identifier)だけを抜き出して表示してみましょう.
それぞれの UMLS concept は pymetamap.Concept.ConceptMMI
というオブジェクトとして与えられるため,各属性にも簡単にアクセスすることができます.
for entity in entities: superficials = entity['superficials'] concept = entity['concept'] print(f'{superficials}\t{concept.semtypes}\t{concept.preferred_name}\t{concept.cui}') >>> ['history', 'medical history of'] [fndg] Medical History C0262926 ['dyspnea on exertion'] [sosy] Dyspnea on exertion C0231807 ['hyperlipidemia'] [dsyn] Hyperlipidemia C0020473 ['hyperlipidemia'] [fndg] Hyperlipidemia, CTCAE C4555212 ['hypertension'] [fndg] Hypertension, CTCAE C1963138 ['hypertension'] [dsyn] Hypertensive disease C0020538 ['hyperlipidemia'] [fndg] Serum lipids high (finding) C0428465 ['CABG'] [topp] Coronary Artery Bypass Surgery C0010055 ['of shortness', 'breath'] [sosy] Dyspnea C0013404 ...