Django + RDKit database cartridge のDocker-composeで化合物データベースを作った話

#概要
こちらは[創薬 Advent Calendar 2017](https://adventar.org/calendars/2412)４日目の記事です。

化合物データベースの構築をより簡便にするためにDocker-composeを作ったよというお話です。

#環境
Mac mini (Late 2014) OS: High Sierra (Jupyter Notebookのユーザー側のパソコン)
Docker version 17.09.0-ce, build afdb6d4 

Dockerで動いているのでおそらくDockerさえインストールされていたらどの環境でも動くと思います。

#前提条件
Django、Docker、Docker-composeの使い方はある程度理解している前提で話を進めます。
(でも結構細かく説明したので初めての方もやってみても面白いかもしれません)

#背景
**長くなってしまったので、Docker-composeのところだけ読みたい方は読み飛ばして[方法](https://qiita.com/yamasakih/items/58b3ef5eae6bfcd7fcf5#%E6%96%B9%E6%B3%95)へ進んでください。**

PostgreSQL, MySQLなどの無償で利用が可能なリレーショナルデータベースはありますが、
まだまだ環境構築が大変、SQL言語がとっつきにくいとなかなか個人で使ってみようというのは
ハードルが高い状況です。
そこでもっと簡単に使えるようにならないかなぁと思って環境をDocker-composeで作ってみました。

##1. なぜ化合物データベースが必要なのか
###1.1 単純な文字列として保存すると支障が出る
[このスライド](https://speakerdeck.com/yamasakih/di-6hui-kemoinhuomateikusuruo-shou-falsehui-chu-xue-zhe-falsetamefalsemian-qiang-hui-zi-liao)にもまとめていますが、簡単に言うと化学構造は単純な文字情報では保存できず、
また従来のデータベースでは化学構造に対して行うような検索ができないということです。

- SMILES  
どこをスタートとするかで書かれる文字列が異なります。また古いバージョンのSMILESだと立体構造の情報が含まれない場合もあります。(立体構造の情報を含むSMILESをISOMERIC SMILESと呼ぶ場合もあります)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/130169/37fcf8a7-4fd2-c1d6-7fe0-31b968be4066.png)

- Canonical SMILES  
どこをスタートとするか決めるCanonical SMILESもアプリケーションによって用いているアルゴリズムが異なる可能性があるので様々なアプリケーションで作成したCanonical SMILESを用いる場合一致しない可能性があります。
- InChI  
これはSMILESと比べると最近に開発されただけあってかなりいいと思います。もし以下に示す検索の種類で、完全一致検索のみを用いるならこちらを採用し文字列として保存してもいいと思います。

###1.2 化学構造ならではの検索をしたい
化学構造は例えばGoogle検索で「創薬」という言葉を検索するような検索方法もある一方で、以下に示すような化学構造だからこそこういう検索をしたいということがあります。

- 完全一致検索  
検索にかけた化学構造(以下、検索クエリ)と完全に構造が一致する化学構造を検索ヒットとして抽出する検索方法です。立体異性体も検索ヒットとしてみなすかなどのオプションがある場合があります。
- 部分構造検索  
検索クエリを部分構造として含む化学構造を検索ヒットとして抽出する検索方法です。例えばフェノールはベンゼン環を検索クエリとし部分構造検索をした場合、検索ヒットします。
- 類似性検索  
例えばfingerprintなどをつかって化学構造同士がどれくらいに似ているかを求め(類似度と呼ばれる指標を計算します)、にている化学構造を検索ヒットとして抽出します。

例えば以三環系抗うつ薬のように似ている化学構造は同じ性質を示すことが多いです。そこで創薬では新規の活性を持つ構造が見つかるとその類似構造や共通の部分構造をもつ化合物にも活性があるのではと試験を行うことがあります。このことからも部分構造検索や類似構造検索の機能は重要であるといえます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/130169/6236f6ff-5167-f3be-0368-b6526fa2a97a.png)

以上の検索をする場合は単純な文字列ではなく化学構造データとして保存し、さらにこれらの検索機能を持つデータベースが必要です。

これまでのことから化学構造を扱う化合物データベースがあるとよさそうだなと思ってもらえたら嬉しいです。

###1.3 化合物データベースの一例
じゃあ化合物データベースを使うのはいいとして具体的にどういうものがあるのかという話を背景の最後にします。
以下のような有償、無償の化合物データベースやデータベースカートリッジがあります。

|名前|参考サイト|無償かどうか|
|:--:|:------|:------|
|CambridgeSoft|http://www.cambridgesoft.com/|     |
|BIOVIA Isentris|http://ls.ctc-g.co.jp/products/accelrys/accelrys_isentris.html|     |
|Pgchem|http://www.pgchem.sk/|無償|
|RDKit database cartridge|http://www.rdkit.org/docs/Cartridge.html|無償|
|Bingo|http://lifescience.opensource.epam.com/bingo/index.html|アカデミックフリー？|

データベースカートリッジとは本来データベースにない機能を拡張するためのプラグインみたいなものです。
今回の記事では無償で利用できるように**PostgreSQL**に**RDKit database cartridge**を組み合わせた化合物データベースを使いたいと思います。

#問題点
ただし、まだ以下に示すような問題点があります。

- SQL言語を習得する必要がある。他の言語とちょっと違って独特で大変そう
- 環境構築が大変そう

これらの問題に関して今回は以下のような方法で解決したいと思います。

- SQL言語を習得する必要がある。他の言語とちょっと違って独特で大変そう  
**O/Rマッパーを使ってPythonでデータベースにアクセスしましょう**
- 環境構築が大変そう  
**Docker-composeを使って簡単に環境構築しましょう**

##1.SQL言語を習得する必要がある。他の言語とちょっと違って独特で大変そう  
SQL言語は独特かはまた別の議論としておいといて、新たに学ぶなら学習コストがかかるのは事実です。 
そこでO/Rマッパーを使うことで、すでにみなさんが習得しているプログラム言語を使ってデータベースにアクセスし、望む検索などを行えます。

O/Rマッパー: 語弊があるかもしれませんが簡単に言うと一般のプログラミング言語でデータベースにアクセスできることを指します。詳しくは[こちら](https://speakerdeck.com/yamasakih/di-6hui-kemoinhuomateikusuruo-shou-falsehui-shan-qi-fa-biao)を参照してください。

組み込んでいるWeb Frame Applicationの一例

| 名前 | 用いているプログラム名 |
|:----:|:------------------:|
|Ruby on Rail|Ruby|
|Django|Python|
|Flask|Python|
|Apatch Click|JAVA|

そこで今回はWeb Frame ApplicationであるDjangoのO/Rマッパーの機能の部分のみを利用しちゃおうと思います(もちろんDjango本来の機能である自社内化合物データベースサイトなども作れますよ)。
Djangoは従来のデータベースにのみしか対応していないので[django-rdkit](https://github.com/rdkit/django-rdkit)という拡張モジュールを使うと化合物データベースにも対応するようになります。
こちらも一緒に導入しちゃいます。

これでSQL言語を学ぶ必要があるという問題は解消されます。

##2. 環境構築が大変そう
化合物データベースの構築は大変ですし、リレーショナルデータベースを扱うならSQL言語を学ぶ必要があります。
例えば今回用意した環境構築を一から自分で構築するなら、

1. データベースとして従来のPostgreSQLを構築
1. RDKit database cartridgeを使ってデータベースを拡張
1. SQL言語を使いたくないのでO/Rマッパーとしてdjango-rdkitを用いてDjangoと連携

といったことをしなくてはいけません。

そこで~~ようやく~~タイトルを回収できますが、今回はその一手間をすべてDocker-composeで行えるものを作ったのでぜひ皆さん使ってみてくださいという話です。

Docker: 仮想環境を構築するツール。Moby Dockerがかわいい。
Docker-compose: 複数の仮想環境を連携しひとまとめにする機能。今回はデータベースとDjangoをひとまとめに連携させている。

Docker-composeを用いることで環境構築が大変とういう問題も解消されます。

#方法
まとめると今回は
PostgreSQL + RDKit database cartridge で化合物データベースを構築し
django + django-rdkit でPythonでその化合物データベースにアクセスしたり操作したりできる環境を構築します。

##1. Dockerのインストール
Dockerをインストールしましょう。
(DockerはWindows 32bitは対応していないそうです。すいません)


##2. リポジトリのクローン
[docker-compose-django-rdkit](https://github.com/yamasakih/docker-compose-django-rdkit)のリポジトリを自身の望むフォルダにクローンしてください。
例えば、ホームディレクトリ直下で良いなら以下のようなコードになります。

```
cd ~
git clone https://github.com/yamasakih/docker-compose-django-rdkit
```

その後、`docker-compose-django-rdkit`というフォルダができていると思うのでその中に移動しましょう。

```
cd docker-compose-django-rdkit
```

##3. Docker-composeの起動
続けてDocker-composeを起動します。

```
docker-compose up
```

はい。これだけでもう

- PostgreSQL + RDKit database cartridgeの化合物データベースが構築され
- django + django-rdkitの仮想環境も構築されています

はやい！~~今までの無駄な文章はなんだったんだろうか~~

```
> $ docker-compose up
Starting dockercomposedjangordkit_db_1 ... 
Starting dockercomposedjangordkit_db_1 ... done
Starting dockercomposedjangordkit_web_1 ... 
Starting dockercomposedjangordkit_web_1 ... done
Attaching to dockercomposedjangordkit_db_1, dockercomposedjangordkit_web_1
db_1   | LOG:  database system was interrupted; last known up at 2017-11-08 00:05:40 UTC
db_1   | LOG:  database system was not properly shut down; automatic recovery in progress
db_1   | LOG:  invalid record length at 0/18841E0
db_1   | LOG:  redo is not required
db_1   | LOG:  MultiXact member wraparound protections are now enabled
db_1   | LOG:  database system is ready to accept connections
db_1   | LOG:  autovacuum launcher started
web_1  | Performing system checks...
web_1  | 
web_1  | System check identified no issues (0 silenced).
web_1  | December 04, 2017 - 01:27:34
web_1  | Django version 1.11.7, using settings 'package_root.settings'
web_1  | Starting development server at http://0.0.0.0:8000/
web_1  | Quit the server with CONTROL-C.
```

試しにGoogle Chromeなどのブラウザを立ち上げ`http://0.0.0.0:8000`あるいは`http://localhost:8000`のいずれかにアクセスすると
仮想サーバーにアクセスすることができ以下の画面が表示されると思います。

**たまに最初の１回目だけ正しく表示されない場合があるのでその場合はターミナル上で`Ctrl + C`で一度仮想サーバーをストップしサイド`docker-compose restart`してからアクセスしてみてください。**
(仮想環境dbより先に仮想環境webがbuildされてしまうからでしょうか？)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/130169/582a37c6-d101-db01-dfaa-1a5227b92668.png)

ちなみに仮想環境を終了する場合には`docker-compose stop`と実行しましょう。
私のパソコンだと終了に少し時間がかかります。

```
> $ docker-compose stop
Stopping dockercomposedjangordkit_web_1 ... done
Stopping dockercomposedjangordkit_db_1  ... done                                                 
```

##4. データベース内にテーブルを作成し化合物を登録してみる
では最後にチュートリアルとして実際に化学構造情報を保存できるテーブルをデータベース内に作成し、化合物を登録して見ましょう。

docker-composeをstopしていた場合は`docker-compose restart`で再び起動しておいてください。

```
> $ docker-compose restart
Restarting dockercomposedjangordkit_web_1 ... done
Restarting dockercomposedjangordkit_db_1  ... done
```

再起動後はバックエンドで仮想環境が動いている状態になります。

###4.1 初期化のmigrateを行う
まずサブコマンド`migrate`を行い、Djangoでデフォルトで作られるテーブルを作成したり、化合物データベースの拡張機能を有効にしたりしましょう。
**従来の`./manage.py migrate`などのコマンドは先頭に`docker-compose run web`とつけることで実行できます。**
したがって今回の場合以下のように実行します。

```
> $ docker-compose run web ./manage.py migrate
Starting dockercomposedjangordkit_db_1 ... done
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, django_rdkit, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying django_rdkit.0001_setup... OK
  Applying sessions.0001_initial... OK
```

###4.2 `tutorial_application`を作成する
続いて以下のように実行して新しいアプリケーション`tutorial_application`を作成しましょう。

```
> $ docker-compose run web ./manage.py startapp tutorial_application
Starting dockercomposedjangordkit_db_1 ... done
```

`tutorial_application`という名前のフォルダが`docker-compose-django-rdkit`内に作成されていると思います。

```
> $ ls
Dockerfile            docker-compose.yml    package_root/         tutorial_application/
LICENSE               manage.py*            requirements.txt
```

続けて`./package_root/settings.py`の`INSTALLED_APPS`の項目に`tutorial_application`を追加しましょう。
`tutorial_application`は`startapp`で作成したアプリ名に適宜変更してください。

```
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django_rdkit',
    'tutorial_application',
]
```

注：同じ名前のフォルダがあると間違いの元なので、私は間違いがないようにDjangoのsettings.pyなどのファイルができるフォルダをデフォルトの名前`docker-compose-django-rdkit`から`package_root`に変更しています。_なぜ自動で同じ名前で作られるんだ_

###4.3 テーブル`Compound`を定義する
`./tutorial_application/models.py`に以下のように追加しましょう。
**`from django.db import models`とデフォルトで書いている場合は削除してください。**

```
from django_rdkit import models

class Compound(models.Model):

    name = models.CharField(max_length=256)
    molecule = models.MolField()

    def __str__(self):
        return self.name
```

これは化合物データベース内に文字列`CharField`を保存する`name`と分子構造`MolField`を保存する`molecule`という２つのカラムを含むテーブルを作るという定義になります。
Djangoではテーブルの定義はこのようにクラスを用いることで行えます。`

| 名前 | 化学構造 |
|:----|:------------------|
|Benzen|c1ccccc1|
|Aspirin|CC(=O)Oc1ccccc1C(=O)O|
|Oseltamivir|CCC(CC)OC1C=C(CC(C1NC(=O)C)N)C(=O)OCC|

こんな感じの運用を想定しています。上の化学構造部分にはSMILESキーを書いていますが実際にはSMILESキーでは保存されていません。

###4.4 テーブル`Compound`を作成する
では実際に定義したテーブル`Compound`をデータベース内に作成しましょう。
まず以下のようにサブコマンド`makemigrations`を用いてテーブルの作成の準備をします。

```
> $ docker-compose run web ./manage.py makemigrations tutorial_application
Starting dockercomposedjangordkit_db_1 ... done
Migrations for 'tutorial_application':
  tutorial_application/migrations/0001_initial.py
    - Create model Compound
```

続けて以下のように実行し、テーブルを作成します。
例として以下のように表示されたらテーブルが無事作成されています。

```
> $ docker-compose run web ./manage.py migrate tutorial_application 
Starting dockercomposedjangordkit_db_1 ... done
Operations to perform:
  Apply all migrations: tutorial_application
Running migrations:
  Applying tutorial_application.0001_initial... OK
```

###4.5 データベース内のテーブルにアクセスしてみる
最後にPythonでデータベース内のテーブルにアクセスし化合物データを登録してみましょう。
化学データベースにアクセスするには`shell`サブコマンドを用います。

```
docker-compose run web ./manage.py shell
```

上記のように実行するとPythonインタープリターが立ち上がります。この環境下ではデータベースにアクセスができます。

####4.5.1 データの登録

```
from tutorial_application.models import Compound
```

まずCompoundテーブルにアクセスするためには`./tutorial_application/models.py`にある`Compound`クラスをインポートします。

```
Compound.objects.create(name='benzen', molecule='c1ccccc1')
```

`Compound.objects.create`とすることで`create`メソッドを実行することができます。
`create`メソッドは`Compound`テーブルに新規にデータを登録するメソッドです。各カラム`name`, `molecule`の値は引数として与えます。
ちょうどクラスのオブジェクトのコンストラクタと同じだと思ってもらえたらイメージしやすいかなと思います。
これで新しいデータが登録されました。

登録されているデータの数を全て確認するには以下のように実行します。

続けてAspiririnとOseltamivirも登録しましょう。

```
Compound.objects.create(name='Aspirin', molecule='CC(=O)Oc1ccccc1C(=O)O')
Compound.objects.create(name='Oseltamivir', molecule='CCC(CC)OC1C=C(CC(C1NC(=O)C)N)C(=O)OCC')
```

####4.5.2 全データの取り出し
登録されているデータ全てを取り出すには`Compound.objects.all()`を使います。

```
Compound.objects.all()
```

取り出した数を知りたいなら`Compound.objects.count()`ですね。

```
Compound.objects.count()
```

####4.5.3 部分構造検索
部分構造検索には`hassubstruct`を化学構造を保存しているカラム名(今回はmolecule)の後ろにアンダーバーを２つつけて使います。
さらに検索したい部分構造は適切にわたすためにValue関数でラッパーしSMILESでわたす必要があります。
例えばベンゼン環を部分構造として持つ化合物だけを取り出したいときは以下のように実行します。

```
from django_rdkit.models import Value
Compound.objects.filter(molecule__hassubstruct=Value('c1ccccc1'))
<QuerySet [<Compound: benzen>, <Compound: Aspirin>]>
```

ベンゼン環を持つbenzenとAspirinのみが検索ヒットしています。

####4.5.4 検索ヒットしたデータの取り扱い
ちなみに検索したい化学構造が複雑でSMILESが書けなくてもRDKitを使うことができるので大丈夫です。
例えばデータベースに保存されているOseltamivirのSMILEは以下のように取り出すことができます。

```
compound = Compound.objects.get(id=3)
compound
<Compound: Oseltamivir>
```

検索ヒットが必ず一個となる検索条件の場合は`get`メソッドを使うと楽です。今回はid=3で検索しています。
実はDjangoでは自動的にidというカラムもテーブル`Compound`に作られて1,2,3,...と割り振られます。
Oseltamivirは3番目に登録したのでid=3です。

さらにテーブルから取り出したデータは`class Compound`のオブジェクトとして扱うことができます。
したがって`compound`はフィールドid, name, moleculeを持っています。

```
compound.id
3
compound.name
'Oseltamivir'
compound.molecule
<rdkit.Chem.rdchem.Mol object at 0x7f61dc605c38>
```

しかも`compound.molecule`をみるとこれはRDKitで扱われている化学構造の`Mol`オブジェクトです。
そうなんですデータベースから取り出した後はRDKitの`Mol`オブジェクトとして扱うことができるのです。

試しにRDKitで`Mol`オブジェクトをSMILESに変換する`Chem.MolToSmiles`関数を使ってみましょう。

```
from rdkit import Chem
Chem.MolToSmiles(compound.molecule)
'CCOC(=O)C1=CC(OC(CC)CC)C(NC(C)=O)C(N)C1'
```

見事にSMILESを取り出すことができましたね。このようにRDKitを日頃使っている方にとってはすごい扱いやすいデータ構造になっています。
最後にあえてこのSMILESで完全一致検索してみましょう。当然Oseltamivirが検索ヒットします。
**完全一致検索は`molecule=Value(検索したいSMILES)`としたらできます。**

```
smiles = Chem.MolToSmiles(compound.molecule)
Compound.objects.filter(molecule=Value(smiles))
<QuerySet [<Compound: Oseltamivir>]>
```

最後に4.5で実行したコードをまとめておきます。

```
> $ docker-compose run web ./manage.py shell
Starting dockercomposedjangordkit_db_1 ... done
Python 3.6.0 |Continuum Analytics, Inc.| (default, Dec 23 2016, 12:22:00) 
[GCC 4.4.7 20120313 (Red Hat 4.4.7-1)] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from tutorial_application.models import Compound
>>> Compound.objects.create(name='benzen', molecule='c1ccccc1')
<Compound: benzen>
>>> Compound.objects.create(name='Aspirin', molecule='CC(=O)Oc1ccccc1C(=O)O')
<Compound: Aspirin>
>>> Compound.objects.create(name='Oseltamivir', molecule='CCC(CC)OC1C=C(CC(C1NC(=O)C)N)C(=O)OCC')
<Compound: Oseltamivir>
>>> Compound.objects.all()
<QuerySet [<Compound: benzen>, <Compound: Aspirin>, <Compound: Oseltamivir>]>
>>> Compound.objects.count()
3
>>> from django_rdkit.models import Value
>>> Compound.objects.filter(molecule__hassubstruct=Value('c1ccccc1'))
<QuerySet [<Compound: benzen>, <Compound: Aspirin>]>
>>> compound = Compound.objects.get(id=3)
>>> compound
<Compound: Oseltamivir>
>>> compound.id
3
>>> compound.name
'Oseltamivir'
>>> compound.molecule
<rdkit.Chem.rdchem.Mol object at 0x7fcae75917a0>
>>> from rdkit import Chem
>>> Chem.MolToSmiles(compound.molecule)
'CCOC(=O)C1=CC(OC(CC)CC)C(NC(C)=O)C(N)C1'
>>> smiles = Chem.MolToSmiles(compound.molecule)
>>> Compound.objects.filter(molecule=Value(smiles))
<QuerySet [<Compound: Oseltamivir>]>
```

###4.6 その他
チュートリアルを通すことでSQL言語を全く知らない方でもPythonのみでデータベースにアクセスすることが可能だと実感してもらえたと思います。

大量データの登録方法や類似構造検索などその他の機能はこちらを参考にしてみてください。
[django-rdkit/tutorial.rst](https://github.com/rdkit/django-rdkit/blob/master/docs/tutorial.rst)

#今後の課題
私が作ったDocker-composeは構築は簡単ですが、弱点の一つとして2.5GBぐらいとすごい容量を使うことがあげられます。
今後はより容量が軽いDockerを作成しそれをベースにしようかなと思います。

あと課題ではないですが無料のPyCharmではDockerのPythonをインタプリターと設定できないのでファイル編集とかがvimとかでしたり
代理になりそうなインタープリターを別途用意する必要があります。
有料のPyCharmではDockerのPythonもインタープリターに設定できるそうです。

今後はDjangoの機能をちゃんと活かして化合物データベースをどう作っていけばなどもGithubのリポジトリで追加していきたいと思っているので
興味のある方はGithubのリポジトリにStarをつけたりWatchしたりむしろ一緒に開発してみましょう。
[docker-compose-django-rdkit](https://github.com/yamasakih/docker-compose-django-rdkit)


#最後に
以上でDjango + RDKit database cartridge のDocker-composeで化合物データベースを作った話を終わりにしたいと思います。
現在、同様にDjangoからではなくJupyter Notebookから化合物データベースにアクセスするDocker-composeも作成中です。
そちらの方がより直感的に操作できるかな？と思っています。興味ある方はぜひ気長にお待ちください。

化合物データベースを効果的に使って、よき創薬ライフを過ごしましょう！

#参考
[Docker Compose &mdash; Docker-docs-ja 17.06.Beta ドキュメント](http://docs.docker.jp/compose/toc.html)
[docker-compose コマンドまとめ](https://qiita.com/aild_arch_bfmv/items/d47caf37b79e855af95f#build)
[docker-composeでmysql & postgreSQL をサクッと起動](https://qiita.com/astrsk_hori/items/1e683a7a2f2b7189cb6e)
[The RDKit database cartridge](http://www.rdkit.org/docs/Cartridge.html)
[django-rdkit](https://github.com/rdkit/django-rdkit)