Pubmedの.xmlデータをpythonで処理する[その2]

#はじめに
この記事は自分用のメモです。が、向上のためのご意見/アドバイスなど頂けますと幸いです。

前回の記事ではxml形式のデータを処理するためのライブラリーの使い方を理解しました。今回の記事では、Pubmedの論文データを処理するためのラッパークラスを作成します。

ラッパークラスでは、各論文データについて、pubmed id、doi、出版年、タイトルなどの基本情報を抽出できるようにします。またこれに加えて、
1. 著者の総数、
2. コレスポンディングオーサーのリスト(複数いれば、全員)
3. ある著者が、何番目の著者であるか、
などを返すことができるようにします。

論文のコレスポンディングオーサーが誰であるかについて、pubmedの論文データに含まれていないことも多いようですが、論文のコレスポンディングオーサーが誰であるかは重要な情報ですので、なるべく丁寧に処理するつもりです。

また、co-firstとか、co-lastとか、'equality'についての情報も、拾えるようにするつもりです。

#コレスポンディングオーサーの処理
コレスポンディングオーサーが誰であるかはpubmdeデータに明示的には書かれていません。つまり、データをよく調べて誰がコレスポンディングオーサーであるか、判定する必要があります。以下のように判定することにしました。

なお、Authorには人を表すものと、研究グループを表すものがありますが、特定の個人がその論文のコレスポンディングオーサーであるかを調べられるようにしたいと思いますので、「人であるAuthor(human author)」以外は考えないことにします。

判定フロー: (各項目で、確定できない場合、下の項目で判定)
1. 著者が一人しかいない場合、その人はコレスポンディングオーサー。
2. 著者の所属情報(Author->AffiliationInfo->Affiliationのテキスト)にメールアドレスがある場合は、その人はコレスポンディングオーサー。
3. 著者が複数いて、そのうちの一人にだけ所属情報がある場合、その著者がコレスポンディングオーサー(すべての著者は同じ所属であるとみなす)。
4. コレスポンディングオーサーがだれであるかわからないケース

つまり4は、著者が複数いて、誰にもメールアドレス情報がなく、著者の複数(全員かもしれない)に所属情報があるケースです。基本的にはpubmedのページからリンクされている論文のページに飛べば、だれがコレスポンディングオーサーかはっきりしますが、ここではこれを追いかけることはしません。
#'equality'についての情報処理
[Pubmedのxmlについての記述](https://www.ncbi.nlm.nih.gov/books/NBK3828/) を読むと、equal contributionを示すには、EqualContribにYをつけろ、と書いてあります。つまり、
\<Author EqualContrib="Y">
としろ、と例があげてあります。

調べてみると[著者一人にだけEqualContrib="Y"が付いている例](https://www.ncbi.nlm.nih.gov/pubmed/27733330?report=xml&format=text)もあるようでしたが、このような問題に加えて、所属情報のところに、equal contributionについての記述がある例がかなりあります。

```xml
<Author>
    <AffiliationInfo>
        <Affiliation>### ここに思い思いの書き方で書かれている ###</Affiliation>
    </AffiliationInfo>
</Author>

例: Equalを含むもの: 
Equal authorship.
Joint last author with equal contributions.
Equal contribution.
Equal contribution as senior author.
A.P. and A.J. are the equal senior authors.
Contribute equal to this work.
Co-first authors with equal contribution.
Equal contributor.
These authors are joint senior authors with equal contribution.
Joint last author with equal contributions.
* Equal contributors.

例: Equallyを含むもの
These authors contributed equally to this article.
Both authors contributed equally.
These authors contributed equally to this work.
Contributed equally.
These authors contributed equally and are co-first authors.
```
equal contributionとは関係ない著者のところに、Equalityについて書いてあるケースもあり、
例 [32281472](https://www.ncbi.nlm.nih.gov/pubmed/?term=32281472)
所属名に、"Equal"が含まれるケースもある。
Foundation for Research on **Equal** Opportunity
Social and Health **Inequalities** Network Pakistan
Center for Health Policy and **Inequalities** Research

内容を読み取って処理するには、記述の幅が広すぎて手に負えません。そこで各著者について、
1. \<Author EqualContrib="Y">の記述があれば1
2. \<Affiliation>にequal、equallyの記述があれば2
3.  どちらもなければ0

として、intのリストとしてしておくことにしました(両方の記述があるものは、63,686件調べて1つもありませんでしたので、無いものとしました。もしあれば処理上1になります。)、この分け方については、実際にどのようなパターンがあるかと共に、後に議論します。

#クラスのデザイン方針
Pubmedデータについて、タグが以下の3つ

1.PubmedArticle、
2.Author、
3.Affiliation

であるものについて、これを扱うためのクラスをそれぞれ作成します。クラス名は、**PubmedArticle**、**PubmedAuthor**、**PubmedAffiliation**とし、それぞれ対応するElementTreeオブジェクトを保持させます。

これらクラスはElementオブジェクトをそのまま保持するラッパークラスです。カプセル化して色々簡単に調べられるようにします。PubmedArticleオブジェクトは、PubmedAuthorオブジェクトへの参照を持ち、PubmedAuthorオブジェクトはPubmedAffiliationオブジェクトへの参照を持ちます。メソッドの呼び出しも、この流れに添い、逆行はさせないようにします。ここでは、下流のクラスから記述します。

#どこまでメソッドを用意するのか？
上記3つのクラスを作って、以下のようにメソッドをあれこれ定義しましたが、そもそもどこまでメソッド作ればよいのでしょうか？

そもそも、ユーザーが、pubmedのデータ形式などについて、十分詳しければそもそもラッパークラスは必要ないわけですが、全くpubmedのデータ形式とか、データのパターンについて知らなくても、これらクラスを使って十分に処理が可能か、というとちょっと疑問です。これは、データの書かれ方がpubmedレコードごとに異なっているなどして、一律に処理できないところが多数あるからです(例、出版年の書かれ方、コレスポンディングオーサーが判然としないケースが多いこと、equality情報の与えられ方が様々であること)。

となるとpubmedのデータがどんなものであるか、ある程度知っているユーザーにとって、便利であるようなクラスを目指すということになると思います。

#PubmedAffiliationクラス
一人の著者が複数のPubmedAffiliationを持っていることがあることに注意が必要です。
以下の番号はコード中のメソッドにつけた番号と対応。
1.初期化メソッド。xmlElementオブジェクトを受け取ります。
2.所属情報にメールアドレスを含むか判別するメソッド、
3.所属情報を返すメソッド、
4.所属情報に、指定したリストに含まれる文字列を全て含むかどうか調べるメソッド、
を用意しました。

```py
import xml.etree.ElementTree as ET
import re

class PubmedAffiliation():

    E_MAIL_PATTERN = re.compile(r'[0-9a-z_./?-]+@([0-9a-z-]+\.)+[0-9a-z-]+')

#1 初期化メソッド
    def __init__(self, xmlElement):
        self.xml = xmlElement
        
#2 所属にメールアドレスが含まれるか: bool
#参考: 所属にメールアドレスが含まれていればコレスポンディングオーサーとみなせるが、古い文献ではメールアドレスが記載されていないことがあるので注意
    def hasAnyMailAddress(self,):
        affiliation = self.xml.text
        result = self.E_MAIL_PATTERN.search(affiliation)
        if result is not None:
            return True
        return False     

#3 所属情報をテキストで返す: str
    def affiliation(self,):
        if self.xml is not None:
            return self.xml.text
        return "-"

#4 所属が指定のリスト(words)に含まれるwordsを全て含んでいるか: bool
    def isAffiliatedTo(self,words):#全部含んでいればTure
        for word in words:
            if not word in self.affiliation():
                return False
        return True
```

#PubmedAuthorクラス
以下の番号はコード中のメソッドにつけた番号と対応。
1. 初期化メソッド。xmlElementを受け取ります。
2. メールアドレスを含む所属情報があるか調べるメソッド、
3. last nameを返すメソッド、
4. fore name (First name)を返すメソッド、
5. イニシャルを返すメソッド、
6. 所属情報(PubmedAffiliationオブジェクト)のリストを返すメソッド
7. 所属情報に、指定の文字列リストに含まれる文字列を全て含むものがあるかどうかを調べるメソッド
を準備しました。

初期化時にNoneをセットしている変数singleCommonAffiは、PubmedArticleオブジェクトの初期化時に、必要に応じてセットされます(pubmedデータによっては、著者一人にだけ所属情報がある場合があり、そのような場合には、この所属情報を著者全員の共通の所属と見なすことにしました)。

```py

class PubmedAuthor():

#1 初期化メソッド
    def __init__(self, xmlElement):
        self.xml = xmlElement
        self.singleCommonAffi = None

#2 メールアドレスが所属に記載されているどうか？: bool    
    def withAnyMailAddress(self,):#コレスポンディングオーサーか？
        for x in self.xml.findall('AffiliationInfo/Affiliation'):#所属
            pubmedAffiliation = PubmedAffiliation(x)
            if pubmedAffiliation.hasAnyMailAddress():
                return True
        return False

#3 last nameを返す: str    
    def lastName(self,):
        x = self.xml.find('LastName')
        if x is not None:
            return x.text
        return "-"

#4 fore nameを返す: str    
    def foreName(self,):
        x = self.xml.find('ForeName')
        if x is not None:
            return x.text
        return "-"

#5 イニシャルを返す: str    
    def initials(self,):
        x = self.xml.find('Initials')
        if x is not None:
            return x.text
        return "-"

#6 この著者に関する所属(PubmedAffiliationオブジェクト)を全部含むリスト: list
    def affiliations(self,):
        x = []
        for y in self.xml.findall('AffiliationInfo/Affiliation'):#所属
            x.append(PubmedAffiliation(y))
        return x

#7 所属情報に、listで指定した単語が全て含まれているかどうか？: bool
    def isAffiliatedTo(self,words):
        for x in self.xml.findall('AffiliationInfo/Affiliation'):#所属
            pubmedAffiliation = PubmedAffiliation(x)
            if pubmedAffiliation.isAffiliatedTo(words):
                return True
        #singleCommonAffiがなければ、これ以上調べない        
        if self.singleCommonAffi is None
            return False

        #singleCommonAffiについて調べる。指定wordsが全部あればTrue
        for word in words:
            if not word in self.singleCommonAffi:
                return False        
        return True
```

#PubmedArticleクラス
初期化時に、xmlElementオブジェクトを受け取り、以下の項目について調べます。
1.human author(PubmedAuthorオブジェクト)のリスト、
2.コレスポンディングオーサー(PubmedAuthorオブジェクト)のリスト、
3.各著者について、equalityについての情報があるかどうか調べたリスト、
4.equlaityについての情報のリスト

多数のメソッドを持ちます。番号はコード中のメソッドにつけた番号と対応。
1. co-authorshipについての情報:str
2. reviewかどうか: bool
3. Erratum (訂正記事)であるかどうか: bool
4. 出版タイプ: str
5. ドキュメント識別子(doi): str
6. pubmed id(pmid): str
7. タイトル: str
8. ジャーナル名: str
9. 出版年: str
10. 出版月: str
11. 記述言語: str
12. foreNameとLastNameのタプルで指定した人物が、何番目の著者かを調べる:int
13. foreNameとLastNameのタプルで指定した人物が、指定した著者リストに含まれるか返す: bool
14. foreNameとLastNameのタプルで指定した人物が、この論文の著者か?
15. コレスポンディングオーサーが明らかになっているか?: bool
16. foreNameとLastNameのタプルで指定した人物が、コレスポンディングオーサーか?: bool


```py
class PubmedArticle():

#0 初期化メソッド
    def __init__(self, xmlElement):
        self.xml = xmlElement
        self.humanAuthors = []
        self.corespondingAuthors = []
        self.collectiveNames = []   #グループ名が著者として含まれているケースがある。non-human author
        self.singleCommonAffi = None #
        self.equalityStatements = [] #equaltityについての記述
        self.authorStates = []

        #authorStatesは、各humanAuthorについて、
        # 0: 記述なし
        # 1: EqualContrib = Y　の記述がある。
        # 2: Affiliationにequality関連の記述がある。
        # として著者ごとに0、１、2とする。
        # 著者全体を考えると、いくつかパターンがある
        #パターン1: 全部1....全員co-firstでco-last
        #パターン2: 前から2つあるいは3つが1....co-1st
        #パターン3: 後ろから2つが1.....co-last
        #パターン4: 先頭の1つが2...何かequalityについて記述がある。読まなきゃわからん。この記述はequalityStatementsに保持。
        #パターン5: その他

        # humanオーサーを集める。
        for x in self.xml.findall('MedlineCitation/Article/AuthorList/Author'):
            pubmedAuthor = PubmedAuthor(x)
            if x.find('CollectiveName') is not None:#<Author>グループ名とかが書いてあるケースがある。著者には含めずに、別に管理する。
                self.collectiveNames.append(pubmedAuthor)
            else :
                self.humanAuthors.append(pubmedAuthor)
        
        # コレスポンディングオーサーを集める。(ついでに、所属情報のある著者が一人の場合は、その所属を調べる)。
        if len(self.humanAuthors) == 1:#オーサーが一人の場合。その人はコレスポンディングオーサー。
            self.corespondingAuthors.append(self.humanAuthors[0])
        else:
            for author in self.humanAuthors:
                if author.withAnyMailAddress():#メールアドレスがaffiliationに書かれていればコレスポンディングオーサー
                    self.corespondingAuthors.append(author)
            if len(self.corespondingAuthors) == 0:
                pubmedAffiliations = []
                humanAuthorsWithAffiliation =[]
                for author in self.humanAuthors:
                    x =  author.xml.find('AffiliationInfo/Affiliation')
                    if x is not None:#所属情報あり
                        humanAuthorsWithAffiliation.append(author)
                        pubmedAffiliations.append(PubmedAffiliation(x))
                        
                if (len(humanAuthorsWithAffiliation) == 1):
                    self.corespondingAuthors.append(humanAuthorsWithAffiliation[0])
                    self.singleCommonAffi = pubmedAffiliations[0]
                    #全部のオーサーにこの情報を持たせる
                    for author in self.humanAuthors:
                        author.singleCommonAffi = self.singleCommonAffi
        
        #文献に、co-firstあるいはco-lastについての情報(equaltityについての情報)が含まれるかどうか判断
        for author in self.humanAuthors:
            state = 0
            if 'EqualContrib' in author.xml.attrib:
                if author.xml.attrib['EqualContrib'] == 'Y':
                    state = 1
            else :
                for x in author.xml.findall('AffiliationInfo/Affiliation'):
                    if ' equal ' in x.text or 'Equal ' in x.text or ' equally ' in x.text or 'Equally ' in x.text:
                        state = 2
                        self.equalityStatements.append(x.text)
                        break
            self.authorStates.append(state)

#1 コオーサーシップについての情報を返します。
    def coauthorshipInfo(self,):
        if all(map(lambda x: x == 1,self.authorStates)):#全部1
            return "All authors are equal contributors."
        if any(map(lambda x: x == 2,self.authorStates)):#少なくとも1つが2
            return "Specific descriptions on co-authorship."
        if self.authorStates[0] == 1 and self.authorStates[-1] == 1:#最初と最後が1
            return "co-first and co-last authorships are described."
        if self.authorStates[0] == 1:#最初が1
            count = 0
            for x in self.authorStates:
                if x == 1:
                    count += 1
                else:
                    break
            return "co-first authorship is described. " + str(count) + " co-first authors"
        if self.authorStates[-1] == 1:#最後が1
            count = 0
            for x in reversed(self.authorStates):
                if x == 1:
                    count += 1
                else:
                    break
            return "co-last authorship is described." + str(count) + " co-last authors"
        return None

#2 reviewかどうか : bool値
    def isReview(self,):
        for x in self.xml.findall('MedlineCitation/Article/PublicationTypeList/PublicationType'):
            if (x.text == 'Review'):
                return True
        return False

#3 訂正記事かどうか : bool値
    def isErratum(self,):
        for x in self.xml.findall('MedlineCitation/Article/PublicationTypeList/PublicationType'):
            if (x.text == 'Published Erratum'):
                return True
        return False

#4 出版タイプ
    def PublicationType(self,):
        for x in self.xml.findall('MedlineCitation/Article/PublicationTypeList/PublicationType'):
            if x.text is not None:
                return x.text
        return "-"

#5 ドキュメント識別子(doi): str
    def doi(self,):
        for x in self.xml.findall('MedlineCitation/Article/ELocationID'):
            if(x.get('EIdType') == 'doi'):
                return x.text
        return "-"

#6 pubmed id(pmid): str
    def pmid(self,):
        element = self.xml.find('MedlineCitation/PMID')
        if element is not None:
            return element.text
        else:
            return "-"

#7 タイトル: str
    def title(self,):
        element = self.xml.find('MedlineCitation/Article/ArticleTitle')
        if element is not None:
            return element.text
        else:
            return "-"

#8 ジャーナル名: str
    def journal(self,):
        element = self.xml.find('MedlineCitation/Article/Journal/Title')
        if element is not None:
            return element.text
        else:
            return "-"

#9 出版年: str
#参考: <MedlineDate>に"2019 Mar - Apr"と書いてあるケースがある。
#参考: <MedlineDate>に"2012-2013"と書いてあるケースがある。
    def year(self,flag="all"):
        element = self.xml.find('MedlineCitation/Article/Journal/JournalIssue/PubDate/Year')
        if element is not None:
            return element.text
        else:
            element = self.xml.find('MedlineCitation/Article/Journal/JournalIssue/PubDate/MedlineDate')
            if element is not None:
                if flag == "all":#デフォルトでは文字列を全部返す
                    return element.text
                else:#それ以外では、最初の4桁の年を返す
                    m = re.search('(\d{4})',element.text)
                    if m is not None:
                        return m.group(0)
                    else:
                        return "0"
            return "0"

#10 出版月: str
    def month(self,):
        element = self.xml.find('MedlineCitation/Article/Journal/JournalIssue/PubDate/Month')
        if element is not None:
            return element.text
        else:
            element = self.xml.find('MedlineCitation/Article/Journal/JournalIssue/PubDate/MedlineDate')
            if element is not None:
                return element.text.split(' ')[1]
            return "-"

#11 記述言語
    def language(self,):
        element = self.xml.find('MedlineCitation/Article/Language')
        if element is not None:
            return element.text
        else:
            return "-"



#################################################################################
##########   著者の氏名(タプル)で問い合わせる。
#################################################################################
#著者が何番目の著者かを調べる(オーサーでなければ0):int。
#12 queryは、foreNameとLastNameのタプル
    def positionInAuthors(self,query):# 1stオーサーなら返り値は1 (0ではない)。queryはタプル (ForeName, LastName) 
        for x in range( len(self.humanAuthors) ):
            if self.humanAuthors[x].foreName() == query[0] and self.humanAuthors[x].lastName() == query[1]:
                return x + 1
            if self.humanAuthors[x].initials() == query[0] and self.humanAuthors[x].lastName() == query[1]:
                return x + 1
        return 0            

#13 著者が、指定した著者リストに含まれるか返す: bool
#指定著者リストは、例えばコレスポンディングオーサーのリスト
    def isAuthorIn(self,query,authors):# 名前_苗字が、指定したAuthorsに含まれているか返す。queryはタプル
        for author in authors:
            if ( author.foreName() == query[0] and author.lastName() == query[1]):
                return True
            if ( author.initials() == query[0] and author.lastName() == query[1]):
                return True
        return False

#14 タプルで指定した著者が、authorか調べる: bool
    def isAuthor(self,query):
        for author in self.humanAuthors:
            if author.foreName == query[0] and author.lastName == query[1]:
                return True
            if author.initials == query[0] and author.lastName == query[1]:
                return True
        return False

#15 コレスポンディングオーサーが明らかになっているか調べる: bool
    def isCorrespondingAuthorDefined(self,):
        if len(self.corespondingAuthors) == 0:
            return False
        else:
            return True

#16 タプルで指定した著者が、コレスポンディングオーサーか調べる: bool
    def isCorrespondingAuthor(self,query):
        for author in self.corespondingAuthors:
            if ( author.foreName() == query[0] and author.lastName() == query[1]):
                return True
            if ( author.initials() == query[0] and author.lastName() == query[1]):
                return True
        return False
```


# 実際に使ってみる
データを読み込んでみます。pubmed_result.xmlは、pubmedのページからダウンロードしてきたxml形式のデータファイルです。データファイルには複数のPubmedレコードが含まれており、ここでは全体を読み込んで変数rootにエレメントツリーを格納します。


```py
test_data = open("/Users/yoho/Downloads/pubmed_result.xml", "r")
contents = test_data.read()
root = ET.fromstring(contents)

```

基本情報へのアクセス方法:

```py
for pubmedArticleElement in root.findall('PubmedArticle'):
    p = PubmedArticle(pubmedArticleElement)#レコード 1件をPubmedArticleオブジェクトにする
    
    print(
        p.pmid(),# pubmed id
        p.doi(),# doi (ドキュメント識別子)
        p.year(flag=1),# 出版年。Year情報のみ。全てならflag = "all"
        p.month(),# 出版月
        p.title(),# 論文タイトル
        p.language(),# 言語
        p.PublicationType(),# 出版タイプ
        sep = "\t",end="\n")
```
基本情報以外へのアクセス方法:

```py
for pubmedArticleElement in root.findall('PubmedArticle'):
    p = PubmedArticle(pubmedArticleElement)#レコード 1件をPubmedArticleオブジェクトにする
    
    # humanAuthorの数
    print (str(p.numberOfAuthors()))

    #著者氏名へのアクセス
    for x in p.humanAuthors:
        print(
            x.foreName(), # First Name
            x.lastName(), # Last Name
            sep="\t",end="\t")
    print("")

    #コレスポンディングオーサーがidentifyできているかどうか調べる
    if len(p.corespondingAuthors) != 0:
        print("コレスポンディングオーサーがpubmed情報からわかる",end="\t")
    else :
        print("コレスポンディングオーサーが誰であるかはpubmed情報からはわからない",end="\t")

    #コレスポンディングオーサーへのアクセス
    if len(p.corespondingAuthors) == 0:
        print("コレスポンディングオーサーが誰であるかはpubmed情報からは不明",end="\t")
    else:
        print("コレスポンディングオーサーの数 :"+str(len(p.corespondingAuthors)),end="\t")
        for x in p.corespondingAuthors:
           print(
            x.foreName(), # First Name
            x.lastName(), # Last Name
            sep=" ",end="\t")
    
    #コレスポンディングオーサーであるか、タプルでFirst NameとLast Nameを指定して調べる
    author = ("Taro","Tohoku")

    if p.isAuthorIn(author,p.corespondingAuthors):
        print(author[0] + " " + author[1] + "はこの論文のコレスポンディングオーサーです。",end="\t")
    else :
        print(author[0] + " " + author[1] + "はこの論文のコレスポンディングオーサーではありません。",end="\t")

    #オーサーであるか、タプルでFirst NameとLast Nameを指定して調べる
    if p.isAuthor(author):
        print(author[0] + " " + author[1] + "はこの論文のオーサーです。",end="\t")
    else:
        print(author[0] + " " + author[1] + "はこの論文のオーサーではありません。",end="\t")
       
    #何番目の著者か、タプルでFirst NameとLast Nameを指定して調べる
    position = p.positionInAuthors(author)
    if position != 0:
        print(str(position) + "番目のオーサー",end="\t")
    else: 
        print(author[0] + " " + author[1] + "はオーサーではない",end="\t")
    

```

#co-authorshipについての記述パターン
ここではタイトルにAIDSが含まれる全pubmedデータについて解析してみました。equalityについてはintのリスト「authorStates」で参照できます。レコード件数は63,686件です(ファイルサイズ500 MB)。

```py

for pubmedArticleElement in root.findall('PubmedArticle'):
    p = PubmedArticle(pubmedArticleElement)

    if any(p.authorStates):
        print(p.pmid(),end="\t")
        print("".join(map(str,p.authorStates)),end="\n")
        if p.authorStates[0] == 2:#2の時は、co-authorshipについて何らかの記述がある。
            pass #省略 print(" ".join(p.equalityStatements),end="\t")
# 出力
# pumed id      co-autorshipの記述状態(1著者1 digit)
# 32209633	000000011
# 30914431	110000000000
# 30912750	100
# 30828360	11000000000000
# 30421884	1100
# 30467102	10000
# 30356992	1100000000
# 29563205	1100000011
# 29728344	111111111
# 29588307	110000000000
# 29254269	110000000000
# 27733330	10
# 26990633	200000000
# 26949197	111000000000000
# 26595543	200000000000
# 26825036	20000000000000
# 26691548	20000
# 26397046	01110000
# 26535055	110
# 26544576	2000000000000
# 26173930	110000000011
# 26166108	20000000000
# 26125144	20000
# 25949269	1111111
# 24906111	20000000
# 24401642	200
# 22350831	110000000000000
# 22192455	11000
# 22098625	1110
# 21129197	11
# 20540714	11
```
全員に1が振られているケース、先頭のみが2であるケース、最初二人と最後二人に1があるケース、などいろいろあるようです。1は規約通りに書かれているということなので採用したいところですが、先頭だけ1であるケース(誰とequal?)、先頭は0で、二人目三人目が1であるケース(データの不備？)などいろいろあるようです。

2が振られているケースには、様々なタイプの記述があり、いちいち記述を読まないと意味するところが分かりません。そこで「必要に応じて、このintのリストを参照してどのような論文なのか判定」すればよく、2の情報は、テキストで参照できれば良いと考えることにしました。

#ある研究機関全体について調べる
ある研究機関全体について、誰がどれくらい論文を書いているか調べたい時があると思います。pubmedで研究機関を指定して検索を行い、得られたxmlを解析するような場合です。まず「FirstNameとLastNameのタプル」をkey、「PubmedArticleオブジェクトを格納したリスト」をvalueとする辞書を作ります。

```py
#辞書の作成
authorAndArticle = {}#辞書
for pubmedArticleElement in root.findall('PubmedArticle'):
    p = PubmedArticle(pubmedArticleElement)

    for author in p.humanAuthors:
        if author.isAffiliatedTo(['Graduate School','Sciences']):
            authorFullName = (author.foreName(),author.lastName()) #タプルをkeyにする
            if authorFullName in authorAndArticle:#辞書にすでにkeyがある場合
                authorAndArticle[authorFullName].append(p)
            else:#辞書にkeyがまだない場合
                authorAndArticle[authorFullName] = [p]

```
人ごとにデータを出力します。

```py
for authorFN in authorAndArticle:
    pubmedArticles = authorAndArticle[authorFN]
    print(authorFN[0] + " " + authorFN[1])
    for pma in pubmedArticles:
        
        print('            ',end= '')

        # ジャーナル情報
        print(pma.pmid(), pma.year(), pma.journal(), sep="\t",end='\t')

        # co-authorshipの判定具合
        print(pma.coauthorshipInfo(),end='\t')
        
        # co-authorship状態情報。エクセル上で文字列として扱われるように先頭に 'を追加
        print("'" + "".join(map(str,pma.authorStates)),end="\t")

        #何番目の著者か
        print(str(pma.positionInAuthors(authorFN)),end='\t')
        
        # 著者の数
        print(str(len(pma.humanAuthors)),end='\t')
        
        #ファーストオーサーか調べる
        if pma.positionInAuthors(authorFN) == 1:
            print("First Author",end='\t')
        else:
            print("____",end='\t')

        #コレスポンディングオーサーか調べる
        if len(pma.corespondingAuthors) == 0:
            print("コレスポンディングオーサー不明",end="\t")
        elif pma.isAuthorIn(authorFN,pma.corespondingAuthors):
            if len(pma.corespondingAuthors) == 1:
                print("Coresponding author",end="\t")
            else:
                print("Coresponding author of total " + str(len(pma.corespondingAuthors)) + " coresponding authors",end='\t')
        else:
            print("",end="\t")
        
        #ラストオーサーか調べる。
        if pma.positionInAuthors(authorFN) == len(pma.humanAuthors):
            print("Last author",end='\t')
        else:
            print("",end='\t')

        print("")
```

これで、誰がどんな論文を書いたか、論文単位のデータから、人単位のデータへと変換することができるようになりました。古い論文では、著者のFirst Nameがデータに含まれていない場合、結婚などで名前が変わった場合などは、違う人として扱われてしまいます。また同姓同名の場合は区別がつきません。新しい論文では、ORCIDで人の区別ができますが、ORCIDが過去の論文に遡って著者に割り振られない限り、一律して著者のidentityを調べることはとても難しそうです。

#終わりに
いろいろ作ってみましたが、pubmedデータの書き方が多彩で、一律処理できない点が難しいところでした。equalityについては、著者ごとに記述があるかどうかのリストを作成するのにとどめましたので、あとはユーザーサイドで処理する必要があります。