【目的】¶
「siRNAを導入した際にどのようなmRNAが発現抑制されるか」について様々な予測がされているが、確定的な理論は存在しない。
そこで、どういった特徴を持ったmRNA(配列)が(この実験系では・マイクロアレイ解析の結果上は)抑制されるのか調べる。
! python --version
# 見栄えの問題
import warnings
warnings.filterwarnings('ignore')
1. 全データを読み込む
マイクロアレイ解析1で行なった前処理後のデータを読み込む。
import pandas as pd
df = pd.read_excel("result.xlsx")
df.head(3)
print("データ数: {}".format(len(df)))
2. mRNAの配列を調べる
Home - Nucleotide - NCBI のデータベースにアクセスし、塩基配列データを取得する。こちらも、もちろん $21511$ の塩基配列を取得するのは骨が折れるので、プログラム化する。なお、GeneName(ex.`NM_015987`) から id(ex.`1519241814`) を取得し、そのidを用いて新たにクエリを投げないと(僕の頭では)塩基配列を取得できなかった ので、二回リクエストを送っている。(idの部分にGeneNameを入れてもうまく検索ができた。)
また、リクエストを送った時にうまく try
内が処理できる場合と処理ができない場合(except
)があるが、リクエストごとに決まっている訳ではなく、送るタイミングによってうまく処理できたりできなかったりしていた。
が、ほとんどの場合 "No items found"と出ていたので、今回は深追いしない:(
import re
import requests
from tqdm import tqdm
guide_match_array = "gagttca"
front_patterns =r'<meta name="ncbi_uidlist" content="(.+)" /><meta name="ncbi_filter"'
back_request_format = "https://www.ncbi.nlm.nih.gov/sviewer/viewer.fcgi?id={}&db=nuccore&report=genbank&conwithfeat=on&hide-cdd=on&retmode=html&withmarkup=on&tool=portal&log$=seqview&maxdownloadsize=1000000"
back_patterns = ">([atgc ]*)</span>"
def gene2array(GeneName):
try:
#=== httpリクエストを飛ばしてプログラムで検索を行う ===
front_response = requests.get('https://www.ncbi.nlm.nih.gov/nuccore/', params={'term': GeneName})
genome_id = re.findall(front_patterns, front_response.text)[0] # ex) 1519241814
back_response = requests.get(back_request_format.format(genome_id))
sequences = re.findall(back_patterns, back_response.text) # ex) ['atgcatgcat gcatgca...', 'at..', .., '..atgc']
sequence = "".join(sequences).replace(" ","")
#=== 配列の長さ ===
length = len(sequence)
#=== seed領域と一致するポジションの5'末端からの割合 ===
position = re.search(guide_match_array, sequence)
p = position.end()/length if position else 0
return [sequence,length,p]
except:
return [None, None, None]
# GeneNameを全て調べる。
gene_names = df.SystematicName.values
sample_gene_name = gene_names[17]
print("sample gene name: {}".format(sample_gene_name))
gene2array(sample_gene_name)
「塩基配列・塩基配列長・シードマッチ領域の5'末端からの割合」が記録されていることがわかる。
うまくプログラムが実行できていることがわかったので、以下を実行して $21511$ データ分の配列を入手する。
lst = []
for genome in tqdm(gene_names):
lst.append(gene2array(genome))
df_output = pd.DataFrame(lst)
df_output.columns=['sequence','length','position']
df_output.to_csv('seeqences.csv')
二時間ほど回したが、以下のような出力が出ていた…。(現在時刻 6/20 20:36)
9%|▉ | 1963/21511 [1:55:43<19:12:28, 3.54s/it]
9%|▉ | 1964/21511 [1:55:45<19:12:04, 3.54s/it]
9%|▉ | 1965/21511 [1:55:50<19:12:17, 3.54s/it]
9%|▉ | 1966/21511 [1:55:54<19:12:17, 3.54s/it]
9%|▉ | 1967/21511 [1:55:56<19:11:56, 3.54s/it]
9%|▉ | 1968/21511 [1:56:00<19:12:05, 3.54s/it]
明日のプレゼン開始時にデータ収集が終わるんだけど解析とは?? pic.twitter.com/0aOb3BVIDF
— しゅーと (@cabernet_rock) 2019年6月20日
3. AWSにインスタンスを立て並列処理
6/21(金)のプレゼンテーションに間に合いそうにないので、急遽 [AWS](https://aws.amazon.com/jp/) でEC2インスタンスを複数台(計 $11$ 個)立てて並列処理を行うことにした。
まず、以下の main.py
ファイルと result.xlsx
を格納した microarray
というディレクトリを準備した。
main.py
¶
#coding:utf-8
import argparse
import numpy as np
import pandas as pd
import re
import requests
from tqdm import tqdm
def gene2array(GeneName):
try:
#=== httpsリクエストを飛ばして検索を行う ===
front_response = requests.get('https://www.ncbi.nlm.nih.gov/nuccore/', params={'term': GeneName})
genome_id = re.findall(front_patterns, front_response.text)[0]
back_response = requests.get(back_request_format.format(genome_id))
sequences = re.findall(back_patterns, back_response.text)
sequence = "".join(sequences).replace(" ","")
#=== 配列の長さ ===
length = len(sequence)
#=== seed領域と一致するポジションを調べる ===
position = re.search(guide_match_array, sequence)
p = position.end()/length if position else 0
return [sequence,length,p]
except:
return [None, None, None]
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument('--begin', type=int)
parser.add_argument('--end', type=int)
args = parser.parse_args()
#=== パラメータ定義 ===
guide_match_array = "gagttca"
front_patterns =r'<meta name="ncbi_uidlist" content="(.+)" /><meta name="ncbi_filter"'
back_request_format = "https://www.ncbi.nlm.nih.gov/sviewer/viewer.fcgi?id={}&db=nuccore&report=genbank&conwithfeat=on&hide-cdd=on&retmode=html&withmarkup=on&tool=portal&log$=seqview&maxdownloadsize=1000000"
back_patterns = ">([atgc ]*)</span>"
begin = args.begin
end = args.end
df = pd.read_excel("result.xlsx")[begin:end]
print("{} ~ {}".format(begin, end))
gene_names = df.SystematicName.values
lst = []
for genome in tqdm(gene_names):
lst.append(gene2array(genome))
df_output = pd.DataFrame(lst)
df_output.columns = ["sequence","length","p"]
df_output.to_csv('sequence{:05}to{:05}.csv'.format(begin,end), index=False)
print("finished.")
# 階層構造は以下の通り。
! tree microarray
データを送信する¶
上記のディレクトリを scp
コマンドでEC2インスタンスに転送し、プログラムが実行できる環境を構築する。諸々の準備が整い、後は実行するだけとなったらAmazon マシンイメージ (AMI) を取り、イメージを取得する。このイメージを元にEC2インスタンスを作成することで、かなりの作業を省略することができる。
# ローカルのデータをそれぞれのEC2インスタンスに送る。
$ scp -r -i ~/.ssh/ShutoBioinfo.pem microarray ubuntu@ec2-3-113-33-209.ap-northeast-1.compute.amazonaws.com:/home/ubuntu/
$ ssh -i "path/to/key.pem" ubuntu@ec2-3-113-33-209.ap-northeast-1.compute.amazonaws.com
----------------------------------------------------------------
Welcome to Ubuntu 18.04.1 LTS (GNU/Linux 4.15.0-1041-aws x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
----------------------------------------------------------------
環 境 構 築
----------------------------------------------------------------
A M I 取 得
----------------------------------------------------------------
ubuntu@ip-172-31-45-117:~$ cd microarray/
ubuntu@ip-172-31-45-117:~/microarray$ nohup python main.py --begin 0 --end 2000 > Logger.log
インスタンス情報とコマンド作成プログラム¶
以下のインスタンスを作成してプログラムを実行した。この時、一々コピーアンドペーストするのは面倒なので、スクレイピングをしている間にコマンドを準備しておく。
# | パブリック DNS (IPv4) | args |
---|---|---|
1 | ec2-3-113-33-209.ap-northeast-1.compute.amazonaws.com | --begin 0 --end 2000 |
2 | ec2-3-112-149-206.ap-northeast-1.compute.amazonaws.com | --begin 2000 --end 4000 |
3 | ec2-3-112-213-203.ap-northeast-1.compute.amazonaws.com | --begin 4000 --end 6000 |
4 | ec2-13-113-177-99.ap-northeast-1.compute.amazonaws.com | --begin 6000 --end 8000 |
5 | ec2-54-249-50-45.ap-northeast-1.compute.amazonaws.com | --begin 8000 --end 10000 |
6 | ec2-13-231-179-134.ap-northeast-1.compute.amazonaws.com | --begin 10000 --end 12000 |
7 | ec2-13-115-166-8.ap-northeast-1.compute.amazonaws.com | --begin 12000 --end 14000 |
8 | ec2-3-112-73-40.ap-northeast-1.compute.amazonaws.com | --begin 14000 --end 16000 |
9 | ec2-52-198-222-27.ap-northeast-1.compute.amazonaws.com | --begin 16000 --end 18000 |
10 | ec2-13-115-220-149.ap-northeast-1.compute.amazonaws.com | --begin 18000 --end 20000 |
11 | ec2-18-182-16-178.ap-northeast-1.compute.amazonaws.com | --begin 20000 --end 22000 |
publicDNSs = [
"3-113-33-209","3-112-149-206","3-112-213-203",
"13-113-177-99","54-249-50-45","13-231-179-134",
"13-115-166-8","3-112-73-40","52-198-222-27",
"13-115-220-149","18-182-16-178"
]
begins = [ i * 2000 for i in range(11)]
ends = [(i+1)* 2000 for i in range(11)]
# ssh接続からプログラム実行まで
for p,b,e in zip(publicDNSs, begins,ends):
print(
" ssh -i 'path/to/key.pem' ubuntu@ec2-{}.ap-northeast-1.compute.amazonaws.com\n".format(p),
"cd microarray\n",
"nohup python main.py --begin {} --end {} > Logger.log &\n".format(b,e),
"exit"
)
4. NCBIの結果を統合する
無事それぞれがスクレイピングを完了したので、結果を統合する。なお、EC2インスタンスからデータを取得するのは、ローカルから以下のコマンドを実行するだけで可能である。
# 結果をEC2からリモートに送る
for p,b,e in zip(publicDNSs, begins,ends):
print("scp -i 'path/to/key.pem' ubuntu@ec2-{}.ap-northeast-1.compute.amazonaws.com:/home/ubuntu/microarray/sequence{:05}to{:05}.csv .".format(p,b,e))
※ 本来ならうまくスクレイピングできなかったデータに関してはもう一度リクエストを飛ばすなりの作業を行ってデータを集める必要があるが、"No items found" とリスポンスが返ってきてしまっていたデータがかなりあったので今回は2回目だけ行い、それ以降は深追いしなかった:(
# 以下のファイルが作られた。
! tree AWS
import os
file_names = [name for name in sorted(os.listdir("AWS")) if name[-4:] == ".csv"]
# 確認
file_names
df_all = pd.DataFrame()
for filename in file_names:
tmp = pd.read_csv(os.path.join('AWS',filename))
df_all = pd.concat([df_all,tmp])
# 結合
df_cocated = pd.concat([df_all[['sequence','length','p']].reset_index(drop=True),df.reset_index(drop=True)], axis=1)
df_cocated.head(3)
# かなりNaN(配列データが取得できなかったもの)が多い
df_cocated.isnull().sum()
5. 相関関係を調べる
ここまでで集めたデータの特徴量と log2(siRNA/mock)
の相関関係を調べる。
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
df_cocated["mock"] = df_cocated[["mock(1)","mock(5)"]].mean(axis=1)
df_cocated["log2(siRNA/mock)"] = np.log2(df_cocated["RNA"]/df_cocated["mock"])
# 正しく配列データが取得できたものだけを利用する。
df_used = df_cocated[df_cocated.length>0].reset_index(drop=True)
print("取得できたmRNA: {}".format(len(df_used)))
# 取得したmRNAの配列の長さの分布
plt.hist(df_used.length)
plt.title("The histgram of the length of mRNA")
plt.xlabel("length")
plt.ylabel("N")
plt.show()
print("7merマッチ出現量の期待値: {}".format(df_used.length.sum()/(4**7)))
# 取得した配列の発現量の分布
plt.hist(df_used["log2(siRNA/mock)"], bins=20)
plt.title("The histgram of the expression level of mRNA")
plt.xlabel("expression level")
plt.ylabel("N")
plt.show()
# シード領域とマッチする領域の出てくる場所の割合
def match_rate(match_array, array, length, rate=False):
position = re.search(match_array, array)
if position:
p = position.end()/length if rate else position.end()
else:
p = 0
return p
# うまく取得できていなかったので、これらのデータを利用
df_used["position"] = df_used.apply(lambda x: match_rate('gagttca', x.sequence, x.length), axis=1)
df_used["position_rate"] = df_used.apply(lambda x: match_rate('gagttca', x.sequence, x.length, rate=True), axis=1)
df_used[["length","log2(siRNA/mock)"]].corr()
df_used_with_seed = df_used[df_used.position>0]
print("取得できたマッチ領域を含むmRNA: {}".format(len(df_used_with_seed)))
↑seedmatchで調べた時と数が異なっている…。こんなに多いはずがない??
df_used_with_seed[["length","position","position_rate","log2(siRNA/mock)"]].corr()
sns.heatmap(
df_used_with_seed[["length","position","position_rate","log2(siRNA/mock)"]].corr(),
vmin=-1.0,
vmax=1.0,
center=0,
annot=True, # True:格子の中に値を表示
fmt='.3f',
)
plt.show()
長さ・マッチ領域の割合はほとんど関係がない😱😱😱
6. seedmatchの結果と比べる
シード領域とマッチする領域の数が異なっていたので、seedmatchの結果をもう一度参照し、比較する。
※なお、取得できたデータに対してのみ行なっていることに注意!!
seedmatch¶
def seedmatchNmer(df, N, match_array, arrayname="", return_data=False):
"""
関数の概要:色々な領域のNmerの発現量を調べる。
@param df :元となるデータフレーム
@param N :領域の長さ
@param match_array:ガイド鎖やパッセンジャー鎖のシード領域と相補的なmRNAの配列(5'-3')
"""
PATTERNS = r'\n(.+)\t([0-9]+)'
GMEANS = []
COLNAMES = []
LENGTH = len(match_array)
df_Nmer = df.sort_values(by="log2(siRNA/mock)")
plt.figure(figsize=(12,8))
for p in reversed(range(len(match_array)+1)):
colname = "{}-{}mer({}-{})".format(arrayname, N,LENGTH-p+1,LENGTH-p+N)
COLNAMES.append(colname)
# "seedmatch"にリクエスト飛ばしてseed領域に対応する配列を検索
array = match_array[p-N:p]
r = requests.post('http://atlas.rnai.jp/seedmatch/seedmatch.cgi', data={"seedseq": array})
df_mRNA = pd.DataFrame(re.findall(PATTERNS, r.text))
df_mRNA.columns=["SystematicName", colname]
# マージ
df_Nmer = pd.merge(df_Nmer, df_mRNA, on="SystematicName", how="left").fillna(0)
# マッチする配列を持っているものを抜き出して
signal_7mer = df_Nmer[df_Nmer[colname]!=0]["log2(siRNA/mock)"].values
GMEANS.append(np.mean(signal_7mer))
length = len(signal_7mer)
# 累積密度をプロット
plt.plot(signal_7mer,[(i+1)/length for i in range(length)], label=colname + ": {}".format(length))
if p-N==0: break
#=== プロット後処理 ===
plt.title('The difference between\n"seed match resions" and "Expression levels"')
plt.xlabel("$\log_{2}(siRNA/mock)$")
plt.ylabel("Cumulative frequency")
plt.grid()
plt.vlines(x=0, ymin=0, ymax=1, colors='black', linewidths=2)
plt.legend()
plt.xlim(-1,1)
plt.show()
#=== ===
X = [i for i in range(len(GMEANS))]
label_before = [i for i in range(len(GMEANS)) if i %2 == 0]
label_after = ["{}-{}".format(p+1,p+N) for p in range(len(GMEANS)) if p %2 == 0]
plt.barh(X,GMEANS)
plt.title('The difference between\n"seed match resions" and "Expression levels"')
plt.xlabel("7mer position")
plt.ylabel("average $\log_{2}(siRNA/mock)$")
plt.yticks(label_before,label_after)
plt.show()
if return_data:
return (df_Nmer, COLNAMES)
guide_array = "UUGAACUCGGUGUUGAUGGCG" # 5'-3'
g_match_array = "CGCCATCAACACCGAGTTCAA" # 5'-3'
seedmatchNmer(df_used_with_seed, 7, g_match_array, arrayname="guide", return_data=False)
どの7merにも抑制傾向がある…。
NCBI¶
def seedNCBINmer(df, N, match_array, arrayname="", return_data=False):
"""
関数の概要:色々な領域のNmerの発現量を調べる。
@param df :元となるデータフレーム
@param N :領域の長さ
@param match_array:ガイド鎖やパッセンジャー鎖のシード領域と相補的なmRNAの配列(5'-3')
"""
GMEANS = []
COLNAMES = []
LENGTH = len(match_array)
df_Nmer = df.sort_values(by="log2(siRNA/mock)")
plt.figure(figsize=(12,8))
for p in reversed(range(len(match_array)+1)):
array = match_array[p-N:p]
colname = "{}-{}mer({}-{})".format(arrayname, N,LENGTH-p+1,LENGTH-p+N)
COLNAMES.append(colname)
df_Nmer[colname] = df_Nmer.apply(lambda x: match_rate(array, x.sequence, x.length), axis=1)
# マッチする配列を持っているものを抜き出して
signal_7mer = df_Nmer[df_Nmer[colname]>0]["log2(siRNA/mock)"].values
GMEANS.append(np.mean(signal_7mer))
length = len(signal_7mer)
# 累積密度をプロット
plt.plot(signal_7mer,[(i+1)/length for i in range(length)], label=colname + ": {}".format(length))
if p-N==0: break
#=== プロット後処理 ===
plt.title('The difference between\n"seed match resions" and "Expression levels"')
plt.xlabel("$\log_{2}(siRNA/mock)$")
plt.ylabel("Cumulative frequency")
plt.grid()
plt.vlines(x=0, ymin=0, ymax=1, colors='black', linewidths=2)
plt.legend()
plt.xlim(-1,1)
plt.show()
#=== ===
X = [i for i in range(len(GMEANS))]
label_before = [i for i in range(len(GMEANS)) if i %2 == 0]
label_after = ["{}-{}".format(p+1,p+N) for p in range(len(GMEANS)) if p %2 == 0]
plt.barh(X,GMEANS)
plt.title('The difference between\n"seed match resions" and "Expression levels"')
plt.xlabel("7mer position")
plt.ylabel("average $\log_{2}(siRNA/mock)$")
plt.yticks(label_before,label_after)
plt.show()
if return_data:
return (df_Nmer, COLNAMES)
df_guide_7mer, guide_7mer_colnames = seedNCBINmer(df_used, 7, g_match_array.lower(), "guide", return_data=True)
7. モデルを作成する
モデルに予測させてみて、「どの特徴量を参考にした??」って聞けば、変数の重要度がわかるという流れ。今回は解釈のしやすい決定木を用いてパラメータも適当に決めてサクッと学習させてみた。
feature_columns = guide_7mer_colnames+["length","position","position_rate"]
# 相関関係を調べる。
sns.heatmap(
df_guide_7mer[feature_columns].corr(),
vmin=-1.0,
vmax=1.0,
center=0,
fmt='.3f',
)
plt.show()
from sklearn.ensemble import RandomForestRegressor
from sklearn import preprocessing
tree = RandomForestRegressor(random_state=0, max_depth=15)
X = df_guide_7mer[feature_columns].values.astype(float)
y = df_guide_7mer["log2(siRNA/mock)"].values.reshape(-1,1).astype(float)
# 標準化
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
X_std = ss.fit_transform(X)
y_std = ss.fit_transform(y)
tree.fit(X, y)
plt.scatter(y,tree.predict(X), s=0.3)
plt.title('predicted "$\log_2(siRNA/mock)$" and answer "$\log_2(siRNA/mock)$"')
plt.ylabel("answer")
plt.xlabel("predict")
plt.show()
n_features = len(feature_columns)
plt.figure(figsize=(12, 8))
plt.barh(range(n_features), tree.feature_importances_ , align='center')
plt.yticks(np.arange(n_features), feature_columns)
plt.title("importances for predict the Expression levels")
plt.show()
どちらにせよ2-8の7merが重要そうだが、mRNAの長さにも依存関係がありそう。
かなり深い木を使っていることもあり、その結果比較的プロットがうまくいっているので、一つのパラメータで語るのが難しい分野であると考えられる、
8. まとめ
今回はほとんど考察をせずに必要そうな特徴量を
- 「mRNAの長さ」
- 「シード配列のポジション(割合/絶対値)」
に限って考えてしまった。もっと他の特徴量も考察したかった。
また、「mRNAに対する抑制度合い」を予測するのであれば、もちろんデータを分割して汎化性能を求める必要がある。
※NCBIのプログラムに関しては目検でデータを確認していないので、それも確認が必要かと思います。