Extract the standard disease names from XML file of ICD10 by Python

国際疾病分類(ICD)とは、WHOによる疾患名の統一基準である。
(同じ病気なのに、みんなバラバラな用語を使うと情報が共有できないよ。標準名を作るからこれを使ってね。)

最新版はICD10(2014年版)。どうやらアメリカでは未だICD9が一般的に普及されており、2014年10月1日から全国的にICD9を廃止し、ICD10に切り替えるそうだ。
ちなみに日本では厚生労働省の指示のもとICD10(2003年版)が利用されている。

オンラインでICD10を検索できるのは以下のサイト。


実際検索してみると、同意義語の検索が弱い。イライラするくらいヒットしない。
マニュアルでやる労力を惜しむため、WHOからデータを取得し、自前のデータベースを作ることにした。


1. Get ICD10 (2010) From WHO




ページ中ほどにある「Classification Download Area」をクリックし、アカウントを作成する。
アカウント登録では、データの使い道や目的を英語で記入する。(一行ぐらいで問題なし)
使用言語を英語に設定してダウンロード。

取得できるファイルは5つ。
  • icd102010enMeta.zip
    • blocks.txt
    • chapters.txt
    • codes.txt
    • readme.txt
  • icdClaML2010ens.zip
    • icd102010en.xml

icd.102010.xmlの形式はこんな感じ。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ClaML SYSTEM "ClaML.dtd">
<ClaML version="2.0.0">
....
       <Class code="U89.9" kind="category">
                <Meta name="MortL4Code" value="UNDEF"/>
                ....
                <Rubric id="D0020989" kind="preferred">
                        <Label xml:lang="en" xml:space="default">
                        Agent resistant to unspecified antibiotic
                        </Label>
                </Rubric>
        </Class>
</ClaML>

赤字がタグ(tag)、青字が属性名(Attribute name)、緑字が属性値(Attribute value)
要素(Element)は開始タグ、テキスト、終了タグの全てを指す。

2. Pythonのxml.etree.ElementTreeを用いてテキストマイニング

参考


まずはパッケージとXMLデータのインポート。
from xml.etree import ElementTree
XMLFILE = "icd102010en.xml"
tree = ElementTree.parse(XMLFILE)
root = tree.getroot()

Classタグの属性を取得。
for e in root.findall('.//Class'):
 print e.attrib
# {'kind': 'category', 'code': 'U89.9'}
Classタグの属性名kindがcategoryの場合の属性を取得。
for e in root.findall(".//Class[@kind='category']"):
 print e.attrib
# {'kind': 'category', 'code': 'U89.9'}
Classタグの属性名codeの属性値を取得。
for e in root.findall('.//Class'):
 print e.get('code')
# U89.9
Class要素下のRubricタグの属性を取得。
for e in root.findall('.//Class'):
 print e.findall('.//Rubric').attrib
# {'kind': 'preferred', 'id': 'D0020989'}

Class要素下のLabel要素に囲まれたテキストを取得。
for e in root.findall('.//Class'):
print e.findtext('.//Label')
# Agent resistant to unspecified antibiotic

for e in root.findall('.//Class'):
 print e.findall('.//Label').text
# Agent resistant to unspecified antibiotic

3. 例外の確認

Class下に複数のRubricが存在し、RubricのLabel下にFragment要素が複数存在する場合。
<Class code="F90.0" kind="category">
<Meta name="MortBCode" value="119"/>
...
<Rubric id="D0004728" kind="preferred">
<Label xml:lang="en" xml:space="default">Disturbance of activity and attention</Label>
</Rubric>
<Rubric id="D0005181" kind="inclusion">
<Label xml:lang="en" xml:space="default">
<Fragment type="list">Attention deficit:</Fragment>
<Fragment type="list">disorder with hyperactivity</Fragment>
</Label>
</Rubric>
<Rubric id="D0005182" kind="inclusion">
<Label xml:lang="en" xml:space="default">
<Fragment type="list">Attention deficit:</Fragment>
<Fragment type="list">hyperactivity disorder</Fragment>
</Label>
</Rubric>
<Rubric id="D0005183" kind="inclusion">
<Label xml:lang="en" xml:space="default">
<Fragment type="list">Attention deficit:</Fragment>
<Fragment type="list">syndrome with hyperactivity</Fragment>
</Label>
</Rubric>
<Rubric id="id-to-be-added-later-1210506823253-1946" kind="exclusion">
<Label xml:lang="en" xml:space="default">hyperkinetic disorder associated with conduct disorder<Reference class="in brackets">F90.1</Reference></Label>
</Rubric>
</Class>

Rubric要素下にFragment要素が存在しない場合は、Label要素のテキストを取得し、Fragment要素が存在する場合はFragment要素のテキストを取得するよう条件分岐した。

Class codeが"F90.0"であるClass要素の属性、Rubric要素の属性、LabelあるいはFragmentのテキストを取得。
for c in root.findall(".//Class[@code='F90.0']"):
 code = c.get('code')
 kind = c.get('kind')
 for r in c.iter('Rubric'):
  id = r.get('id')
  rkind = r.get('kind')
  fex = r.find('.//Fragment')
  if fex is None:
   for l in r.iter('Label'):
    text = l.text
  else:
   text = ""
   for f in r.iter('Fragment'):
    text = text + ' ' + f.text
  print code, kind, id, rkind, text
# F90.0 category D0004728 preferred Disturbance of activity and attention
# F90.0 category D0005181 inclusion Attention deficit: disorder with hyperactivity
# F90.0 category D0005182 inclusion Attention deficit: hyperactivity disorder
# F90.0 category D0005183 inclusion Attention deficit: syndrome with hyperactivity
# F90.0 category id-to-be-added-later-1210506823253-1946 exclusion hyperkinetic disorder associated with conduct disorder

辞書形式のリストにするとこんな感じ。
out = []
for c in root.findall(".//Class[@code='F90.0']"):
 code = c.get('code')
 kind = c.get('kind')
 for r in c.iter('Rubric'):
  id = r.get('id')
  rkind = r.get('kind')
  fex = r.find('.//Fragment')
  if fex is None:
   for l in r.iter('Label'):
    text = l.text
  else:
   text = ""
   for f in r.iter('Fragment'):
    text = text + ' ' + f.text
  out.append({
   "ClassCode": code,
   "ClassKind": kind,
   "RubricID": id,
   "RubricKind": rkind,
   "Text": text
  })
# out[1]
# {'Text': 'Disturbance of activity and attention', 'RubricKind': 'preferred', 'RubricID': 'D0004728', 'ClassCode': 'F90.0', 'ClassKind': 'category'}

辞書のリストをcsv形式に書き出してみたところ、文字コードエラーが発生。
参考:How do I convert this list of dictionaries to a csv file? [Python]
import csv
keys = ['ClassCode', 'ClassKind', 'RubricID', 'RubricKind', 'Text']
f = open('output.csv', 'wb')
dict_writer = csv.DictWriter(f, keys)
dict_writer.writer.writerow(keys)
dict_writer.writerows(out)
# Traceback (most recent call last):
#  File "<stdin>", line 1, in <module>
#  File "/usr/local/lib/python2.7/csv.py", line 154, in writerows
#    return self.writer.writerows(rows)
# UnicodeEncodeError: 'ascii' codec can't encode character u'\xfc' in position 15: ordinal not in range(128)

もしテキストがunicodeだったらutf-8に変換することにした。
参考:PythonのUnicodeEncodeErrorを知る
out = []
for c in root.findall(".//Class[@kind='category']"):
 code = c.get('code')
 kind = c.get('kind')
 for r in c.iter('Rubric'):
  id = r.get('id')
  rkind = r.get('kind')
  fex = r.find('.//Fragment')
  if fex is None:
   for l in r.iter('Label'):
    if isinstance(l.text, unicode):
     term = l.text.encode('utf-8')
    else:
     term = l.text
  else:
   term = ""
   for f in r.iter('Fragment'):
    if isinstance(f.text, unicode):
     term = term + ' ' + f.text.encode('utf-8')
    else:
     term = term + ' ' + f.text
  out.append({
   "ClassCode": code,
   "ClassKind": kind,
   "RubricID": id,
   "RubricKind": rkind,
   "Text": term
  })


Label要素下にPara要素が含まれるケースがあるため、条件分岐を3つに変更。
 out = []
for c in root.findall(".//Class[@kind='category']"):
 code = c.get('code')
 kind = c.get('kind')
 for r in c.iter('Rubric'):
  id = r.get('id')
  rkind = r.get('kind')
  fex = r.find('.//Fragment')
  pex = r.find('.//Para')
  if fex is not None:
   term = ""
   for f in r.iter('Fragment'):
    if isinstance(f.text, unicode):
     term = term + ' ' + f.text.encode('utf-8')
    else:
     term = term + ' ' + f.text
  elif pex is not None:
   for p in r.iter('Para'):
    if isinstance(p.text, unicode):
     term = p.text.encode('utf-8')
    else:
     term = p.text
  else:
   for l in r.iter('Label'):
    if isinstance(l.text, unicode):
     term = l.text.encode('utf-8')
    else:
     term = l.text
  out.append({
   "ClassCode": code,
   "ClassKind": kind,
   "RubricID": id,
   "RubricKind": rkind,
   "Text": term
  })

csv形式で問題なく書き出せた。
import csv
keys = ['ClassCode', 'ClassKind', 'RubricID', 'RubricKind', 'Text']
f = open('output.csv', 'wb')
dict_writer = csv.DictWriter(f, keys)
dict_writer.writer.writerow(keys)
dict_writer.writerows(out)

# cat output.csv | head -5
# ClassCode,ClassKind,RubricID,RubricKind,Text
# A00,category,D0000003,preferred,Cholera
# A00.0,category,D0000004,preferred,"Cholera due to Vibrio cholerae 01, biovar cholerae"
# A00.0,category,D0000934,inclusion,Classical cholera
# A00.1,category,D0000005,preferred,"Cholera due to Vibrio cholerae 01, biovar eltor"

データベースにするとこんな感じ。
ClassCodeClassKindRubricIDRubricKindText
A00categoryD0000003preferredCholera
A00.0categoryD0000004preferredCholera due to Vibrio cholerae 01, biovar cholerae
A00.0categoryD0000934inclusionClassical cholera
A00.1categoryD0000005preferredCholera due to Vibrio cholerae 01, biovar eltor

Share on Google Plus

About Piyoko

    Blogger Comment
    Facebook Comment

0 コメント:

コメントを投稿