BiopythonでPubMedから論文情報を抽出して語の出現頻度を可視化・キーワード抽出

# モチベーション
研究者のみなさま、今年も科研費執筆お疲れ様です。科研費申請書の「関連する国内外の研究動向と本研究の位置づけ」欄を書く（助けにする）ために、PubMedの論文情報を自然言語処理で簡単に解析したいです。語の頻度の可視化と、キーワード抽出を試してみます。

# 環境
- MacOS Mojave 10.14.5
- Python 3.7.10
- Jupyter Notebook 6.4.3

# 準備

```bash:bash
pip install Bio
pip install nltk
pip install rake-nltk
pip install yake
pip install spacy
python3 -m spacy download en
```
# BiopythonでPubMedから論文情報をダウンロードする
まず検索して件数を出します。[Chlamydomonas](https://ja.wikipedia.org/wiki/%E3%82%AF%E3%83%A9%E3%83%9F%E3%83%89%E3%83%A2%E3%83%8A%E3%82%B9)で検索してみましょう。[ここ](https://qiita.com/joemphilips/items/767c67524e4b7e328834)の9.14を参考にやっていきます。

```Python:Python
from Bio import Entrez
Entrez.email = "sample@email" #メールアドレスを入力
handle = Entrez.egquery(term="Chlamydomonas")
record = Entrez.read(handle)
for row in record["eGQueryResult"]:
     if row["DbName"]=="pubmed":
            number = row["Count"]
            print(number) #9335
```
次にesearchを使ってIDリストを取得します。

```Python:Python
handle = Entrez.esearch(db="pubmed", term="Chlamydomonas", retmax=number, usehistory="y") #取得上限をさっき検索した数に設定
record = Entrez.read(handle)
idlist = record["IdList"]
print(idlist)
```

efetchで論文情報を取得します。

```Python:Python
from Bio import Medline
handle = Entrez.efetch(db="pubmed", id=idlist, rettype="medline",
                           retmode="text")
records = Medline.parse(handle)
records = list(records)
```
![スクリーンショット 2021-09-10 9.17.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/820036/a9b6a7f7-21f5-f6a6-a6c3-f29a1d6a59ec.png)
論文情報が取得できました。タイトル（TI）とアブストラクト（AB）を抽出して操作します。

```Python:Python
titles = []
abstracts = []

for record in records:
    title = record.get("TI", "?")
    abstract = record.get("AB", "?")
    titles.append(title)
    abstracts.append(abstract)

texts = titles + abstracts #リストを結合
```
![スクリーンショット 2021-09-10 9.21.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/820036/5d2a34a7-e4ef-6763-11ee-e0d856fbd683.png)

# キーワードの抽出と可視化
[日本語で前にやった](https://qiita.com/kageazusa/items/f0c439e61be3240ffcc1)のとほぼ同じです。前処理として形態素解析をしないだけ日本語より楽です。
## 単純に語の出現頻度を数える
`texts`をバラバラにしてみます。まずはテストから。

```Python:Python
import re
print(re.split('\.|,| |:|;|\(|\)|\[|\]|\?|\!', abstracts[0]))
```
![スクリーンショット 2021-09-10 9.39.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/820036/5af5cf51-654b-71f2-d8f2-f7ccf824dd62.png)


いい感じですが、空の要素が含まれるので削除します。

```Python:Python
words = re.split('\.|,| |:|;|\(|\)|\[|\]|\?|\!', abstracts[0])
words = [word for word in words if word != '']
print(words)
```
![スクリーンショット 2021-09-10 9.33.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/820036/c4107d40-8eaa-866e-a2db-79ebd8892c09.png)

これをリスト`texts`に対して繰り返します。

```Python:Python
words_list = []

for text in texts:
    words = re.split('\.|,| |:|;|\(|\)|\[|\]|\?|\!', text)
    words = [word for word in words if word != '']
    words_list.extend(words)
```
![スクリーンショット 2021-09-10 9.40.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/820036/6ab61945-2551-a5ab-cdf1-75e4fc40d676.png)

最後に、大文字を小文字に変換します。長さは1760627語でした。

```Python:Python
lower_list = list(map(str.lower, words_list))
```
### 可視化
準備ができたので可視化していきます。以下、上位50位の頻度をグラフにします。

```Python:Python
import collections
import matplotlib.pyplot as plt

words, counts = zip(*collections.Counter(lower_list).most_common())

plt.figure(figsize=[12, 6])
plt.bar(words[0:50], counts[0:50])
plt.xticks(rotation =90)
plt.ylabel('count')
plt.savefig('PubMed_bar', dpi=200, bbox_inches="tight")
```

![PubMed_bar.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/820036/2ba4d8e2-4e9e-9eb2-4e74-0a974694c030.png)

the, of, and, in, a, ...という結果になったので、ストップワードを除去してみます。

### ストップワード除去
nltkからストップワードを呼び出し、リスト`lower_list`から削除します。

```Python:Python
from nltk.corpus import stopwords
stop_words = stopwords.words('english')
lower_list2 = [word for word in lower_list if word not in stop_words]
```
![スクリーンショット 2021-09-10 9.51.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/820036/4f402e6e-f1c5-4c29-2396-ab4ae3ee51de.png)

ストップワードを除去した場合の語の頻度を可視化した結果を以下に示します。
![PubMed_bar2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/820036/c26f8503-7f88-a840-9aa6-81b62f7af7ea.png)

Chlamydomonasで論文検索しましたが、chlamydomonas, reinhardtiiが最も多いという当然の結果になりました（Chlamydomonas reinhardtiiの2語で種名です）。他にもprotein(s), cell(s), gene(s), chloroplastなどそれらしい単語が上位に来ています。

# spaCy
ここからキーワード抽出ツールを使っていきます。
[ここ](https://towardsdatascience.com/keyword-extraction-process-in-python-with-natural-language-processing-nlp-d769a9069d5c)を参考に、spaCy, YAKE, rake-nltkの3種類を試してみました。まずspaCyです。

```Python:Python
import spacy
nlp = spacy.load("en")
keywords_spacy = []

for text in texts:
    doc = nlp(text)
    keywords_spacy.extend(list(doc.ents))

keywords_spacy = [str(keyword) for keyword in keywords_spacy]
```
![スクリーンショット 2021-09-10 20.12.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/820036/85018fe3-cbf5-aa63-08da-0b8d9822343c.png)

タイトルの大文字がそのまま入っているのかなと思いますが、このまま多い順に集計してみましょう。
![PubMed_spacy_bar.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/820036/a05d223e-5da5-9bd4-785a-269600abfd46.png)
102207キーワードが抽出されました。Chlamydomonas, C. reinhardtiiは当然入っているとして、PSII, RNA, ATP, PSIなど、大文字だけの略語が目立ちます。Arabidopsis, Escherichia, Chlorella, Volvoxなど、他の生物の属名も入っています。大文字があると重要と判定するのかもしれません。

## YAKE
次にYAKEでキーワード抽出してみます。

```Python:Python
import yake
kw_extractor = yake.KeywordExtractor()
keywords_yake = []

for text in texts:
    keywords_y = kw_extractor.extract_keywords(text)
    keywords_y = [t[0] for t in keywords_y]
    keywords_yake.extend(keywords_y)
```
なんだか途中で`ValueError: max() arg is an empty sequence`が出て止まってしまい、原因が特定できなかったのですが、144272キーワードを抽出できていたのでこのまま可視化してみます。

![スクリーンショット 2021-09-10 21.07.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/820036/7c60e37b-f215-224b-5c3d-a7781ce9bc9a.png)

![PubMed_yake_bar2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/820036/23564276-444b-484c-c9e2-1c0a258816cf.png)

chlamydomonas, chlamydomonas reinhardtii, reinhardtiiが上位3つを占めています。chlamydomonas reinhardiなどの表記ゆれが入っているのも特徴的です。数字は含まれていません。

## rake-nltk
最後にrake-nltkです。

```Python:Python
from rake_nltk import Rake

rake_nltk_var = Rake()
keywords_rake = []

for text in texts:
    rake_nltk_var.extract_keywords_from_text(text)
    keyword_extracted = rake_nltk_var.get_ranked_phrases()
    keywords_rake.extend(keyword_extracted)
```

![スクリーンショット 2021-09-10 21.07.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/820036/ab70fb4b-e86a-592b-2601-70aa77f78c37.png)

![PubMed_rake_bar.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/820036/b1b6ecb5-11e8-fc7c-6954-e8d18aeee530.png)
chlamydomonas reinhardtii, green alga chlamydomonas reinhardtiiはフレーズで抽出されているものの、他は単語のみが上位に来るという結果になりました。found, used, show, identified, observedなどの動詞が多いのが特徴的です。printした結果を見ると、一般性の乏しい長いフレーズが抽出される傾向があり、それで集計すると上位に単語が多くなるのかなと思います。

# まとめと所感
PubMedのデータから語の頻度を可視化し、spaCy, YAKE, rake-nltkでキーワード抽出を試してみました。今回はYAKEが一番キーワードっぽい結果になっているかなと思いますが、それぞれ一長一短ありそうです。個人的には、ストップワード除去して語の頻度だけ出すで研究の動向欄を書く（助けにする）という目的には十分かなという感じもありました。

# 参考
- [【自然言語処理】科研費データベースからMeCab-ipadic-neologdとtermextractでキーワードを抽出する](https://qiita.com/kageazusa/items/f0c439e61be3240ffcc1)
- [Biopython を利用したNCBIのEntrez データベースへのアクセス](https://qiita.com/joemphilips/items/767c67524e4b7e328834)
- [Keyword Extraction process in Python with Natural Language Processing(NLP)](https://towardsdatascience.com/keyword-extraction-process-in-python-with-natural-language-processing-nlp-d769a9069d5c)
- [自然言語のpythonでの前処理のかんたん早見表（テキストクリーニング、分割、ストップワード、辞書の作成、数値化）](https://qiita.com/kakiuchis/items/9149c7249f748668bb9b)
- [spacy.load('en')が実行できない](https://teratail.com/questions/173701)
- [Python リストの要素を違う型に変換する](https://natu-ym.com/python-list-conv/)
- [Python: リスト内の文字列を全て大文字or小文字にする](https://qiita.com/sino20023/items/c5f56386e6e03d6d078d)
- [リストの空の要素を駆逐する](https://qiita.com/github-nakasho/items/a08e21e80cbc9761db2f)
- [Pythonで文字列を分割（区切り文字、改行、正規表現、文字数）](https://note.nkmk.me/python-split-rsplit-splitlines-re/)