2025/02/11(火);codocプラグインが正常に動作していなかったためcodocの部分を削除。それに伴ってコードのダウンロードを停止。
- 1 はじめに
- 2 今回の解析の目的
- 3 edgeRとDESeq2の違いとそのどちらを使うか
- 4 Gene set enrichment analysisとOverrepresentation analysisの違い
- 5 使用するパッケージ
- 6 必要なデータセットの読み込み
- 7 QC
- 8 edgeRによる遺伝子発現解析
- 9 Volcano PlotとMAplot
- 10 Venn Diagram
- 11 DEG(Differentially Espressed/Expressing Gene)を使ったOverrepresentation Analysis
- 12 GSEA(Gene Set Enrichment Analysis)のためのデータセット作成
- 13 GSEA
- 14 Leading Edge Analsysis
- 15 Enrichment Mapによるグラフ作成
- 16 Leading Edge Geneを抽出する
- 17 抽出したLeading Edge Geneはどんなパスウェイに関連しているか調べる
- 18 ggplot2でGSEAの結果を可視化する
- 19 Rのバージョン違いに注意すること
- 20 これで一通りの解析は終了
- 21 コードがほしい場合
はじめに
先日、RNA-seqの解析でのFASTQのQC、アダプター除去(アダプタートリミング)、ゲノムへのマッピングの方法を記した。ここでは、それに引き続く解析であるedgeRによる遺伝子発現解析の方法、GSEAを使ったGSEA(Gene Set Enrichment Analysis)、clusterProfilerを使ったOverrepresentation Analysisの方法を記すことにする。自分は、ここで記す一連の解析がRNA-seqのデータの解析の基本であると考えている。もしこのRNA-seq解析の目的が、唯のデモンストレーション、例えば、あるタンパク質のノックダウンの効果やそのパスウェイなどが既に他の実験である程度証明出来ており、それを更にRNA-seqで証明するだけ、のような場合、ここに記す基本的な解析で十分な気がする。時間があればそれ以上深堀しても良いのだが、ただ複雑になって説明しにくくなるだけな気がする。
上記とは逆に、探索的なことを行うことで候補となるメカニズムの当たりをつけに行くような場合は上記のような遺伝子発現解析とパスウェイ解析だけでは足りない可能性がある。この場合、自分であればPCA(Principal Component Analysis)やクラスタリングによる候補遺伝子の抽出、転写因子ネットワークの解析、Immune deconvolutionによる腫瘍内免疫環境の推定などを行う。
なお、GSEAに関しては過去に書いているので、それらだけが知りたい場合はそちらの方が有用かもしれない。そこではBroad InstituteのGSEAを使った解析について書いている。他にもシングルサンプルのGSEA(pre-ranked)やRを使ったsingle sample GSEAを書いている。
今回の解析の目的
今回の解析はヒトではなくマウスである。マウスのアノテーションや情報はヒトよりも随分と少ないので、この点はちょっと気をつける必要がある。
今回の解析はシンジェニックモデルから得たいくつかのマウス腫瘍とその宿主(マウス)の全血のRNA-seqであり、その担がんマウスにとある薬剤(CPD)を投与し、そのコントロールであるDMSOと比較する、という実験である。このあたりの薬剤は都合上詳しく言えないが、ここにある方法を参考にする場合は、該当する部分を自分たちの関心のある薬剤に置き換えれば良い。
そして、今回の目的は、とあるパスウェイが薬剤投与群でちゃんと動いているかどうか説明することである。もはや、説明ができ次第、それで解析終了と言える。上記でも述べた通り、このような目的であれば、深追いの探究的な研究は必要ないと言える。深追いの解析をしていると色々な発見があって面白いのだが、そこは時間とか、他のやることとか、コンピューターリソースとか、ライセンスとか、更には費用とはも考えて、色々なバランスで行うか行わないかを決めなければならない。
edgeRとDESeq2の違いとそのどちらを使うか
edgeRもDESeq2も似たような計算方法を使っている(と思う)が、自分は主にedgeRを使っている。最終的に個人の好みでDESeq2とedgeRを使い分ければ良いと思っている。
edgeRとDESeq2の違いの一つは正規化(normalization)の方法である。edgeRはTMM(Trimmed Mean of M values)正規化、DESeq2はRLE(Relative Log Expression)正規化を使っている。TMM正規化は変動の少ない遺伝子を選んできてそれを使って正規化する方法、RLE正規化は遺伝子発現量(カウント)の幾何平均で各遺伝子発現量を割って、その中央値で正規化する方法である。正規化について考えてみると、edgeRもDESeq2も、対象とする群にしか使えないんだろうなってことが分かる。だからTGCAにアップロードされているデータはTPM(Transcripts Per Million)なんだろう。
DESeq2とedgeRの主な差別化点としては、DESeq2はきっちり2群の比較しか出来きないが、edgeRの方はより柔軟に解析することができるところと考えている。edgeRは2群の比較にしてもいくつか選択肢(たしかDESeq2もあったと思う)があり、さらに、Anova-likeな検定で複数群の比較も可能である。これはDESeq2にはない利点である。Anova-likeな検定では、複数群で発現に差がある遺伝子はわかるが、どの群で違っているのかはわからない点には注意が必要である。それに加えて、言うても厳密にアルゴリズムを合わせて計算したことは実はないし、詳しい理由を説明する力が自分にはないので確かな保証は出来ないのだが、自分の経験上edgeRの方がDESeq2よりも多くのDEGを拾うことができるように思う。このあたりはおそらく計算に依って異なるのだろうから、アルゴリズムを合わせて計算してみたら、逆にDESeq2のほうが多かった、とかいう場合もあるだろうと思う。その場合は、そうなんだろう。RNA-seqの解析は、大量のデータから候補を拾ってくるあたり、ある意味スクリーニングのような意味合いもあるので、候補遺伝子は多い方が良く、そこから関連のあるパスウェイだって多く拾ってくることが出来るはずである。依って、自分は経験上多くのDEGを拾うことができるedgeRを使うことにしている。以前はDESeq2を使っていたのだが、最近ではどうもedgeRのほうが目的にRNA-seqの目的に合っているように思っている。上記の理由から、様々な状況に対応できるためである。「あの解析ではDESeq2を使ったけど、この解析ではedgeRを使いました」という場合があったが、その時「それやったら前の解析もedgeRでやったほうが良かったかもな….」と思っていた。だったら、はじめから柔軟な解析が可能なedgeRである。
Gene set enrichment analysisとOverrepresentation analysisの違い
パスウェイ解析として主にGSEA(Gene Set Enrichment Analysis)とOverrepresentation Analysisがある。前者は、解析したい遺伝子群(対象となる遺伝子・クエリとして入力する遺伝子群。例えば、2群を比較して統計的有意に発現に差があった遺伝子群)を発現の高い順から低い順に並べて、解析したい遺伝子の発現量が、事前に用意したあるパスウェイに属する遺伝子群(Gene Set)に対しどのくらい寄与しているか、ということを種々の方法により計算し、どちらの群(たとえば薬剤投与群、またはDMSO群)で、どのパスウェイ(Gene set)がより動いているか、働いているか、機能しているか等を評価する方法である。
一方、Overrepresentation AnalysisはGSEAよりも直感的でシンプルな解析であり、解析したい遺伝子群が、どのパスウェイにどれくらい所属しているかを解析する方法を採る。解析結果を見ればなんとなくわかるが、Overrepresentationという呼称は、解析したい遺伝子群が、例えば、GOやKEGGなどに登録されたパスウェイを構成する遺伝子のうちどれくらいを占め、それが他のパスウェイに比べてどれだけ確かかを検定しているところから来ているのだろう。主観的なイメージではあるが、Overrepresentationとは、その言葉通り、「多く(over)に代表とされる(represetation)」パスウェイを見つけるための解析といった感じである。こういった解析なので、Overrepresentation Analysisでは、各遺伝子の発現量やランクは特に必要ない(あってもいいが、それはもうGSEAみたいな計算になるはず。ここで使用するclusterPrifilerは発現量をノード(点・各遺伝子)に色を付けるために使用する。)。一方、GSEAは発現量の値が必要である。この当たりが、GSEAとOverrepresentation Analysisの違いである。
計算方法の違いは、すなわち、使い方の違いでもある。GSEAは対象となる遺伝子の発現量の値を使ってあるパスウェイへの寄与を調べてるので、Overrepresentation analysisよりも解析するための条件が多い(発現量が必要である)。発現量がすごく低かったり、解析したい遺伝子群が少なすぎたりすると上手いこと計算できないし、どちらかと言えば、Overrepresentation Analysisよりも厳しい解析になる用に思う。特に、ここで使用するBroad InstituteのGSEAというソフトは、さらにサンプル間でPermutation testを行うという徹底ぶりである。逆に言えば、GSEAでヒットしてきたパスウェイと、他の実験データとの一貫性があれば、それはもうそのパスウェイの関連性が非常に高いものと考えることが出来ると思う。一方、Overrepresentation Analysisは、各遺伝子がどのパスウェイに所属しているのか程度しかわからないように思う。だから、GSEAに比べてヒットしてくるパスウェイの数は圧倒的に多い。しかしながら、たとえば、細胞Aでは細胞Bに比べて本当に現象Xに関するパスウェイが深く関わっているかどうかを単に示すだけ、のような解析ではOverrepresentation Analysisでも良くて、むしろ直感的に分かりやすくて良いと考えている。その理由は、Overrepresentation Analysisは、比較群に対して何らかの顕著な違いをもつ遺伝子、例えば、RNA-seqであればDEGを使用するため(これはGSEAでもそうであるが。そうしないと絞り込みが非効率である。)である。逆に、そういったフィルターなしにOverrepsesentation Analysisを使う意味はほとんどない。Research Misconductにならないようならば、この辺は上手く使っていけば良いと考えている。
前置きがかなり長くなってしまったが、以下からRを使って解析を進めていく。Rはかなり会話的なソフトなので、各ステップ毎に何をやったかを説明していく。
使用するパッケージ
この解析では以下に記すパッケージを使用する。遺伝子発現解析に必要なものとしてはedgeR、clusterProfiler、org.Mm.eg.db、rtracklayerである。他は図示したり(beeswarm)、表を整えたり(tidyverse)とかそういったパッケージであり、もし他に必要なパッケージがあれば適宜、追加すれば良いと思う。
# DEG
library(edgeR)
# enrichment analysis
library(clusterProfiler)
library(VennDiagram)
# library(ggVennDiagram)
library(EnhancedVolcano)
# basic
library(ggrepel)
library(beeswarm)
library(org.Mm.eg.db)
library(doParallel)
library(foreach)
library(pheatmap)
library(rtracklayer)
library(rvest) # this is for extraction of html table; results of leading edge analysis.
library(xml2) # this is for extraction of html table; results of leading edge analysis.
library(openxlsx) # to read xlsx file.
library(ComplexHeatmap)
library(tidyverse)
library(data.table)
library(gridExtra)R必要なデータセットの読み込み
まずは、必要なデータを読み込む。これが無くては埒が明かない。
rtracklayerによりGTFファイルを読み込む
最初に、GTFファイルを読み込む。GTFファイルは、ゲノム上のどの位置にどの遺伝子があり、それはどんな遺伝子名なのか等の情報が書かれたアノテーションファイルである。これは、リードのマッピングのときに使用したものと同じものを使う必要がある。ヒトでもマウスでも、自分は通常、GENCODEのものを使用している。このrtracklayerがあれば、bashのAWK等を使わずとも、R内でデータフレームで処理できるので、このパッケージはある意味では最も有効かつ必須であると個人的に思っている。いうてもここではensenblの遺伝子IDと遺伝子名を取ってくるだけなのでAWKを使う必要があるとしてもそんなに大変ではないのだが。如何せん、R内で処理が出来るのがとても良い。AWKだったら遺伝子IDと遺伝子名のファイル、他には、遺伝子の長さなどが考えられるが、それらを書いたファイルを一つ作る必要が出てくる。それが無いだけで便利である。
GTFファイルには、各遺伝子の長さの情報もある。これは例えばTPMを計算するときに有用である。他にも、興味深い情報が沢山載せてある。
# Read GTF.
gtf <- readGFF("~/reference_seq/gencode.vM36.primary_assembly.annotation.gtf")
gene_id_gene_name <- gtf %>% select(all_of(c("gene_id", "gene_name")))%>% distinct(gene_id, .keep_all = TRUE)Rリードカウントのデータを読み込む
ここでは、featureCountsにより出力されたリードカウントのデータ(ファイル名をcount.txtとする)を読み込む。使用するのはtidyverseのread_tsv()である。featureCountsの出力(ここではcount.txt)には、一行目に使ったbashのコマンドが書かれている。よってreat_tsv()にskip = 1と入れて、その1行目をスキップして読み込む必要がある。そうすれば、データフレームとしてリードカウントの結果を読み込むことが出来る。必要なデータは1列目のgene_idと3列目以降なので、select(-c(2:6))で不要な列を削除しておく。
しかしながら、もう一点鬱陶しいところとして、列名がfeatureCountsに与えたsorted.bamのファイルパスになっているところである。こんな長々しい列名で今後解析するのは非常にナンセンスであるので、列名は出来る限り短く、その検体をよく表す名称に変更しておく。
# Read original data.
count_original <- read_tsv("/mnt/storage/Data/output_featurecounts/count.txt", skip = 1)
# Remove several columns that will not be used for further analysis
count_original_2 <- count_original %>% select(-c(2:6))
# Rename the columns into simple one as possible as can be.
colnames(count_original_2) <- c("gene_id", "T1", "T2", "T3", "T4", "T5", "T6", "T7", "T8", "T9", "T10", "T11", "T12", "T13", "T14", "T15", "T16", "T17", "T18", "T19", "T20", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12", "B13", "B14", "B15", "B16", "B17", "B18", "B19", "B20")RQC
これは、発現解析に直接利用することは無いが、これから解析しようとするライブラリがどんなものかを説明するのに役立つし、もしクオリティーに問題があるファイルが見つかった場合はその原因を探るためにも重要なステップと言える。言うても、そんなにガサガサなライブラリなんてのは、そもそも余程汚いサンプル(長期間ホルマリンに浸ってしまったFFPE(Formalin Fixed paraffin Enbedded)のサンプルなど)をシークエンスに回さない限り、そうそう手に入らないような気もするけど。そして、今回、そのガッサガサなサンプルを解析することになってしまった。そんなときは、同一のモデルを一緒に比較して良いかどうか示すために、以下に記す解析、特にPCA(Principal Component Analysis)なんかは、見かけだけだとしても有用である。以下の解析の他に行うとしたら、RSeQCを用いた解析だろう。あるサンプルがDEG解析に影響を及ぼしているような場合、そのサンプルをRSeQCで解析して他との違いを示せば良い。最悪、その結果を元にそのクオリティーの悪いサンプルを解析から外す、などの処理が可能である。
ライブラリサイズの確認
リードカウントを読み込んだら、次は、念の為にQCを行う。QCといってもそんなに大したことをする必要は無いと思うが、一応、ライブラリサイズは確認しておいたほうが良いだろうと思う。ライブラリと言うのは、cDNAライブラリとか、そういうときに使うライブラリの事である。沢山の遺伝子情報を集めた一つの情報とでも言うべきだろうか。そのリードカウントの合計値がライブラリサイズとなる。最初に読み込んだRのライブラリとは違うので、ちょっと注意が必要である。
後ほど、全サンプルに渡ってカウントが0だった遺伝子を除去する、というステップを入れる。これをrowSums()を使って行っておく。別に後に行っても良いが、自分は全サンプルでカウントされなかった遺伝子の除去は必ず行うので、もう最初にやってしまう。
# QC to understand situation of the libraries
check_lib_size <- colSums(count_original_2[,2:ncol(count_original_2)]) %>% data.frame() %>% rename("library_size" = ".")
# Calculate total read count regarding to each gene over all sample.
gene_count <- rowSums(count_original_2[,2:ncol(count_original_2)])%>% data.frame() %>% rename("gene_count_over_sample" = ".")
rownames(gene_count) <- count_original_2$gene_idR階層的クラスタリング
全検体が同じ土俵で解析できるかどうか確認する必要もありそうだ。そのためには、簡単なところで階層的クラスタリング、多少なりとも解析した感のあるPCA(Principal Component Analysis)か便利だろうと思う。同一グループの検体、例えば、腫瘍AのDMSO投与群がみんな一つのクラスターに居る、という状況が望ましいだろう。そうなれば、一つの群として似たような性質を持つライブラリであると見なすことができるはずである。
# Check which sample is largely difference from other one.
## Prepare dataset for hclust
count_original_2_transpose <- count_original_2 %>% t() %>% data.frame()
colnames(count_original_2_transpose) <- count_original_2_transpose[1,]
count_original_2_transpose <- count_original_2_transpose[-1,]
# hierarchical clustering
check_cluster <- hclust(dist(count_original_2_transpose), method = "ward.D2", members = NULL)
plot(check_cluster)RPCA(Principale Conponent Analysis)
もう一つの検体のチェック方法として、PCA(Principal Component Analysis)がある。上記の階層的クラスタリングは、サンプルがどれくらい似ているかだけ見ることが出来るが、それに加えてPCAでは、どの遺伝子の寄与が大きいのか、ということまで解析することが可能である。ただし、この段階どそれは必要ないと思うが。PCAを使うここでの目的は、例えば「腫瘍Aではここクラスター、腫瘍Bではここのクラスターに分けられていて、お互いにごちゃごちゃに位置していることは無いので、ライブラリ調整やシークエンスがちゃんと出来ているな。よし、じゃあ安心して以降の解析に進むか…」的なことを視覚的に確認することである。これは以下のコードを流すだけなので、今後のためにやったほうが良いように思う。
注意点としては、一般的にはprcomp()でscale = TRUEを使った方が良いが、ここでは最初にFALSEでPCAを行っている点である。これはデフォルトでFALSEである。scale = TRUEにすると、相関係数行列を使ったPCAになる。PCAを勉強しているとき、相関行列を使わない意味がないと思った記憶があるので、自分はここはいつもTRUEである。そっちの方が途中の計算が圧倒的に分かりやすくなった記憶があるし、結果で示されるドットのバラツキ感も減る。prcomp()のマニュアルでも、scale = TRUEがadvisableである。
ということで、ここはscale = TRUEを入れるべきである。しかしながら、このcount_original_2(単に列名を変えただけのデータセット)はゼロを多く含んでいるようで、ここをTRUEにするとエラーが返って来てしまった。相関行列が計算出来なかったのだろう。なので、最初はFALSEで行っている。後述でどのサンプルでもカウントされなかった遺伝子を除いたデータセット(count_original_4)を作成するが、それだとscale = TRUEを入れてもエラーが出なかったし、予想通りそうした方がよりクラスターの結果がリーズナブルに見えた。雰囲気的に言えば「データセットの複雑性が減ってより次元削減が上手くいった」って感じである。おそらくバラツキが減るためだろう。シングルセルRNAシークエンスのようなあまりにも多次元なデータを解析したことのあるひとならこの雰囲気をわかってくれるはず。
text()のところであるが、一昔前ならばmaptoolsというパッケージを使えば良かったが、このパッケージは既にCRANから削除されていて、かつ、最近のRにはソースからのでさえインストール出来なくなっていた(自分は。2022年のときはインストール出来たのだが…頑張れば出来そうであるが、そんなことを頑張る価値は無い気がしてやらなかった)。なので、ベースのシステムにあるtest()という関数を用いることにする。これで十分。きれいなグラフが必要ならば、ggplot2を使ったほうが良いと思う。
# PCA for count_original_2
## prepare dataset
count_original_2_transpose <- apply(count_original_2_transpose, 2, as.numeric) %>% data.frame(row.names = colnames(count_original_2[,2:41]))
# Calculate PCA
pca_count_original_2 <- prcomp(as.matrix(count_original_2_transpose))
# Check contribution
plot(pca_count_original_2)
# Check each PC
plot(pca_count_original_2$x[,2], pca_count_original_2$x[,3], pch = 16, col = c(rep("red", 5), rep("blue", 5), rep("lightblue", 5), rep("orange", 5)))
# Add label for each dot
text(pca_count_original_2$x[,2], pca_count_original_2$x[,3], labels = rownames(count_original_2_transpose), pos = 4, cex = 0.7, col =c(rep("red", 5), rep("blue", 5), rep("lightblue", 5), rep("orange", 5)))
# Plot # ggplot2 version for count_original_2
pca_count_original_2_ggplot <- data.frame(
"PC2" = pca_count_original_2$x[,2],
"PC3" = pca_count_original_2$x[,3],
"color" = c(rep("red", 5), rep("blue", 5), rep("lightblue", 5), rep("orange", 5)),
"label" = rownames(count_original_2_transpose)
)
ggplot(pca_count_original_2_ggplot, aes(x = PC2, y = PC3, color = color, label = label)) +
geom_point(size = 3) +
geom_text_repel(size = 3) +
scale_color_identity() +
labs(x = "PC2", y = "PC3") +
theme_classic()Rこうやってざっとライブラリの確認を行ったわけであるが、しかしながら貴重なサンプルなどは、それがどれだけ汚いライブラリで、同じ群のライブラリが同一クラスターに居ない場合でも、解析を止めることは出来ない(検体がかなり多い場合は可能だろうけど、もったいないと思う)。そうなってしまったとしても、解析を続ける必要がある。この辺りの解析は、後の解析においてトラブルがあった場合にその情報を使って色々と考察できるようにするための用意、と個人的に考えている。
もしここであまりにも異常な検体が見つかった場合、RSeQCなどでBAMファイルのQCが出来るので、それをやってみれば良い。案外、原因が見つかる場合が多い。例えば、B4のライブラリだけ孤立したところにクラスタリングされてきた、という場合、RSeQCのgeneBody_coverage.pyを使ってみたら、なんかシークエンス中にcDNAが分解されていそう考えることが出来るのではないかと、だった、とか、B4のライブラリサイズだけ顕著に少なかった、とか、そういうことがわかったりする。
どの検体でも検出されなかった遺伝子の除外
ここで案外重要と考えていることは、あまりにもリードカウントの低い遺伝子は削除する必要がある、ということである。edgeRもDESeq2も、正規化(Normalization)後に負の二項分布を使ったモデリングを行うので、全検体に渡って検出されていない遺伝子を無下に除いてしまうのは実は良くないのかもしれない。しかし、edgeRやDESeq2などのマニュアルでもカウントの低いリードを除いていたりするので、それにしたがってここでも除くことにする。正直自分はモデリングなどには疎いので、その当たりの話を正確に説明する自信はないのだが、低いリードカウントの遺伝子は後のDEG(Differentially Expressed Gene)の結果や、その後のqPCRによるバリデーションなんかに影響するように思う。例えば、ある遺伝子がFDR補正後のp値が0.05以下でDEGとして検出されたとしよう。しかし、そのカウント数が実は5しかなかったとしたら、どうだろうか。それはつまり、そのエクソンにマップされたリードが5個しかなかったということであり、そんな低いカウントを遺伝子が確かに発現しているとして考えることは妥当だろうか。低カウントリードを除くということは、そういった実際には検出が出来なそうな遺伝子を除くといことである。しかしながら、解析に含める最低のカウント数を5にするか、10にするか、20にするか、50にするかはなかなか判断が難しいことである。なにか基準となる数値が必要である気はする。自分は、全検体での総和が0の遺伝子は除く。よく「あの遺伝子は??」とか、しょーもない質問をしてくる連中が居るためだ。関心のある遺伝子があるだけで、連中を多少納得させることが出来る。解析的には、全検体での総和が全検体数未満もしくは比較群のN数以下の遺伝子を除くのも良いかも知れない。
そのために、ここでは検体に渡るカウント値の合計をrowSums()で算出し、そのヒストグラムをざっと見てみる。ヒストグラムにより良い落とし所が見つかればそれで良い。上述の通り、ここでは検体に渡るカウント値の合計が0の遺伝子、すなわち、どの検体でも検出されなかった(リードがマッピングされなかった)遺伝子を除くことにする。この当たりは適宜変える必要がある。
解析に使えそうなデータセットcount_original_4が出来上がったら、念のために、上記でも行ったPCAを行って検体間の類似性は確認した方が良いと思う。これは、なにかトラブルがあった場合、それがライブラリに起因するかどうかを確認するためでもある。データセットから全検体で検出されなかった遺伝子、すなわち、全検体に渡ってゼロだったカウントを除いたのでprcomp()がscale = TRUEを受け付けるようになっている。上記と同様、plot()を使った組み込み関数版と、ggplot2を使った外向け版である。
# Remove count zero genes over all samples.
count_original_3 <- count_original_2 %>% column_to_rownames("gene_id")
count_original_4 <- count_original_3[rowSums(count_original_3) != 0,]
# Check each gene using histgram.
hist(gene_count$gene_count_over_sample, breaks = 1e9, xlim =c(0, 1e5), ylim = c(0, 300))
hist(gene_count$gene_count_over_sample, breaks = 1e9, xlim =c(0, 1e5), ylim = c(0, 50))
# PCA, for count_original_4
count_original_4_transpose <- count_original_4 %>% t() %>% data.frame()
pca_count_original_4 <- prcomp(as.matrix(count_original_4_transpose), scale = TRUE)
plot(pca_count_original_4)
plot(pca_count_original_4$x[,2], pca_count_original_4$x[,3], pch = 16, col = c(rep("red", 5), rep("blue", 5), rep("lightblue", 5), rep("orange", 5)))
text(pca_count_original_4$x[,2], pca_count_original_4$x[,3], labels = rownames(count_original_2_transpose), pos = 4, cex = 0.7, col =c(rep("red", 5), rep("blue", 5), rep("lightblue", 5), rep("orange", 5)))
# Plot # ggplot2 version for count_original_4
pca_count_original_4_ggplot <- data.frame(
"PC2" = pca_count_original_4$x[,2],
"PC3" = pca_count_original_4$x[,3],
"color" = c(rep("red", 5), rep("blue", 5), rep("lightblue", 5), rep("orange", 5)),
"label" = rownames(count_original_4_transpose)
)
ggplot(pca_count_original_4_ggplot, aes(x = PC2, y = PC3, color = color, label = label)) +
geom_point(size = 3) +
geom_text_repel(size = 3) +
scale_color_identity() +
labs(x = "PC2", y = "PC3") +
theme_classic()RedgeRによる遺伝子発現解析
必要なデータセットの読み込みやQCは上記でOKなので、次はedgeRで2群間で発現に違いのある遺伝子を見つけに行く。
edgeRの準備
今回の実験では、マウス乳がん細胞株4T1(Rでは変数の頭を数字にすると変数として使えないので、4T1はFT1とした。)とEO771を使い、それをシンジェニックマウスに移植し、その担がんマウスに対してDMSOと薬剤であるCPD(Compound)を投与し、数日後にその薬効のメカニズムを説明するために腫瘍と血液を採取し、それぞれから回収したRNAを使ってRNA-seqを行っている。なので、比較群としては、CPM vs DMSOの2群の比較をそれぞれ、4T1、EO771、腫瘍(tumor)、血液(全血)(blood)の4グループとなる。それを定義するデータフレームを先ず作成する。
正直言えば、最初からcomparisonの列のようなデータを入力しても良かったように思う。正直、他のtumor、tissue、drugという列は、今後使用しないためである。各列(tumor、tissue、drug)はrep()を使って繰り返し4T1やEO771といった値を持つベクトルを生成し、それを各列に入れている。comparison列を作るためにuniteを作成している。uniteでは読み込んだRライブラリの都合上コンフリクトが発生しそうだったので、tidyr::によりライブラリを指定している。
ここでは、factor()によってデータの順番を決めておく。これは、グラフなどにしたとき、その横軸、もしくは場合に依っては縦軸がアルファベット順になってしまうのを防ぐために、任意の順番で指定するという目的がある。
group <- data.frame(
"tumor" = rep(x = c("FourT1", "EO771", "FourT1", "EO771"), each = 10),
"tissue" = rep(x = c("tumor", "blood"), each = 20),
"drug" = rep(x = c("CTRL", "CPD", "CTRL", "CPD"), each = 5, times = 2)
)
rownames(group) <- colnames(count_original_4)
group <- group %>% tidyr::unite(c("tumor", "tissue", "drug"), col = "comparison", sep = "_", remove = FALSE) %>% data.frame()
group$comparison <- factor(group$comparison,
levels = c("FourT1_tumor_CTRL", "FourT1_tumor_CPD", "EO771_tumor_CTRL", "EO771_tumor_CPD", "FourT1_blood_CTRL", "FourT1_blood_CPD", "EO771_blood_CTRL", "EO771_blood_CPD"),
labels = c("FourT1_tumor_CTRL", "FourT1_tumor_CPD", "EO771_tumor_CTRL", "EO771_tumor_CPD", "FourT1_blood_CTRL", "FourT1_blood_CPD", "EO771_blood_CTRL", "EO771_blood_CPD"),
exclude = NA, ordered = is.ordered(group$comparison), nmax = NA)R作成するデータフレームとして、以下の写真のようなものが出来上がる。全体のスクリーンショットは撮れていないが、下の方にbloodのデータもある。

edgeRによる正規化・Dispersionの計算・負の二項分布へのフィッティング
ここがedgeRの肝の部分である。しかし、正しく使うためにマニュアルを読んでも、イキって原著論文を読んでも、全くもって何言ってっかわからん。なので、もしかしたら以下の説明は、正しいようで間違っている可能性もある。「こんな感じで計算している」とは言えると思う。
DESeq2もedgeRも、各遺伝子のカウントの平均値と分散(Dispersion)を求め、それらを使って負の二項分布に当てはめて各遺伝子のカウントの期待値を各群で求め、その期待値を使って検定する。それを求めるのが以下のコードになる。
最初にDGEListというカウントデータ(count_original_4)をedgeRで取り扱うためのオブジェクトを作る。次にそのオブジェクトをcalcNormFactors()に入れて正規化する。次に、model.matrix()を使って、検定するためのデザイン行列というものを作成する。これはすなわち、群間が同じモデルで示されるのか、違うモデルになるのか設定するところである。これを作成することで群間の検定が出来るようになる(帰無仮説を棄却できるようになる)。シンプルな2群ならば~groupを入れるようだ。これによりデザイン行列の一列目が1になる。また~0+groupを入れるとデザイン行列の1列目が0になる。この場合、後になってどの群とどの群を比較するのか、定義する必要になる。個人的には後者の方が比較群をはっきりさせることが出来て良いのではないかと考えている。
そして、estimateDisp()で各遺伝子から分散を求め、plotBCV()で分散とカウントの期待値はどんな感じか確認して、その平均と分散を使ってglmQLFit()により分布にフィッティングを行う。
# Prepare DGEList object
y_raw <- edgeR::DGEList(count_original_4, group = group$comparison)
# Normalization
y_raw <- calcNormFactors(y_raw)
# Set comparison pairs
design <- model.matrix(~0+group, data=y_raw$samples)
# Calculate dispersion
y_raw <- edgeR::estimateDisp(y_raw)
# check dispersion on graph
plotBCV(y_raw)
# Fitting to NB for analysis of raw count data.
fit_raw <- glmQLFit(y_raw, design = design, robust=FALSE)RedgeRによる2群間比較でDiffernetially Expressed Gene(DEG)を求める
フィティングが終わったら検定である。その前に、比較する群を指定する必要がある。そのデザイン行列に-1と1かけ、必要のない群には0をかけることで2群を比較する。それが# define comparison pair againとコメントアウトしている部分になる。このベクトルを次のglmQLFTest()で使用することで、目的の群間(2群)の比較を行うことが出来る。ちなみに、againとコメントしたのは、フィッティング時にデザイン行列を一度作成しているため(model.matrix(~0+group, data=y_raw$samples)の部分)である。
上述のように、edgeRでもDESeq2でも、得られた遺伝子の平均値と分散を使って負の二項分布にフィッティングし、それにより得られたリードカウントの期待値を使って検定する。なぜわざわざこんなことするかというと、その理由は技術的、生物学的なバラツキなどの影響を軽減できるためらしい。
でも結局のところ、ここで統計的有意差ありとなったDEGが本当に実際のサンプルで発現が違うかどうか、シークエンスに使った同じ検体のRNAや、それが難しいならば同様の実験で得られたRNAを定量PCRを行うことで結果の検証するから、この辺りに絶大なこだわりを持つのはモデリングが好きな生物統計家に任せてしまっても良いと思う。
glmQLFTest()は分散が大きいライブラリを扱う場合の偽陽性のコントロール(ここで言るコントロールは対照ではなく制御という意味)なんかに有効らしいので、RNA-seq全般に適用可能らしい。検定の方法としては他にexactTtest()なんかもあるので、N数が5程度と多くなく、サンプルの分散(共分散)が小さい場合は、それも試してみるべきである。
また、この手のデータはなんでもかんでも負の二項分布へのフィッティングが良いと思うのも、良くないと言える。たとえば、NanostringのnCounterとかである。あれはedgeRやDESeq2なんかよりもlimmaとか他のRパッケージの使用も検討すべきである。nCounterとかは検出にプローブを使っており、遺伝子数も昨今のRNA-seqよりもかなり少なく、カウントの様子もRNA-seqと少し違うためである。
# define comparison pair again.
FT1_tumor <- c(-1, 1, 0,0,0,0,0,0)
EO771_tumor <- c(0,0,-1,1,0,0,0,0)
FT1_blood <- c(0,0, 0,0,-1, 1, 0,0)
EO771_blood <- c(0,0,0,0,0,0,-1,1)
# Calculate DEG
qlf_FT1_tumor <- glmQLFTest(fit_raw, contrast = FT1_tumor)
qlf_EO771_tumor <- glmQLFTest(fit_raw, contrast = EO771_tumor)
qlf_FT1_blood <- glmQLFTest(fit_raw, contrast = FT1_blood)
qlf_EO771_blood <- glmQLFTest(fit_raw, contrast = EO771_blood)RDEGを抽出する
発現量に統計的有意差のある遺伝子はtopTags()で出力可能である。しかしながら、トップ何位の遺伝子というのは後からいくらでも拾ってくることが出来るので、ここでは全部の遺伝子に関する値を出力する。そして、FDRの補正方法としてはBH(Benjamini-Hochberg)法一択である。これでlogFC、logCPM、F値、FDRが出力される。logFCはglmQLFTest()の直前にセットした群、ここではlog2(CPD/DMSO)、logCPMはカウント値の平均のlog2変換である。
今後必要になってくるのは、この出力結果のひとつであるFDRの値、FC(Foldchange, logFC)の値、遺伝子IDもしくは遺伝子名である。しかし、よく見てみれば遺伝子名、ここではgene_nameが入っていない、なので、それを入れることにする。gene_id_gene_nameはrtracklayerでGTFファイルを読んだときに一緒に作ったデータフレームである。それには遺伝子ID(gene_id)と遺伝子名(gene_name)が入っているので、それをDEGの結果(deg_FT1_tumor)とinner_join()することで遺伝子名を追加する。そしてこれを各群の比較の結果に対しても行う。
念の為にこれらの結果をリストに入れておく(#5の部分)。後に同じ処理を適応することがわかっているならば、このようにリストに入れてしまったほうが便利である。for()で繰り返せば似たようなコードをコピーペーストする必要がなくなる。
# DEG results with FDR correction
# 1
deg_FT1_tumor <- topTags(qlf_FT1_tumor,
n = nrow(qlf_FT1_tumor$table),
adjust.method = "BH") %>% data.frame() %>% rownames_to_column(var = "gene_id")
deg_FT1_tumor <- inner_join(deg_FT1_tumor, gene_id_gene_name, by = "gene_id")
deg_FT1_tumor %>% readr::write_tsv(file = "~/output/qlf_FT1_tumor.tsv", col_names = TRUE)
# 2
deg_EO771_tumor <- topTags(qlf_EO771_tumor,
n = nrow(qlf_EO771_tumor$table),
adjust.method = "BH") %>% data.frame() %>% rownames_to_column(var = "gene_id")
deg_EO771_tumor <- inner_join(deg_EO771_tumor, gene_id_gene_name, by = "gene_id")
deg_EO771_tumor %>% readr::write_tsv(file = "~/output/qlf_EO771_tumor", col_names = TRUE)
# 3
deg_FT1_blood <- topTags(qlf_FT1_blood,
n = nrow(qlf_FT1_blood$table),
adjust.method = "BH") %>% data.frame() %>% rownames_to_column(var = "gene_id")
deg_FT1_blood <- inner_join(deg_FT1_blood, gene_id_gene_name, by = "gene_id")
deg_FT1_blood %>% readr::write_tsv(file = "~/output/qlf_FT1_blood.tsv", col_names = TRUE)
# 4
deg_EO771_blood <- topTags(qlf_EO771_blood,
n = nrow(qlf_EO771_blood$table),
adjust.method = "BH") %>% data.frame() %>% rownames_to_column(var = "gene_id")
deg_EO771_blood <- inner_join(deg_EO771_blood, gene_id_gene_name, by = "gene_id")
deg_EO771_blood %>% readr::write_tsv(file = "~/output/qlf_EO771_blood.tsv", col_names = TRUE)
#5
deg <- list()
deg[[1]] <- deg_FT1_tumor
deg[[2]] <- deg_EO771_tumor
deg[[3]] <- deg_FT1_blood
deg[[4]] <- deg_EO771_bloodRGSEAやOverrepresentation analysisで使用するのは、主に発現に統計的有意差があった遺伝子(DEG)だけなので、それを抽出したデータフレームも作成し、それもリストに入れておく。有意水準としてはFDR <0.05で良いと思う。もっと厳しく0.01とかでも有りだろうけど、厳しくして拾えるDEGの数が少なすぎたりするとそれも問題なので、ここは多くても良いと思う。狙っている遺伝子や遺伝子群がその有意水準で拾うことが出来れば、それで良い。しかし、だからといって0.1とかにするのは、別にいいけど、何か条件がなければ論文に投稿することは出来ないだろう。
deg_FT1_tumor_0.05less <- deg_FT1_tumor %>% filter(FDR < 0.05)
deg_EO771_tumor_0.05less <- deg_EO771_tumor %>% filter(FDR < 0.05)
deg_FT1_blood_0.05less <- deg_FT1_blood %>% filter(FDR < 0.05)
deg_EO771_blood_0.05less <- deg_EO771_blood %>% filter(FDR < 0.05)
deg_0.05less <- list()
deg_0.05less[[1]] <- deg_FT1_tumor_0.05less
deg_0.05less[[2]] <- deg_EO771_tumor_0.05less
deg_0.05less[[3]] <- deg_FT1_blood_0.05less
deg_0.05less[[4]] <- deg_EO771_blood_0.05lessR正規化(Normalization)されたデータを出力する
この正規化されたカウントデータの値は、後述のGSEAで使用する。また、もしかしたら関係者で共有する必要も考えられるため、一応出力しておく。edgeRのcpm()で正規化されたカウント値を出力することが出来る。しかしながら、ここでもgene_nameが抜けているので、上記と同じようにinner_join()を使って遺伝子名を付ける必要がある。また、今後のためにこれらもリストに入れておく。
# Output normalized count
normalized_raw_counts_df <- cpm(y_raw, normalized.lib.sizes = TRUE,
log = FALSE) %>% data.frame() %>% rownames_to_column(var = "gene_id") %>%
write_tsv("~/output/normalized_raw_counts_df.tsv",
col_names = TRUE)
# Add gene_name
normalized_counts_gene_name <- inner_join(normalized_raw_counts_df, gene_id_gene_name, by = "gene_id")
# Store the dataframe of each comparison group to list
normalized_counts <- list()
normalized_counts[[1]] <- normalized_counts_gene_name[,1:11]
normalized_counts[[2]] <- normalized_counts_gene_name[, c(1, 12:21)]
normalized_counts[[3]] <- normalized_counts_gene_name[, c(1, 22:31)]
normalized_counts[[4]] <- normalized_counts_gene_name[, c(1, 32:41)]Rここでは、正規化されたカウント値を使って、自分でDMSO群もしくはCPD群のカウント値の平均値(mean_DMSO, mean_CPD)、FC(Fold-change)、Log2FCを求め、次のステップで行うvolcano plotとMA plotでDEGに色をつけるための色(red、blue、black)をセットする。以降の処理はこのように同じ処理を各グループに適用していくことが多く、リストが有用になる。こうしなければコードの量が凄く増えて大変なことになる例えば、ここではグループが4つあるが、それをリストで一気に処理しない場合はその4倍になる。シングルセルRNAシークエンスなんて、10個以上の細胞種がクラスタリングされてくるわけであり、それに対してリストを使わなかったら、10倍にも20倍にもコードが膨れ上がる。同じコードが続いたときのコピーペーストの眠たさと言えば地獄だし、それにエラーが起きたときは、全部同じに見えることからその確認も地獄だし、エラーが起こらなかったとしても、1ヶ月後にコードを見直すときも改めてゾッとしてしまう。
関数に依ってはリストにするとどうしてもうまく動かないものもなるので、そういうときはしゃあなしでリスト無しで解析しなければならない。また逆にリストにするとわかりにくくなる場合があるので、そういうときもリストなしの方が良いかも知れない。可読性ってのも、再現性を高めるためには重要である。
# calculate mean, FC, and label DEGs with red, blue or black.
for(n in 1:length(normalized_counts)){
normalized_counts[[n]] <- normalized_counts[[n]] %>% mutate(
mean_DMSO = apply(normalized_counts[[n]][,2:6], MARGIN = 1, FUN = mean),
mean_CPD = apply(normalized_counts[[n]][,7:11], MARGIN = 1, FUN = mean),
FC = apply(normalized_counts[[n]][,7:11], MARGIN = 1, FUN = mean)/apply(normalized_counts[[n]][,2:6], MARGIN = 1, FUN = mean),
logFC = log2(apply(normalized_counts[[n]][,7:11], MARGIN = 1, FUN = mean)/apply(normalized_counts[[n]][,2:6], MARGIN = 1, FUN = mean)))
#
normalized_counts[[n]] <- inner_join(normalized_counts[[n]], deg[[n]], by = c("gene_id"), suffix = c("_count", "_deg"))
#
normalized_counts[[n]] <- normalized_counts[[n]] %>% mutate(
color = case_when(
logFC_count > log2(1.5) & FDR < 0.01 ~ "red",
logFC_count < log2(0.75) & FDR < 0.01 ~ "blue",
TRUE ~ "black"))
}Rここで、edgeRによって出力されるlogFCとlogCPMの値について知っておくべき事がある。上記のように、単に比較群と対照群を割っただけでは、発散する値がでてしまい、それはinfとか-infとかNaNとかになる。しかし、edgeRのlogFCにはそれがない。これはどういう事かというと、このブログに詳しく書かれている。ちゃんと分析してくれたこの方には感謝である。どうやら、正規化した後にデフォルトで0.125が足されているようだ。おそらくこれは、log1pとかの計算と似たようなことであり、妥当と思う。
Volcano PlotとMAplot
これを示すのが有効な場合は、薬剤処理によって非常に限られた数の遺伝子が明確に変化するような場合と考えられる。RNA-seq解析の結果、かなりの数のDEGが検出されるだろう。その中から発現の差が大きいトップ何位を抽出したところで、一体その図から何が言えるのだろうか。言えることは「これが統計的有意なDEGトップ20位に位置する遺伝子です。こんな遺伝子が入っておりました」であり、レビューしている方は「ふーん、で?」と来るはずである。初っ端からVolcano plotやMA plotでデータを示したところで、全くと言って良いほど要約になっていないように思う。
これは逆に言えば、すでに限られた数の遺伝子が抽出できている場合には有効ということと思う。例えば、後に示すようなGSEAやOverrepresentation analysisを行って特定の遺伝子、もしくは遺伝子セットを見つけることができた場合、それらをVolcano plotで表示することで、その他多数に比べて何倍くらい遺伝子発現が増加もしくは減少しているのか、そしてそのFDR補正後のP値はどのくらいかを効率的に示すことが出来る。今思ってもやってしまったなぁと思うが、考えてみればLeading Edge GeneをVolcano plotで示せば良かったと思っている。
しかしながら、この手の解析ではDEGの結果のとりあえずの要約の一つとして示す人も多いのも事実である。なので、ここでも作成しておくことにする。
準備
このコードを改めて見たとき、「なにこれ、本気か」と思った。リストとパイプ(%>%)のエグみが全開じゃあないか。さらにifとかも使っている。自分はいつからこんなコードを書くようになってしまったんだろうか。これ多分、書いてるときは夢中でやってるんだけど、後からみて嫌になるヤツだ。実際、嫌になった。
ここで何をやっているかと言えば、edgeRで得られるlogFCの値(これは正規化済のカウントマトリックスとinner_join()されている)がlog2(1.5)以上の遺伝子とFDRが-1*log10(FDR)が-1*log10(0.05)以上の遺伝子のトップ20位、さらに、log2(0.75)以下の遺伝子とFDRが-1*log10(FDR)が-1*log10(0.05)以上の遺伝子のトップ20位を拾ってきて、volcano plotのラベルを入れるための遺伝子名の列を作る作業である。トップ20位以下の場合、その遺伝子名をNAとすることで、ドットのラベルをトップ20位だけ表示させる。このランキングは30位でも50位でも一向にかまわん。しかし、ラベルが多すぎると互いに重なりあって結局読めなくなる。それに、上述したようにこのvolcano plotやMA plotは探索的な目的には全く合っていないので、20位とか、せいぜい30位くらいでも良いように思う。このラベルは後に作成するMA plotでも利用する。このコードチャンクが見にくいときは、最後に置いたコードをダウンロードしてRstudioで開いて見るか、このコードをRstudioにコピーして表示させ、解体したら良い。
ちなみに、Rはこの手のif() else()が超絶遅いので要注意。しかしながらcase_when()が上手いこと使えない状況ではどうしてもif() else if() else() を使わないと行けない。cellchatとか、CellPhoneDBとかね。dplyrをはじめ、Rは本当にこの手の処理が苦手である。pythonとか、一瞬で終わるから。
#
normalized_counts_volcano <- list()
temp_descending <- list()
temp_descending_gene <- list()
temp_ascending <- list()
temp_ascending_gene <- list()
label_volcano <- list()
for (i in 1:4){
temp_descending[[i]] <- normalized_counts[[i]][-1*log10(normalized_counts[[i]]$FDR) >= -1*log10(0.05) & normalized_counts[[i]]$logFC_deg >= log2(1.5),]
temp_descending[[i]]$intensity <- -1*log10(temp_descending[[i]]$FDR)*temp_descending[[i]]$logFC_deg
temp_descending[[i]] <- temp_descending[[i]] %>% arrange(desc(intensity))
temp_descending_gene[[i]] <- temp_descending[[i]]$gene_name[1:20]
#
temp_ascending[[i]] <- normalized_counts[[i]][-1*log10(normalized_counts[[i]]$FDR) >= -1*log10(0.05) & normalized_counts[[i]]$logFC_deg <= log2(0.75),]
temp_ascending[[i]]$intensity <- log10(temp_ascending[[i]]$FDR)*temp_ascending[[i]]$logFC_deg
temp_ascending[[i]] <- temp_ascending[[i]] %>% arrange(desc(intensity))
temp_ascending_gene[[i]] <- temp_ascending[[i]]$gene_name[1:20]
#
normalized_counts_volcano[[i]] <- normalized_counts[[i]]
normalized_counts_volcano[[i]]$label_volcano <- rep("", nrow(normalized_counts_volcano[[i]]))
normalized_counts_volcano[[i]]$label_volcano_check <- rep(NA, nrow(normalized_counts_volcano[[i]]))
#
label_volcano[[i]] <- c(temp_descending_gene[[i]], temp_ascending_gene[[i]])
normalized_counts_volcano[[i]]$label_volcano_check <- normalized_counts_volcano[[i]]$gene_name %in% label_volcano[[i]]
#
for(n in 1:nrow(normalized_counts_volcano[[i]])) {
if(normalized_counts_volcano[[i]]$label_volcano_check[n]){
normalized_counts_volcano[[i]]$label_volcano[n] <- normalized_counts_volcano[[i]]$gene_name[n]}
else{normalized_counts_volcano[[i]]$label_volcano[n] <- NA}
}
}RVolcano plot
この解析では4グループある。なので、まず1行4列のプロットを出力するためにpar()でそのように分割する。volcano plotはY軸にFDR補正後のP値のlog10変換に-1をかけて正の値にした値、横軸にFC(Fold change)をlog2変換したものを利用する。これらはedgeRを用いた検定結果を使用するので、FDRやlogFC_degを使っている。なお、”_deg”とついているのは正規化されたカウント値のデータフレームとedgeRの検定結果をinner_join()しているためである。このときにsuffix
としてedgeRの検定結果側に入っていたlogFCに”_deg”を付けてlogFC_degにしている。
# plot
par(mfrow = c(1, 4), mar = c(1,2,1,1.5))
for(i in 1:4){
plot(normalized_counts_volcano[[i]]$logFC_deg, -1*log10(normalized_counts_volcano[[i]]$FDR), col = normalized_counts_volcano[[i]]$color, pch = 16, cex = 0.8)
text(
normalized_counts_volcano[[i]]$logFC_deg, -1*log10(normalized_counts_volcano[[i]]$FDR),
labels = normalized_counts_volcano[[i]]$label_volcano,
pos = 4,
cex = 0.7, col = "black")
}Rvolcano plotを描くためのパッケージであるEnhancedVolcanoを使っても良いと思う。これはggplot2ベースのプロットになることから、EnhancedVolcano()の結果をリストにいれて、それをgrid.arrange()で出力すれば、4つの図を並べることができる。こちらの方がキレイといえばキレイなんだけど、前述の理由によりこのvolcano plotが有用そうな場面も限られていることから、正直、どっちでも良い。
volcano <- list()
for(i in 1:4){
volcano[[i]] <- EnhancedVolcano(toptable = normalized_counts[[i]],
x = "logFC_deg",
y = "FDR",
lab = normalized_counts[[i]]$gene_name,
pCutoff = 0.05,
FCcutoff = 1.5,
title = NULL,
subtitle = NULL,
caption = NULL,
xlab = NULL,
ylab = NULL,
axisLabSize = 11,
labSize = 3,
pointSize = 1) + theme(legend.position = "none")}
#
grid.arrange(grobs = volcano, ncol = 4)RMAplot
Volcano Plotに比べれば、こちらのほうが使えるように思う。MAplotは、Mean Average Plotの略だと思う。Volcano Plotと同様に、昨今のRNA-seqによるDEG解析で、顕著な差がある遺伝子が10本の指で数え切れる場合なんて先ずないし、DEGだってそのリストを表示しただけでは一体何のことだかわからないからGSEAやoverrepresentation analysisがあるわけだ。だから、このMAplotだってどのくらい意味があるかっていうことである。
しかしながら、イキってるだけのボンクラなVolcano Plotに比べてこのMAplotには明らかな利点がある。それは検定が不要なことである。だから、各群でN=2の比較にも使用できる。そのような実験計画を立てる奴はどうかしているんじゃあないかと思うところであるが、やっちまったもんはしょうがない。MAplotくらいしか、比較する手段はない、っていうことになる。だから、個人的にはVolcano plotよりはMAplotの方が使えるように感じる。
最初にedgeRに備わっているMAplotを示す。これはplotSmear()という関数で、入力としてDGEListを受け取る。非常に簡単なので便利なのだが、この図が使えるのは仲間内だけだろうなって感じである。
par(mfrow = c(1, 4), mar = c(1,2,1,1.5))
plotSmear(qlf_FT1_tumor, de.tags = deg_FT1_tumor_0.05less$gene_id)
plotSmear(qlf_EO771_tumor, de.tags = deg_EO771_tumor_0.05less$gene_id)
plotSmear(qlf_FT1_blood, de.tags = deg_FT1_blood_0.05less$gene_id)
plotSmear(qlf_EO771_blood, de.tags = deg_EO771_blood_0.05less$gene_id)Rなので、先程volcano plotのために作成したデータセットを使って、Rが元から持つplot()を使って同じような図を出力する。それが以下である。plotSmear()に比べて随分と要点が分かる図になっていると思う。
par(mfrow = c(1, 4), mar = c(1,2,1,1.5))
for(i in 1:4){
plot(normalized_counts_volcano[[i]]$logCPM, normalized_counts_volcano[[i]]$logFC_deg, col = normalized_counts_volcano[[i]]$color, pch = 16, cex = 0.8)
text(
normalized_counts_volcano[[i]]$logCPM, normalized_counts_volcano[[i]]$logFC_deg,
labels = normalized_counts_volcano[[i]]$label_volcano,
pos = 4,
cex = 0.7, col = "black")}Rこうしてvolcano plotとMAplotを見ていると、検定が不要なMAplotの優秀さが分かると思う。Volcano plotでp値がかなり小さく、かつFCも大きくことなる遺伝子が、MAplotでは他から大きく外れたところに居るように見える。つまり逆にいえば、N=2などで検定が出来ない比較だったとしてもMAplotを描いてそれが外れてくるような遺伝子は、実はその発現が高い、もしくは低いのかもしれない、ということを示唆している。こうやって見てみると、当然ながら確かな情報が多いのはvolcano plotなのかもしれないが、生物学的に意味あり気な感じがするのはMAplotということなのでは無いだろうか。
Venn Diagram
この解析では、同じ薬効に関して異なる比較群(グループ)が4つある。どのグループにも共通して変動する遺伝子を見つけるためには、まずはベン図が有用だろうと思う。ベン図だけではどの遺伝子が共通して変動するのかはわからないが、どちらの方が変動する遺伝子が多いのか、とか、そういったことも分かる。もし、例えば4T1の方が薬効が強く出る細胞で、EO771に比べて多くの遺伝子が統計的有意に変動していることがわかれば、その薬剤に感受性のある腫瘍モデルでは、他の腫瘍モデルで共通して変動するだろう遺伝子に加えて、薬剤の感受性に関わる遺伝子はこれかも、という議論が出来る
かもしれない。なので、グループが複数ある場合はベン図は書いたほうが良いと考えている。
まず、比較したい遺伝子名をリストにいれて、そのリストをvenn.diagram()に入れるだけ。しかし、このベン図は上記のようにpar(mfrow = c(1,4))やgrid.arrange()で図を分割できなかった。なので、これらはfor(i in 1:4)で一気に描かずに、4つを順次記載していくことにした。
ここに、common_gene_tumor_upとか、各群で共通した遺伝子名をintersect()で抽出したベクトルがあるが、それはここでは使わないので注意である。どっかで使えるかも、とか思ってここで作成したけど、こういうのはあまり良くない気がする。どこでどのオブジェクトを作ったかわからなくなってしまう。
venn_tumor_up <- list()
venn_tumor_down <- list()
venn_blood_up <- list()
venn_blood_down <- list()
venn_tumor_up[1] <- deg_FT1_tumor_0.05less %>% filter(logFC > 0 & FDR < 0.05) %>% select(gene_name)
venn_tumor_up[2] <- deg_EO771_tumor_0.05less %>% filter(logFC > 0 & FDR < 0.05) %>% select(gene_name)
names(venn_tumor_up) <- c("FT1", "EO771")
common_gene_tumor_up <- intersect(venn_tumor_up[1]$FT1, venn_tumor_up[2]$EO771)
venn_tumor_down[1] <- deg_FT1_tumor_0.05less %>% filter(logFC < 0 & FDR < 0.05) %>% select(gene_name)
venn_tumor_down[2] <- deg_EO771_tumor_0.05less %>% filter(logFC < 0 & FDR < 0.05) %>% select(gene_name)
names(venn_tumor_down) <- c("FT1", "EO771")
common_gene_tumor_down <- intersect(venn_tumor_down[1]$FT1, venn_tumor_down[2]$EO771)
venn_blood_up[1] <- deg_FT1_blood_0.05less %>% filter(logFC > 0 & FDR < 0.05) %>% select(gene_name)
venn_blood_up[2] <- deg_EO771_blood_0.05less %>% filter(logFC > 0 & FDR < 0.05) %>% select(gene_name)
names(venn_blood_up) <- c("FT1", "EO771")
common_gene_blood_up <- intersect(venn_blood_up[1]$FT1, venn_blood_up[2]$EO771)
venn_blood_down[1] <- deg_FT1_blood_0.05less %>% filter(logFC < 0 & FDR < 0.05) %>% select(gene_name)
venn_blood_down[2] <- deg_EO771_blood_0.05less %>% filter(logFC < 0 & FDR < 0.05) %>% select(gene_name)
names(venn_blood_down) <- c("FT1", "EO771")
common_gene_blood_down <- intersect(venn_blood_down[1]$FT1, venn_blood_down[2]$EO771)
grid.newpage()
grid.draw(venn.diagram(venn_tumor_up, filename = NULL, main = "Upregulated genes in tumor", fill=c(2,4), scaled=TRUE))
grid.newpage()
grid.draw(venn.diagram(venn_tumor_down, filename = NULL, main = "Downregulated genes in tumor", fill=c(2,4), scaled=FALSE))
grid.newpage()
grid.draw(venn.diagram(venn_blood_up, filename = NULL, main = "Upregulated genes in blood", fill=c(2,4), scaled=FALSE))
grid.newpage()
grid.draw(venn.diagram(venn_blood_down, filename = NULL, main = "Downregulated genes in blood", fill=c(2,4), scaled=FALSE))
# To output to image file
venn.diagram(venn_tumor_up, filename = "~/output/venn_tumor_up.png", main = "Upregulated genes in tumor", fill=c(2,4), scaled=FALSE)
venn.diagram(venn_tumor_down, filename = "~/output/venn_tumor_down.png", main = "Downregulated genes in tumor", fill=c(2,4), scaled=FALSE)
venn.diagram(venn_blood_up, filename = "~/output/venn_blood_up.png", main = "Upregulated genes in blood", fill=c(2,4), scaled=FALSE)
venn.diagram(venn_blood_down, filename = "~/output/venn_blood_down.png", main = "Downregulated genes in blood", fill=c(2,4), scaled=FALSE)Rここでは4つのベン図があるが、これをリストにしてgrid.arrange()に入れれば、パネルで表示することができる。
venn <- list()
venn[[1]] <- venn.diagram(venn_tumor_up, filename = NULL, main = "Upregulated genes in tumor", fill=c(2,4), scaled=TRUE)
venn[[2]] <- venn.diagram(venn_tumor_down, filename = NULL, main = "Downregulated genes in tumor", fill=c(2,4), scaled=FALSE)
venn[[3]] <- venn.diagram(venn_blood_up, filename = NULL, main = "Upregulated genes in blood", fill=c(2,4), scaled=FALSE)
venn[[4]] <- venn.diagram(venn_blood_down, filename = NULL, main = "Downregulated genes in blood", fill=c(2,4), scaled=FALSE)
grid.arrange(grobs = venn, ncol = 2, nrow = 2)Rしかし、どうやらプラットフォームに依っては上記の単純なコードがうまく動かない場合があるようだ。そのときは以下で表示する。実はこれ、動かなかったからChatGPTに教えてもらったコードである。これを流したら一発でほしい図が出た。どうしてもわからない場合は、ChatGPTも上手に使ったら良いと思う。ただし、ChatGPTには生データは流さないように。後々、偉いことになる可能性がある。文字列なども、明かしては駄目な部分は変えてからChatGPTに投げた方が身のためである。
grid.newpage()
pushViewport(viewport(layout = grid.layout(2, 2)))
vp1 <- viewport(layout.pos.row = 1, layout.pos.col = 1)
vp2 <- viewport(layout.pos.row = 1, layout.pos.col = 2)
vp3 <- viewport(layout.pos.row = 2, layout.pos.col = 1)
vp4 <- viewport(layout.pos.row = 2, layout.pos.col = 2)
venn1 <- venn.diagram(venn_tumor_up, filename = NULL, main = "Upregulated genes in tumor", fill = c(2,4), scaled = TRUE)
venn2 <- venn.diagram(venn_tumor_down, filename = NULL, main = "Downregulated genes in tumor", fill = c(2,4), scaled = FALSE)
venn3 <- venn.diagram(venn_blood_up, filename = NULL, main = "Upregulated genes in blood", fill = c(2,4), scaled = FALSE)
venn4 <- venn.diagram(venn_blood_down, filename = NULL, main = "Downregulated genes in blood", fill = c(2,4), scaled = FALSE)
pushViewport(vp1)
grid.draw(venn1)
popViewport()
pushViewport(vp2)
grid.draw(venn2)
popViewport()
pushViewport(vp3)
grid.draw(venn3)
popViewport()
pushViewport(vp4)
grid.draw(venn4)
popViewport()RDEG(Differentially Espressed/Expressing Gene)を使ったOverrepresentation Analysis
続いてOverrepresentation analysisである。この解析では、DEGの遺伝子名だけを使って、そのDEGがどのパスウェイに属しているかを見る解析である。遺伝子名さえあればOKなので、比較的手軽な解析であり、グループがそれぞれN=2だったとしても可能である。非常に便利な解析だと思う。結局、生物学として必要なのは小難しい数字ではなく「これらの遺伝子はなんとかというパスウェイに属している奴らだな」ということなので、そういった観点からも有用であると思う。これがDEGの結果を万人に理解できるようにした要約って感じである。
準備
前述のように、ここでは処理の方法が決まっているので、必要なデータをリストにいれて、一気にclusterProfilerに投げることにする。そうしなければ、大量に似たようなコードをコピーペーストする羽目になり、コードの行数が激増することになる。以前にDEGの結果FDR < 0.05だった遺伝子から、さらにDMSO群にくらべて1.5倍以上、もしくは0.75倍以下の発現量だった遺伝子を拾ってきて、それらをリストに順次格納する。最後にそのリストが何かわかるように名前を付けておく。
# These are dataset
deg_graph <- list()
deg_graph[[1]] <- deg_FT1_tumor_0.05less %>% dplyr::filter(logFC > log2(1.5))
deg_graph[[2]] <- deg_FT1_tumor_0.05less %>% dplyr::filter(logFC < log2(0.75))
deg_graph[[3]] <- deg_EO771_tumor_0.05less %>% dplyr::filter(logFC > log2(1.5))
deg_graph[[4]] <- deg_EO771_tumor_0.05less %>% dplyr::filter(logFC < log2(0.75))
deg_graph[[5]] <- deg_FT1_blood_0.05less %>% dplyr::filter(logFC > log2(1.5))
deg_graph[[6]] <- deg_FT1_blood_0.05less %>% dplyr::filter(logFC < log2(0.75))
deg_graph[[7]] <- deg_EO771_blood_0.05less %>% dplyr::filter(logFC > log2(1.5))
deg_graph[[8]] <- deg_EO771_blood_0.05less %>% dplyr::filter(logFC < log2(0.75))
names(deg_graph) <- c("FT1_tumor_up", "FT1_tumor_down", "EO771_tumor_up", "EO771_tumor_down", "FT1_blood_up", "FT1_blood_down", "EO771_blood_up", "EO771_blood_down")RclusterProfilerによるGO overrepresentation analsis
Overrepresentation analysisにはclusterProliferを利用する。データベースとしては、最も知名度が高そうなGO(Gene Ontology)、そして、GOのカテゴリーの一つであるBP(Biological Pathway)を使用する。GOを参照する他の解析方法の代表としてはDAVIDやPantherがあるが、これらはウェブベースなので、一気に処理するには向いていない。clusterProfilerにもDAVIDを使う関数も用意されているが、clusterProfilerに組み込まれたDAVIDはかなり古い可能性があるのでオススメできない。新しいDAVIDはウェブのものを使用する必要がある。
ここで初めて気がついたことがある。いつからかわからないけど、DAVIDがNIHのページの一つになっている。とうとうメンテナンスが開始されたらしい。昔のページからNIHへの移動は2020年くらいに始まっているようだ。DAVIDはGOだけでなく他のデータベースも参考にできるから、有用である。自分もグラフの書き方さえ知っていれば、DAVIDを使うかもしれない。
deg_graph_output <- list()
for(i in 1:length(deg_graph)){
deg_graph_output[[i]] <- enrichGO(gene = deg_graph[[i]]$gene_name,
OrgDb = org.Mm.eg.db,
keyType = "SYMBOL",
ont = "BP",
pAdjustMethod = "BH",
pvalueCutoff = 0.05,
qvalueCutoff = 0.05,
readable = TRUE)}
names(deg_graph_output) <- c("FT1_tumor_up", "FT1_tumor_down", "EO771_tumor_up", "EO771_tumor_down", "FT1_blood_up", "FT1_blood_down", "EO771_blood_up", "EO771_blood_down")RGOを用いたパスウェイのエンリッチメントを行ったら、次はそれを可視化する。これで、統計的有意に発現が高かった(低かった)遺伝子が、そのパスウェイに属しているのか、要約することが出来る。この手の解析をあまりよくわかっていない人たちに一言で説明する場合、この出力が最もわかりやすいと思う。後述するGSEAはNet Enrichment Scoreという直感的に難解な値が登場するので、パスウェイ解析の結果を他人に分かりやすく説明する場合は、この結果を用いるのが個人的にはおすすめである。このcnetplot()にはfoldchangeという変数も備わっている。ここに遺伝子の発現量に対応する値、例えばFold-Changeを入れることにより、各ドットにその値の大小に対応した色が付く。
for(i in 1:length(deg_graph_output)){
cnetplot(deg_graph_output[[i]], node_label="all",
cex_category = 0.5,
cex_gene = 0.3,
cex_label_category = 0.5,
cex_label_gene = 0.3) %>% plot()
}RGSEA(Gene Set Enrichment Analysis)のためのデータセット作成
GSEAは群間(多くのパッケージが2群間だと思う。)の遺伝子発現プロファイル(リードカウント)を比較することで、どのシグナル経路(分子パスウェイとか、そう呼称されるが、ここでは解析的に遺伝子セットと呼ぶ。)が、どちらの群に多く寄与しているかを解析する方法である。上記のOverrepresentation analysisもGSEAも、両者ともどの遺伝子がどの遺伝子セットにどのくらい属するかを解析する手法ではあるが、前者は対象となる遺伝子は、一般的に知られている遺伝子セットに何割くらい属しているのか調べる方法であり、後者は、対象となる遺伝子の発現量は、一般的に知られている遺伝子セットにどのくらい影響を与えているか、ということを調べる手法と言える。実際には一般的に知られている遺伝子セットを、リードカウントの高い順位ならべ、そのランクから遺伝子セットへの影響(しているかもしれない)の大きさをEnrichment Scoreとして算出する、という感じである。しかしながら、論文を読んでみても計算方法は理解できるが、正直、釈然としない、といか、直感的にパッとわからないところも多いが。この論文なので、是非読んでみたら良い。結局、それって単にランキングだけで良くないか?この「Normalized」って何?ってなるから。
そういうわけで、GSEAもGSVAにしても、結局は似たようなことをやっている。正直、好きな方で良いように思うが、このBroad InstituteのGSEAは群間もしくは遺伝子間でパーミューテーション検定(Permutation Test)を行い、そのEnrichment Scoreをより確かなものにしているというちょっとした付加価値もあり、世界中で利用されているという一般性も兼ね備えている。だから、GSEAといえばこのGSEAというソフトをとりあえず使ってしまえば良いのではないだろうか。
正直に言えば、前述の通り、このEnrichment ScoreやNet Enrichment Scoreの数値が直感的に本気でわかりにくいし、ここで行われているPermutation Testが、実際の生物現象にどれだけ意味があるかは疑問である。GSEAは分子パスウェイに着目したら、ストーリー上は良いのかもしれないが、DEGを使ってOverrepresantation analysisを行えば、十分にエンリッチされている遺伝子セットや分子パスウェイを拾ってこれるし、実験を進めようと思ったら具体的な遺伝子に着目する必要があるので、結局のところ、実験的にはOverrepresentation analysisで十分なのではないか、と個人的に思うところである。発現量の数値が不要な分、応用も効くし。
言うても、このGSEAはRNA-seq解析で一般的に行う、一連の解析の一つに組み込む必要はあると思う。そういうことで、順次データを作成していく。まずはBroad InstituteのGSEAに放り込むデータのフォーマットを整えることにする。
フォーマットはここに記載されている。また、本当に理解するためにはマニュアルをしっかり読んだほうが良い。ここでは、統計的有意差があったDEGを用いて解析する。実は全遺伝子を用いてGSEAを行うことができるが、そうすると計算が長くなるし、そもそも群間で有意な発現の差があった遺伝子を見つけ出して、それらがどんな分子パスウェイに関連しているかが知りたいので、持っている遺伝子発現プロファイル全部をGSEAに放り投げることは、自分ならやならい。
まずは、カウントデータ(これをカウントマトリックスと言ったりする。)を作成する。これはGCTファイル(.GCT)という。edgeRで正規化したカウントを利用し、そこからFDR <0.05だった遺伝子だけを抽出してくる。GSEAは2群間の比較になるので、ここでは各モデルごとにCPD群とDMSO群を比較する。
しかしながら、これまでの解析ではEnsenbl IDを元にして解析を進めてきた。GSEAではEnsenble IDでも実行可能であるが、Chipファイルにより変換しなくてはならず(はず。普段これはやらんので定かではない。)、手間が増えてしまうことから遺伝子名(gene_name)をカウントマトリックスを付け、それを使うことにする。そのために、最初の方でGTFファイルから抜き出して作成したgene_id_geme_nameというファイルとinner_jon()する。
最後にGSEAに必要な列を抜き出す。
# Extract DEG genes from normalized count matrix.
gsea_FT1_tumor <- normalized_raw_counts_df[normalized_raw_counts_df$gene %in% deg_FT1_tumor_0.05less$gene_id == TRUE,1:11]
gsea_EO771_tumor <- normalized_raw_counts_df[normalized_raw_counts_df$gene %in% deg_EO771_tumor_0.05less$gene_id == TRUE,c(1, 12:21)]
gsea_FT1_blood <- normalized_raw_counts_df[normalized_raw_counts_df$gene %in% deg_FT1_blood_0.05less$gene_id == TRUE,c(1, 22:31)]
gsea_EO771_blood <- normalized_raw_counts_df[normalized_raw_counts_df$gene %in% deg_EO771_blood_0.05less$gene_id == TRUE,c(1, 32:41)]
# Add gene name into count matirx
gsea_FT1_tumor <-inner_join(gsea_FT1_tumor, gene_id_gene_name, by = "gene_id")
gsea_EO771_tumor <-inner_join(gsea_EO771_tumor, gene_id_gene_name, by = "gene_id")
gsea_FT1_blood <-inner_join(gsea_FT1_blood, gene_id_gene_name, by = "gene_id")
gsea_EO771_blood <-inner_join(gsea_EO771_blood, gene_id_gene_name, by = "gene_id")
# select required colomns.
gsea_FT1_tumor <- gsea_FT1_tumor %>% select(all_of(c("gene_name", "gene_id", "T1", "T2", "T3", "T4", "T5", "T6", "T7", "T8", "T9", "T10")))
gsea_EO771_tumor <- gsea_EO771_tumor %>% select(all_of(c("gene_name", "gene_id","T11", "T12", "T13", "T14", "T15", "T16", "T17", "T18", "T19", "T20")))
gsea_FT1_blood <- gsea_FT1_blood %>% select(all_of(c("gene_name", "gene_id","B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10")))
gsea_EO771_blood <- gsea_EO771_blood %>% select(all_of(c("gene_name", "gene_id","B11", "B12", "B13", "B14", "B15", "B16", "B17", "B18", "B19", "B20")))RカウントマトリックスをGSEAに入れるには、データのフォーマットに従う必要がある。詳しくはここを読んでほしいところである。一行目は#1.2、二行目からデータ、という感じである。それをcat()で作成し、それにwrite_tsv()でカウントマトリックスを付ける。
file_path <- c("~/output/gsea/gsea_FT1_tumor.gct",
"~/output/gsea/gsea_EO771_tumor.gct",
"~/output/gsea/gsea_FT1_blood.gct",
"~/output/gsea/gsea_EO771_blood.gct")
gsea_data <- list()
gsea_data[[1]] <- gsea_FT1_tumor
gsea_data[[2]] <- gsea_EO771_tumor
gsea_data[[3]] <- gsea_FT1_blood
gsea_data[[4]] <- gsea_EO771_blood
for(i in 1:length(file_path)){
file.remove(file_path[i])
cat("#1.2", file =file_path[i], sep = "\t", append = FALSE)
cat("\n", file =file_path[i], sep = "\t", append = TRUE)
cat(paste0(nrow(gsea_data[[i]]),"\t", ncol(gsea_data[[i]][,3:ncol(gsea_data[[i]])])), file =file_path[i], sep = "\t", append = TRUE)
cat("\n", file =file_path[i], sep = "\t", append = TRUE)
write_tsv(gsea_data[[i]], file =file_path[i], col_names = TRUE, append = TRUE)
}RGSEAにはデータとなるカウントマトリックスに加えて、クラスファイル(.cls)が必要である。これは、どちらの群をコントロールとするか、どのサンプルがどの群に入るかを書いたファイルである。今回は最初の5検体がCPD群、次の5群がDMSO群なので、そのように記載して、それを.clsを拡張子にして保存する。
file.remove("~/output/gsea/DMSO_vs_CPD.cls")
#
cat("10 2 1", file ="~/output/gsea/DMSO_vs_CPD.cls", sep = "\t", append = FALSE)
cat("\n", file ="~/output/gsea/DMSO_vs_CPD.cls",sep = "\t", append = TRUE)
cat("# DMSO FIM001", file ="~/output/gsea/DMSO_vs_CPD.cls", sep = "\t", append = TRUE)
cat("\n", file ="~/output/gsea/DMSO_vs_CPD.cls",sep = "\t", append = TRUE)
cat("DMSO DMSO DMSO DMSO DMSO CPD CPD CPD CPD CPD", file ="~/output/gsea/DMSO_vs_CPD.cls", sep = "\t", append = TRUE)RこれでGSEAを行うためのデータの準備は整った。
GSEA
次はGSEA本番である。画像の通り、ここではGSEAのバージョン4.3.3を使う。最近では機能としてはほとんど変わっていないので、最近のバージョンであればどれを使っても一緒だろうと思う。ただし、バージョンが変われば計算結果も違う場合も十分に考えられるので、このバージョンはしっかり記憶シておく方が良い。最初にLoad Dataから、上記で作成したGCTファイル、CLSファイル、GMTファイルをアップロードする。GMTファイルは自分でも作成できるし、ここからマウス、ヒトそれぞれダウロードできる。GMTファイルをダウンロードするところに「ここに全遺伝子セットをまとめたファイル置いておくけど、これは本当にオススメできないよ。みたい奴をGSEAしたほうがいいで。」と書いてあるが、そんなもん知らん。全部入りをダウンロードして、一気に解析した方が探索的な目的には合っているはず。しかし、細胞のタイピングをやるだとか、そういう目的があるならば、H8とかM8とか、そうやって個別にダウンロードしてGSEAするのは有りだ。
Errorが出る場合があるが、その場合は大体フォーマットが間違えているので、改めて作成するか、間違ったところを直す必要がある。こだわりがなければエクセルやテキストファイルで直してしまっても良いと思う。

データのアップロードができたら、次はRun GSEAを押す。

そうすると以下のような画面になる。ここでのポイントは、GCTファイルを遺伝子名(Ensembl IDでは無く)で作成しており、用いるGMTファイルが遺伝子名(Entrez IDでは無く)を用いている場合は、Collapse/Remap to gene symbleの欄をNo_Collapseにする。その場合は、Chip platformは空欄にする。No_Collapseの場合は、ここに何か入れても空欄でも、どうせ使用されないのでエラーも起こらない(はず)。 ここはもしGCTファイルを遺伝子IDを使っている場合、適切なCHIPファイルを使って遺伝子IDを遺伝子名に直す部分である。自分はそれが面倒で、エラーの原因にもなるので、はじめから遺伝子名でGCTファイルを作成している。
それら必須な項目(Required fields)が正しく入力されていれば、下の方にあるRunボタンを押すだけであるが、Analysis nameは入力した方が良いと思う。沢山やっていると何か何やらわからなくなるためである。わかりやすい名前にすべきである。
もう一つ重要な点としては、Seed for Permutationというところである。これはデフォルトではタイムスタンプが使われるが、これだと計算結果の再現性がなくなってしまう(もちろん、大部分は変わらないが、有意差の微妙なところがRunボタンを押すごとに、有意差ありになったり有意差なしになったりして非常に鬱陶しいことになる。なので、いつも一定の数値を入れるほうが良い。
他の項目は、自分の好きに(適切に)やったら良い。Runボタンを押すと、GSEA reportsのところがRunningになる。

Runボタンを押してしばらくすると、そこがSuccessになる。ここではSuccess(with warning)とか出ているが、そんなん言われてもどうしようも無いので無視。下の方にあるshow folderを押すと、データが保存されたディレクトリに行くことができるし、この例でいうところのSuccess(with warning)を押すと、結果(これはindex.htmlである。)に飛ぶことができる。

Success(with warning)を押して出てくる画面は、出力されたディレクトリの中にあるindex.htmlである。このファイルを開けば、結果のまとめが表示される。

Index.htmlを開き、Detailed enrichment results in html formatから何かのパスウェイのDetailsを押すか、snapshotから、以下のようなよく見る図を表示することが出来る。これは、DMSO投与群で発現が高い遺伝子が、HALLMARK_HYPOXIAというパスウェイに多く寄与している、ということを示している。これは逆に、しかしながら処理に依るとは思うが、CPD投与群では、HALLMARK_HYPOXIAへの寄与が減っている、とも読むことができると思う。縦の黒線が青のスケールの濃い方に沢山あるならば、それはそのパスウェイにかなり高く寄与しているということである。赤のスケール側にもいくつか寄与の高い遺伝子があるが、これはCPD投与群でもHALLMARK_HYPOXIAというパスウェイと関係する遺伝子が、少なからず、高いものもある、という意味である。

Runボタンの隣にCommandボタンがあるが、そこからこの解析に使ったコードをコピーできる。何度も何度も選ぶの面倒なので、こうやってコピーしてBashで一気に流すには有り中の有りである。
# FT1_tumor
gsea-cli.sh GSEA -res /home/kats/output/gsea/gsea_FT1_tumor.gct -cls /home/kats/output/gsea/DMSO_vs_CPD.cls#CPD_versus_DMSO -gmx /home/kats/output/gsea/msigdb.v2024.1.Mm.symbols.gmt -collapse No_Collapse -mode Max_probe -norm meandiv -nperm 1000 -permute gene_set -rnd_seed 500000 -rnd_type no_balance -scoring_scheme weighted -rpt_label FT1_tumor -metric Signal2Noise -sort real -order descending -create_gcts true -create_svgs true -include_only_symbols true -make_sets true -median false -num 100 -plot_top_x 20 -save_rnd_lists false -set_max 500 -set_min 3 -zip_report true -out /home/kats/output/gsea/result
# EO771_tumor
gsea-cli.sh GSEA -res /home/kats/output/gsea/gsea_EO771_tumor.gct -cls /home/kats/output/gsea/DMSO_vs_CPD.cls#CPD_versus_DMSO -gmx /home/kats/output/gsea/msigdb.v2024.1.Mm.symbols.gmt -collapse No_Collapse -mode Max_probe -norm meandiv -nperm 1000 -permute gene_set -rnd_seed 500000 -rnd_type no_balance -scoring_scheme weighted -rpt_label EO771_tumor -metric Signal2Noise -sort real -order descending -create_gcts true -create_svgs true -include_only_symbols true -make_sets true -median false -num 100 -plot_top_x 20 -save_rnd_lists false -set_max 500 -set_min 3 -zip_report true -out /home/kats/output/gsea/result
# FT1_blood
gsea-cli.sh GSEA -res /home/kats/output/gsea/gsea_FT1_blood.gct -cls /home/kats/output/gsea/DMSO_vs_CPD.cls#CPD_versus_DMSO -gmx /home/kats/output/gsea/msigdb.v2024.1.Mm.symbols.gmt -collapse No_Collapse -mode Max_probe -norm meandiv -nperm 1000 -permute gene_set -rnd_seed 500000 -rnd_type no_balance -scoring_scheme weighted -rpt_label FT1_blood -metric Signal2Noise -sort real -order descending -create_gcts true -create_svgs true -include_only_symbols true -make_sets true -median false -num 100 -plot_top_x 20 -save_rnd_lists false -set_max 500 -set_min 3 -zip_report true -out /home/kats/output/gsea/result
# EO771_blood
gsea-cli.sh GSEA -res /home/kats/output/gsea/gsea_EO771_blood.gct -cls /home/kats/output/gsea/DMSO_vs_CPD.cls#CPD_versus_DMSO -gmx /home/kats/output/gsea/msigdb.v2024.1.Mm.symbols.gmt -collapse No_Collapse -mode Max_probe -norm meandiv -nperm 1000 -permute gene_set -rnd_seed 500000 -rnd_type no_balance -scoring_scheme weighted -rpt_label EO771_blood -metric Signal2Noise -sort real -order descending -create_gcts true -create_svgs true -include_only_symbols true -make_sets true -median false -num 100 -plot_top_x 20 -save_rnd_lists false -set_max 500 -set_min 3 -zip_report true -out /home/kats/output/gsea/resultBashこのようにBashで一気に流すほうが、楽。

Leading Edge Analsysis
GSEAが終わったら次はLeading edge analysisを行う。edgeとは、ネットワークグラフのエッジ(リンク)、つまり、各遺伝子間を繋ぐ線のことである。各遺伝子をノードと呼ぶ。そしてこのleading edgeとは、エンリッチされてきた遺伝子セットのうち、遺伝子に対して最も影響力のありそうなパスウェイのことを指す。これを見つけることを、Leading edge analysisと呼ぶ。ここから、エンリッチされてきた遺伝子セットに最も含まれている遺伝子を拾ってくるのが、自分は最も有効であると考えている。生物学では結局のところパスウェイに最も影響力の大きい遺伝子は何かを見つける必要があるので、この解析が役に経つ。
Locate a GSEA result folder from the file systemに上記で得たGSEAの結果のディレクトリをして、Load GSEA resultsを押せば、Leading Edge Analysisが始まる。

終われば結果の表が表示される。そして下にあるRun Leading edge analysisボタン、もしくはBuild HTML reportを押せば、それぞれその結果が表示、もしくはHTMLファイルとして外部へ出力される。
ここで非常に重要なことがある。以下の画像で青く選択された表(表をControl + Aで全選択してある)をControl + Cでコピーして、FT1_blood.tsv、EO771_blood.tsvなどのファイル名で保存しておく。面倒ではあるが、この青で選択された部分の列名は選択されていないので、そこは自力で記入する必要がある。言うても9列なので、我慢して手入力する。もし100ファイル作成しろって言われたら、どうしようかって感じである。

しかしながら、特にこの後の実験に有用とされる以下の情報こそ、一体何が書いてあるのか、読むことはできない。これが一番重要なんじゃあないのだろうか。だからもっと別な形で出力してほしいものである。確かに、GMTファイルのダウンロードのときに「全遺伝子セットを使って解析するのはオススメ出来ないで!」って言ってたけど、にしても結果みにくいわ。
個人的に最も必要なのは、左下の出力のNumber of Gene SetsとGeneのヒストグラムである。ヒストグラムの一番に位置する遺伝子が、最も多くのパスウェイに影響している遺伝子であり、この遺伝子がシグナリングに対して支配的かも知れないことがわかる。それが知りたいのに、文字が小さくて(そもそも見えない)拾うことができない。
これは後ほど自力で解決する。

Enrichment Mapによるグラフ作成
Leading Edge Analysisの一環として、各遺伝子間の繋がりについてネットワーク(グラフ)に出力することが出来る。それはEnrichment Map Visualizationというところから行う。先程のLeading Edge Analysisと同様、Locate a GSEA result folder from the file systemにGSEAの結果が保存されているディレクトリを入力し、以下の画像のように入力し、ネットワークとして可視化する。
これを行う際の注意点としては、事前にCytoscapeを立ち上げて置く必要があることである。Cytoscapeは外部のソフトなので、ここからダウンロードする。さらに、CytoscapeをインストールするまえにJavaをインストールする必要もある。2025年2月11日の時点で、必要なものはJRE version17が必要である。自分は以下でインストールした。もちろん、Cytocapeはここからダウンロードする必要がある。
Cytoscapeがインストール出来たら、それを起動させ、AppからEnritmnetoMapもインストールする必要がある。これはCytoscape内でインストールするアプリである。
sudo apt update
sudo apt install openjdk-17-jre
sudo sh '/home/kats/Cytoscape_3_10_3_unix.sh'BashEnrichment Map Buildを押す前にCytoscapeの起動を忘れると、以下のようなメッセージが出て先に進めない。

Cytoscapeの準備が出来、起動させて準備が出来たら、以下の画像のように入力し、Build Enrichment Mapボタンを押す。

うまく言っていたら以下のメッセージが表示される。

しばらく待つと、Cytoscapeに格好の良いグラフが表示される。正直、格好が良いだけで意味はあんまり無いのだが。これもしばらく時間がかかる可能性もあるので、出力されたグラフはちゃんと保存しておく方が良いだろう。

本当に必要なのはここからである。グラフの表示されているペーンの左下にNode TableとかEdge TableとかNetwork Tableとかが選べるプルダウンがある。ここをノードテーブルにすると上記の画像のようにエンリッチされてきた各ネットワークの情報が表示されるので、それを全部選択し、そのちょっと左にあるExport to Tableという黒のドキュメントから外に矢印が出ているアイコンがあるので、それをクリックする。

それをクリックすると、選んだ表をCSVファイルでエクスポートすることができる。ここではCSVファイルにFT1_blood.csvとEO771_blood.csvと名前を付けて保存する。このCSVを使って、ここからLeading Edgeを解析する。

Leading Edge Geneを抽出する
FT1_blood.csvとEO771_blood.csvを使ってLeading edge gene(正直言って、この言い方が正しいのか微妙である。Leading;他を引っ張っている、Edge;繋がりの、Gene;遺伝子。なんか微妙。しかし、GSEAのマニュアルもLeading edge geneという言葉を使っているし、まあいいか。)を抽出する。本当ならば、GSEAのLeading edge analysisの出力結果に表示されていれば一番良いのだが、総遺伝子数が多かったりすると全く見えなくなるので、自分はこのように対処している。
まず、出力であるFT1_blood.csvを読み込む。次に、FDR < 0.05以下かつCPD群で亢進するパスウェイのみを抜き出す。次に、EnrichmentMap::Genesという列にそのパスウェイにはどんな遺伝子セットがヒットしているかが書かれているので、それを抜き出す、それらは一つのレコードの中に|で区切って書かれているので、strsplit()によりその|で分けてunlist()によりベクトルを作り、tableで各遺伝子が出現する頻度をカウントし、その結果をデータフレームにしてる。これをDMSO群でも行うのが以下のコードである。これがすなわち、Leading edge geneである。このくらいのことなので、一連のGSEAでこのデータを取得できるように開発してほしいところである。
# FT1_blood
## CPD
em_FT1_blood <- read_csv("~/output/output_cytoscape/FT1_blood.csv")
colnames(em_FT1_blood)
em_FT1_blood_CPD <- em_FT1_blood %>% dplyr::filter(
em_FT1_blood$`EnrichmentMap::fdr_qvalue (Dataset 1)` < 0.05 &
em_FT1_blood$`EnrichmentMap::Colouring (Dataset 1)` > 0 )
em_FT1_blood_CPD_2 <- em_FT1_blood_CPD %>% dplyr::select(`EnrichmentMap::Genes`) %>% dplyr::rename("gene" =`EnrichmentMap::Genes`)
gene_FT1_blood_CPD <- 1 # initnalize dataframe
for(i in 1:nrow(em_FT1_blood_CPD_2))
{
gene_FT1_blood_CPD[i] <- em_FT1_blood_CPD_2$gene[i] %>% strsplit("\\|")
}
gene_FT1_blood_CPD <- unlist(gene_FT1_blood_CPD)
leadingedge_FT1_blood_CPD <- table(gene_FT1_blood_CPD) %>% data.frame() %>% rename("leading_edge_gene" = "gene_FT1_blood_CPD")
leadingedge_FT1_blood_CPD <- leadingedge_FT1_blood_CPD %>% arrange(desc(Freq))
leadingedge_FT1_blood_CPD$FIM001_or_DMSO <- "CPD"
## DMSO
em_FT1_blood_DMSO <- em_FT1_blood %>% dplyr::filter(
em_FT1_blood$`EnrichmentMap::fdr_qvalue (Dataset 1)` < 0.05 &
em_FT1_blood$`EnrichmentMap::Colouring (Dataset 1)` < 0 )
em_FT1_blood_DMSO_2 <- em_FT1_blood_DMSO %>% dplyr::select(`EnrichmentMap::Genes`) %>% dplyr::rename("gene" =`EnrichmentMap::Genes`)
gene_FT1_blood_DMSO <- 1 # initnalize dataframe
for(i in 1:nrow(em_FT1_blood_DMSO_2))
{
gene_FT1_blood_DMSO[i] <- em_FT1_blood_DMSO_2$gene[i] %>% strsplit("\\|")
}
gene_FT1_blood_DMSO <- unlist(gene_FT1_blood_DMSO)
leadingedge_FT1_blood_DMSO <- table(gene_FT1_blood_DMSO) %>% data.frame() %>% rename("leading_edge_gene" = "gene_FT1_blood_DMSO")
leadingedge_FT1_blood_DMSO <- leadingedge_FT1_blood_DMSO %>% arrange(desc(Freq))
leadingedge_FT1_blood_DMSO$FIM001_or_DMSO <- "DMSO"R上記と同じことをEO771_blood.csvでも行う。
# EO771_blood
## CPD
em_EO771_blood <- read_csv("~/output/output_cytoscape/EO771_blood.csv")
colnames(em_EO771_blood)
em_EO771_blood_CPD <- em_EO771_blood %>% dplyr::filter(
em_EO771_blood$`EnrichmentMap::fdr_qvalue (Dataset 1)` < 0.05 &
em_EO771_blood$`EnrichmentMap::Colouring (Dataset 1)` > 0 )
em_EO771_blood_CPD_2 <- em_EO771_blood_CPD %>% dplyr::select(`EnrichmentMap::Genes`) %>% dplyr::rename("gene" =`EnrichmentMap::Genes`)
gene_EO771_blood_CPD <- 1 # initnalize dataframe
for(i in 1:nrow(em_EO771_blood_CPD_2))
{
gene_EO771_blood_CPD[i] <- em_EO771_blood_CPD_2$gene[i] %>% strsplit("\\|")
}
gene_EO771_blood_CPD <- unlist(gene_EO771_blood_CPD)
leadingedge_EO771_blood_CPD <- table(gene_EO771_blood_CPD) %>% data.frame() %>% rename("leading_edge_gene" = "gene_EO771_blood_CPD")
leadingedge_EO771_blood_CPD <- leadingedge_EO771_blood_CPD %>% arrange(desc(Freq))
leadingedge_EO771_blood_CPD$FIM001_or_DMSO <- "CPD"
## DMSO
em_EO771_blood_DMSO <- em_EO771_blood %>% dplyr::filter(
em_EO771_blood$`EnrichmentMap::fdr_qvalue (Dataset 1)` < 0.05 &
em_EO771_blood$`EnrichmentMap::Colouring (Dataset 1)` < 0 )
em_EO771_blood_DMSO_2 <- em_EO771_blood_DMSO %>% dplyr::select(`EnrichmentMap::Genes`) %>% dplyr::rename("gene" =`EnrichmentMap::Genes`)
gene_EO771_blood_DMSO <- 1 # initnalize dataframe
for(i in 1:nrow(em_EO771_blood_DMSO_2))
{
gene_EO771_blood_DMSO[i] <- em_EO771_blood_DMSO_2$gene[i] %>% strsplit("\\|")
}
gene_EO771_blood_DMSO <- unlist(gene_EO771_blood_DMSO)
leadingedge_EO771_blood_DMSO <- table(gene_EO771_blood_DMSO) %>% data.frame() %>% rename("leading_edge_gene" = "gene_EO771_blood_DMSO")
leadingedge_EO771_blood_DMSO <- leadingedge_EO771_blood_DMSO %>% arrange(desc(Freq))
leadingedge_EO771_blood_DMSO$FIM001_or_DMSO <- "DMSO"
R抽出したLeading Edge Geneはどんなパスウェイに関連しているか調べる
こうやってLeading edge geneが抽出できたわけだが、なんとも迷惑なことに、Enrichmment Mapでグラフを書いたときに、どうやら遺伝子名が全部大文字になってしまっている。これを見たときには愕然とした。「最悪や、おれ、ヒトの遺伝子使ったってこと?じゃあ最初からミスってたってこと???」となる。焦りながら確認したところ、Enrichment Mapが原因だったということに気が付き、やれやれだぜ、となった次第である。
ちょっと不安は残るがもう仕方ない。nitchnetrというシングルセルRNAシークエンス用のパッケージにヒトとマウスのオルソログ(ホモログ)変換の関数があるが、そもそもこれは異なる動物種のゲノムにマッピングして得られた結果ではないので、その必要もないように思う。なので、ここはstr_to_title()を使って、シンプルに頭だけ大文字にする。
次に、前述のvenn.diaphram()でどのくらいの遺伝子が各モデル(4T1、EO771)で共通しているのかを調べる。実際に共通の遺伝子を拾ってくるのはintersect()を使えば良い。
leadingedge_FT1_blood_CPD$leading_edge_gene <- str_to_title(leadingedge_FT1_blood_CPD$leading_edge_gene)
leadingedge_EO771_blood_CPD$leading_edge_gene <- str_to_title(leadingedge_EO771_blood_CPD$leading_edge_gene)
leadingedge_FT1_blood_DMSO$leading_edge_gene <- str_to_title(leadingedge_FT1_blood_DMSO$leading_edge_gene)
leadingedge_EO771_blood_DMSO$leading_edge_gene <- str_to_title(leadingedge_EO771_blood_DMSO$leading_edge_gene)
venn_leadingedge_blood_CPD<- list()
venn_leadingedge_blood_DMSO<- list()
venn_leadingedge_blood_CPD[1] <- leadingedge_FT1_blood_CPD %>% select(leading_edge_gene)
venn_leadingedge_blood_CPD[2] <- leadingedge_EO771_blood_CPD %>% select(leading_edge_gene)
names(venn_leadingedge_blood_CPD) <- c("FT1", "EO771")
common_gene_leadingedge_blood_CPD <- intersect(venn_leadingedge_blood_CPD[1]$FT1, venn_leadingedge_blood_CPD[2]$EO771)
grid.newpage()
grid.draw(venn.diagram(venn_leadingedge_blood_CPD, filename = NULL, main = "Leading edge gene in blood with CPD", fill=c(2,4), scaled=FALSE))
venn.diagram(venn_leadingedge_blood_CPD, filename = "~/output/output_venndiagram/venn_leading_edge_blood_CPD.png", main = "Leading edge gene in blood with CPD", fill=c(2,4), scaled=FALSE)
venn_leadingedge_blood_DMSO[1] <- leadingedge_FT1_blood_DMSO %>% select(leading_edge_gene)
venn_leadingedge_blood_DMSO[2] <- leadingedge_EO771_blood_DMSO %>% select(leading_edge_gene)
names(venn_leadingedge_blood_DMSO) <- c("FT1", "EO771")
common_gene_leadingedge_blood_DMSO <- intersect(venn_leadingedge_blood_DMSO[1]$FT1, venn_leadingedge_blood_DMSO[2]$EO771)
grid.newpage()
grid.draw(venn.diagram(venn_leadingedge_blood_DMSO, filename = NULL, main = "Leading edge gene in blood with DMSO", fill=c(2,4), scaled=FALSE))
venn.diagram(venn_leadingedge_blood_DMSO, filename = "~/output/output_venndiagram/venn_leading_edge_blood_DMSO.png", main = "Leading edge gene in blood with DMSO", fill=c(2,4), scaled=FALSE)Rこの共通の遺伝子は、誰に何時聞かれてもおかしくないため、ファイルに出力しておく。聞かれたらメールで送ってやればよい。
common_gene_leadingedge_blood_CPD %>% data.frame() %>% rename("gene" = ".") %>% write_tsv("~/output/output_leadingedge/common_gene_leadingedge_blood_CPD")
common_gene_leadingedge_blood_DMSO %>% data.frame() %>% rename("gene" = ".") %>% write_tsv("~/output/output_leadingedge/common_gene_leadingedge_blood_DMSO")RLeading edge geneをヒートマップにしてみる。ここで注意することは、CPD群だからといってもそれのに遺伝子発現が高いものだけが含まれているわけではない。発現が低い遺伝子もパスウェイへの寄与をGSEAが計算しているので、それらは低いはずである。しかし、やはり大部分がCPD側、もしくはDMSO側で高くなっているはずである。
common_gene_leadingedge_blood_CPD
heatmap_common_gene_leadingedge_blood_CPD <- normalized_counts_gene_name[normalized_counts_gene_name$gene_name %in% common_gene_leadingedge_blood_CPD == TRUE, ]
heatmap_common_gene_leadingedge_blood_CPD <- heatmap_common_gene_leadingedge_blood_CPD[,-c(1:21)]
data_pheatmap <- as.matrix(heatmap_common_gene_leadingedge_blood_CPD[,1:20])
rownames(data_pheatmap) <- heatmap_common_gene_leadingedge_blood_CPD$gene_name
pheatmap(data_pheatmap,
cluster_rows = TRUE,
cluster_cols = FALSE,
scale = "row",
show_rownames = FALSE,
show_colnames = TRUE,
clustering_method = "ward.D2",
cutree_rows = 2,
legend = FALSE)
pheatmap_value <- pheatmap(data_pheatmap,
cluster_rows = TRUE,
cluster_cols = FALSE,
scale = "row",
show_rownames = TRUE,
show_colnames = TRUE,
clustering_method = "ward.D2",
cutree_rows = 2,
legend = FALSE)
pheatmap_annotation <- data.frame("gene_name" = pheatmap_value@row_names_param$labels)
pheatmap_annotation$order <- c(1:nrow(pheatmap_annotation))
pheatmap_annotation$gene_name <- factor(x = pheatmap_annotation$gene_name,
levels = pheatmap_annotation$gene_name,
labels = pheatmap_annotation$gene_name,
exclude = NA, ordered = is.ordered(pheatmap_annotation$gene_name), nmax = NA)
pheatmap_annotation$order <- c(1:nrow(pheatmap_annotation))
heatmap_common_gene_leadingedge_blood_CPD_check_gene_name <- inner_join(
heatmap_common_gene_leadingedge_blood_CPD, pheatmap_annotation, by = "gene_name")
heatmap_common_gene_leadingedge_blood_CPD_check_gene_name <- heatmap_common_gene_leadingedge_blood_CPD_check_gene_name %>% mutate(
mean_FT1_DMSO = apply(heatmap_common_gene_leadingedge_blood_CPD_check_gene_name[,1:5], MARGIN = 1, FUN = mean),
mean_FT1_CPD = apply(heatmap_common_gene_leadingedge_blood_CPD_check_gene_name[,6:10], MARGIN = 1, FUN = mean),
mean_EO771_DMSO = apply(heatmap_common_gene_leadingedge_blood_CPD_check_gene_name[,11:15], MARGIN = 1, FUN = mean),
mean_EO771_CPD = apply(heatmap_common_gene_leadingedge_blood_CPD_check_gene_name[,16:20], MARGIN = 1, FUN = mean),
FC_FT1 = apply(heatmap_common_gene_leadingedge_blood_CPD_check_gene_name[,6:10], MARGIN = 1, FUN = mean)/apply(heatmap_common_gene_leadingedge_blood_CPD_check_gene_name[,1:5], MARGIN = 1, FUN = mean),
FC_EO771 = apply(heatmap_common_gene_leadingedge_blood_CPD_check_gene_name[,16:20], MARGIN = 1, FUN = mean)/apply(heatmap_common_gene_leadingedge_blood_CPD_check_gene_name[,11:15], MARGIN = 1, FUN = mean),
)
heatmap_Up <- heatmap_common_gene_leadingedge_blood_CPD_check_gene_name %>% dplyr::filter(FC_FT1 > 1 & FC_EO771 > 1)
heatmap_Down <- heatmap_common_gene_leadingedge_blood_CPD_check_gene_name %>% dplyr::filter(FC_FT1 < 1 & FC_EO771 < 1)
tiff(filename = "~/output/data_pheatmap.tiff",
width = 800, height = 800, units = "px", pointsize = 12,
compression = "none",
bg = "white", res = 150)
pheatmap(data_pheatmap,
cluster_rows = TRUE,
cluster_cols = FALSE,
scale = "row",
show_rownames = FALSE,
show_colnames = TRUE,
clustering_method = "ward.D2",
cutree_rows = 2,
legend = FALSE)
dev.off()R以下はLeading edge geneが主にどんなパスウェイに属しているか、clusterPrifilerによるOverrepresentation analysisで可視化した例である。こうすれば、CPDの処理により両モデル(4T1、EO771)の血液でどんな分子パスウェイが動くのかを可視化出来る。
common_pathway_CPD <- enrichGO(gene = common_gene_leadingedge_blood_CPD,
OrgDb = org.Mm.eg.db,
keyType = "SYMBOL",
ont = "BP",
pAdjustMethod = "BH",
pvalueCutoff = 0.05,
qvalueCutoff = 0.05,
readable = TRUE)
cnetplot(common_pathway_CPD, node_label="category",
cex_category = 0.1,
cex_gene = 0.1,
cex_label_category = 0.1,
cex_label_gene = 0.1)Rggplot2でGSEAの結果を可視化する
上記ではCPD投与により発現が統計的有意に増減する遺伝子のうち、多くの分子パスウェイに関与する、言ってみればレギュレーターやハブになるような遺伝子(Leading edge)を抽出した。Leading edge geneは実際の実験に落とし込める可能性があることから個人的にはより有用と思っているが、当然ながら、一体なんの分子シグナルが影響を受けているのか、ということも解析する必要がある。しかし、この亢進もしくは抑制されているシグナリングを知ったところで、この先の実験に落とし込めることってのは少ないのでは無いかと思う。分子シグナリング自体が有用なのは、「この遺伝子をノックダウン・ノックアウトすると、どんなパスウェイが結果として変動するのか、それはノックダウン・ノックアウトして起こっている現象、例えばアポトーシス亢進、血管新生抑制、転移能亢進、薬剤抵抗性の亢進などを説明できるパスウェイが拾えるか」ということを証明すること、すなわち、論文の結論におけるパンチラインみたいなところである。
可視化する方法としては、よく見るあの図(HALLMARK_HYPOXIAの図)の羅列、というのも有りとは思うが、やはりclusterProfilerで見かけるドットプロットのようなものが、結果の要約としては優れているように見える(このリンクにあるFigure 14.1)。しかしながらこれはclusterProfilerではない。だったら、それをパクって可視化シてしまえば良い。
自分は以下の2つの方法でこれらの結果の概要を図示するようにしている。
Leading Edge Gene Analysisで出力できるHTMLをそのまま利用する
GSEAの欠点はやはりLeading edge analysisの出力が非常に乏しいところである。少なくともhtmlの結果をTSVとかCSVに出力してくれたら非常に楽なんじゃあないのだろうか。開発者のヒト、本気で考えてくれ。
ということで、自分が考えたのが、Leading edge analysisでBuild HTML reportボタンから出力して得られたindex.htmlをそのまま利用してしまう作戦である。完全に力技であるが、このあたりってそもそも結果の解釈が難しく、説明しづらいので、誤った知識で間違って解釈してしまうよりも、htmlの結果をそのまま利用するのは良い方法だと思っている。
そのためには、ちょっとしたスクレイピングを行うわけである。まずread_html()でそのLeading edge analysisの出力結果のindex.htmlを読み込み、表の部分をhtml_nodes(“table”)で抜き出す。そして、その表の部分をhtml_table()でデータフレームに変換してやる。そうすることで、あのLeading edge analysisの出力結果のindex.htmlをそのままデータフレームとして編集できるようになる。そしてclusterProfilerで行われているように、gene_ratioとして、ヒットした遺伝子数/分子パスウェイの遺伝子数を計算する。
# Read the HTML file
html_FT1_blood <- read_html("~/output/output_leadingedge/FT1_blood.LeadingEdgeTool.1738988252614/index.html")
# Extract all tables
table_FT1_blood <- html_FT1_blood %>% html_nodes("table")
# Process each table
# # members # members in signal Tag % List % Signal strength
for (i in 1:length(table_FT1_blood)) {
# Convert the table to a data frame
leadingedge_FT1_blood <- html_table(table_FT1_blood[[i]], fill = TRUE)
leadingedge_FT1_blood <- leadingedge_FT1_blood %>% dplyr::rename(
"Pathway" = "X1",
"members" = "X2",
"hits" = "X3",
"tag_percent" = "X4",
"list_percent" = "X5",
"signal_strength" = "X6"
)
}
# Calculate gene ratio
leadingedge_FT1_blood <- leadingedge_FT1_blood %>% dplyr::mutate(
gene_ratio = hits/members
)RclusterProfilerのdotplotをパクるためには、Enrichment Mapの結果から抽出したNES(Net Enrichment Score)、ES(Enrichment Score)、FDR Q value、Colouring(CLSファイルの設定で変わるが、CPDもしくはDMSOのどちら側でどのくらいエンリッチされているか)、gs_size(Geneset size)あたりが必要なので、それを両モデル(4T1、EO771)において抽出しておく。この数字は後ほど図示するドットのサイズや色に反映させる。
dataset_dotplot_FT1_blood <- em_FT1_blood %>% dplyr::select(
"EnrichmentMap::Name",
"EnrichmentMap::NES (Dataset 1)",
"EnrichmentMap::ES (Dataset 1)",
"EnrichmentMap::fdr_qvalue (Dataset 1)",
"EnrichmentMap::Colouring (Dataset 1)",
"EnrichmentMap::gs_size")
dataset_dotplot_FT1_blood <- dataset_dotplot_FT1_blood %>% rename(
"Pathway" = "EnrichmentMap::Name",
"NES" = "EnrichmentMap::NES (Dataset 1)",
"ES" = "EnrichmentMap::ES (Dataset 1)",
"FDR" = "EnrichmentMap::fdr_qvalue (Dataset 1)",
"Colouring" = "EnrichmentMap::Colouring (Dataset 1)",
"Count" = "EnrichmentMap::gs_size")
dataset_dotplot_FT1_blood_FDR_005 <- dataset_dotplot_FT1_blood %>% dplyr::filter(FDR < 0.05)R次に、Enrichment Mapの結果の一部を抽出した図とHTMLから取ってきた図をinner_join()する。そして、gene_ratioとESで降順に並べ替え、gene_ratioのトップ30位の遺伝子セット(分子パスウェイ)を抜き出す。別に50位も100位でも良いが、上位に自分が示したいパスウェイがあることが重要と思う。これはこれまで研究で行ってきた現象が反映されているかどうかを示すことに強みがあるためである。他に目的があれば、それに従えば良い。出力はggplot()で行う。
## For CPD
## select pathways in patients or in normal
dataset_dotplot_FT1_blood_FDR_005_CPD <- dataset_dotplot_FT1_blood_FDR_005 %>% dplyr::filter(Colouring > 0)
## Marge with HTML results
dataset_dotplot_FT1_blood_FDR_005_CPD <- dplyr::inner_join(dataset_dotplot_FT1_blood_FDR_005_CPD, leadingedge_FT1_blood)
## order dataset, gene_ratio, ES
dataset_dotplot_FT1_blood_FDR_005_CPD <- dataset_dotplot_FT1_blood_FDR_005_CPD %>% arrange(
desc(gene_ratio), desc(ES)
)
# extract top 30 pathway based on gene_ratio
pathway_FT1_blood_CPD <- dataset_dotplot_FT1_blood_FDR_005_CPD %>%
arrange(desc(gene_ratio)) %>%
head(30)
# factor, pathway
# dotplot plot the data with opossite order. Therefore rev() needs to be used for Dotplot.
order_pathway_FT1_blood_CPD <- c(pathway_FT1_blood_CPD$Pathway)
pathway_FT1_blood_CPD$Pathway <- factor(
pathway_FT1_blood_CPD$Pathway,
labels = rev(order_pathway_FT1_blood_CPD),
levels = rev(order_pathway_FT1_blood_CPD),
exclude = NA,
ordered = is.ordered(pathway_FT1_blood_CPD$Pathway),
nmax = NA)
# dot plot for patient
ggplot(pathway_FT1_blood_CPD, aes(x = gene_ratio, y = Pathway)) +
geom_point(aes(size = ES, color = FDR)) +
scale_color_gradient(low = "red", high = "blue") +
labs(
x = "Ratio of enriched genes in a pathway",
y = "Pathway",
color = "FDR adjusted p-value",
size = "Enrichment Score"
) +
theme_minimal() +
theme(axis.text.y = element_text(size = 5, hjust = 1))
ggsave("~/output/output_figure/gsea_pathway_FT1_blood_CPD.png", bg = "white", dpi = 720, height = 10, width = 20, unit = "cm")
# Paste this Heatmap() on to Console, and set Width = 800, hight = 400, Remove check "Maintain asect ratio" then save as png file.
## For DMSO
## select pathways in patients or in normal
dataset_dotplot_FT1_blood_FDR_005_DMSO <- dataset_dotplot_FT1_blood_FDR_005 %>% dplyr::filter(Colouring < 0)
## Marge with HTML results
dataset_dotplot_FT1_blood_FDR_005_DMSO <- dplyr::inner_join(dataset_dotplot_FT1_blood_FDR_005_DMSO, leadingedge_FT1_blood)
## order dataset, gene_ratio, ES
dataset_dotplot_FT1_blood_FDR_005_DMSO <- dataset_dotplot_FT1_blood_FDR_005_DMSO %>% arrange(
desc(gene_ratio), desc(ES)
)
# extract top 30 pathway based on gene_ratio
pathway_FT1_blood_DMSO <- dataset_dotplot_FT1_blood_FDR_005_DMSO %>%
arrange(desc(gene_ratio)) %>%
head(30)
# factor, pathway
# dotplot plot the data with opossite order. Therefore rev() needs to be used for Dotplot.
order_pathway_FT1_blood_DMSO <- c(pathway_FT1_blood_DMSO$Pathway)
pathway_FT1_blood_DMSO$Pathway <- factor(
pathway_FT1_blood_DMSO$Pathway,
labels = rev(order_pathway_FT1_blood_DMSO),
levels = rev(order_pathway_FT1_blood_DMSO),
exclude = NA,
ordered = is.ordered(pathway_FT1_blood_DMSO$Pathway),
nmax = NA)
# dot plot for patient
ggplot(pathway_FT1_blood_DMSO, aes(x = gene_ratio, y = Pathway)) +
geom_point(aes(size = ES, color = FDR)) +
scale_color_gradient(low = "red", high = "blue") +
labs(
x = "Ratio of enriched genes in a pathway",
y = "Pathway",
color = "FDR adjusted p-value",
size = "Enrichment Score"
) +
theme_minimal() +
theme(axis.text.y = element_text(size = 5, hjust = 1))
ggsave("~/output/output_figure/gsea_pathway_FT1_blood_DMSO.png", bg = "white", dpi = 720, height = 10, width = 20, unit = "cm")
# Paste this Heatmap() on to Console, and set Width = 800, hight = 400, Remove check "Maintain asect ratio" then save as png file.RLeandin Edge Analysisで表示されるテーブルを利用する
Leading edge analysisを行った後、HTMLに出力するまえに全パスウェイをCSVに出力したが、それを利用する方法が以下である。
これが必要な理由もある。実験によっては2群間の比較の結果、あまり多くの遺伝子セットがエンリッチされてこないこともある。その場合、当然FDR <0.05よりも小さい遺伝子セットの数は少ないことがあり、上記のHTMLの結果をそのまま利用する方法で得られた図では「ちょっと寂しい」場合がある。
そう思って全部の遺伝子セットについてLeading edge analysisでBuild HTML reportをやってみようとしても、今度は対象とする遺伝子が多すぎてBuild HTML reportの時点でエラーが起きたりする。Build HTML reportで出力するとエラーが起こるが、しかしながら、GSEAの結果をアップロードして、Leading Edge Analysisする時点(Build HTML reportボタンを押す前)ではエラーが起こらず、計算は出来る。しかもその表をコピーすることもできる。ということは、それを使ってしまえば良いってわけである。
一応言っておくが、「図が寂しい」から増やすとか言っている奴はクズなので信じる必要はない。先輩だろうと教授だろうと、そんなヤツ何も知らん科学童貞である。科学に寂しいも何もない。そんなもんは考えからして捏造の温床である。
そういうことなので、ここで作成したFT1_tumor.tsvを例にして図を作成する。まず、そのテーブルを読んで、その列の一つである”leadingedge”という列には入っている”,”(コンマ)のところでseparate()を使って列を区切り、次に、新しくできた”tag_percent”、”list_percent”、”signal_strength”の列の値にある数字以外の文字列、それぞれ、tags=%、list=%、signal=%を文字無しである””で置き換え、それをnumeric型にする。次にそれを100で割っておく。
出来上がった表について、nesとtag_percentで降順で並び替えて、それをggplot()で可視化すれば良い。もし他の検体や比較群(ここで言うところのFT1_tumor.tsv、EO771_tumor.tsv、EO771_blood.tsv)でも同じことを行えばOKである。
HTMLを読み込んで強制的に使う場合と異なるのは、全遺伝子セットを有意差(FDR)に関わらずNESでソートしている点である。これで、FDR < 0.05じゃなくても、TOP30位(別に何位まででもOK)の遺伝子セットを表示出来るようになり、数だけは揃う。
leadingedge_2nd_FT1_tumor <- read_tsv("~/output/output_leadingedge/FT1_tumor.tsv")
#
leadingedge_2nd_FT1_tumor <- leadingedge_2nd_FT1_tumor %>% separate(leadingedge, into = c("tag_percent", "list_percent", "signal_strength"), sep = ", ")
#
leadingedge_2nd_FT1_tumor$tag_percent <- gsub("[^0-9]", "", leadingedge_2nd_FT1_tumor$tag_percent) %>% as.numeric() # unit is percentage
leadingedge_2nd_FT1_tumor$list_percent <- gsub("[^0-9]", "", leadingedge_2nd_FT1_tumor$list_percent) %>% as.numeric() # unit is percentage
leadingedge_2nd_FT1_tumor$signal_strength <- gsub("[^0-9]", "", leadingedge_2nd_FT1_tumor$signal_strength) %>% as.numeric() # unit is percentage
leadingedge_2nd_FT1_tumor$hits <- leadingedge_2nd_FT1_tumor$size*(leadingedge_2nd_FT1_tumor$tag_percent/100)
leadingedge_2nd_FT1_tumor$hits <- trunc(leadingedge_2nd_FT1_tumor$hits)
# CPD
leadingedge_2nd_FT1_tumor_nes_FIM_30th <- leadingedge_2nd_FT1_tumor %>% arrange(desc(nes)) %>% head(30)
leadingedge_2nd_FT1_tumor_nes_FIM_30th <- leadingedge_2nd_FT1_tumor_nes_FIM_30th %>% arrange(desc(tag_percent))
#
order_leadingedge_2nd_FT1_tumor_nes_FIM_30th<- c(leadingedge_2nd_FT1_tumor_nes_FIM_30th$Pathway)
#
leadingedge_2nd_FT1_tumor_nes_FIM_30th$Pathway <- factor(
leadingedge_2nd_FT1_tumor_nes_FIM_30th$Pathway,
labels = rev(order_leadingedge_2nd_FT1_tumor_nes_FIM_30th),
levels = rev(order_leadingedge_2nd_FT1_tumor_nes_FIM_30th),
exclude = NA,
ordered = is.ordered(leadingedge_2nd_FT1_tumor_nes_FIM_30th$Pathway),
nmax = NA)
# dot plot for CPD
ggplot(leadingedge_2nd_FT1_tumor_nes_FIM_30th, aes(x = tag_percent, y = Pathway)) +
geom_point(aes(size = nes, color = fdr_p_val)) +
scale_color_gradient(low = "red", high = "blue") +
labs(
x = "Ratio of enriched genes in a pathway",
y = "Pathway",
color = "FDR adjusted p-value",
size = "Net Enrichment Score"
) +
theme_minimal() +
theme(axis.text.y = element_text(size = 5, hjust = 1))
ggsave("~/output/output_figure/leadingedge_2nd_FT1_tumor_nes_FIM_30th.png", bg = "white", dpi = 720, height = 10, width = 20, unit = "cm")
#
#
#
# DMSO
leadingedge_2nd_FT1_tumor_nes_DMSO_30th <- leadingedge_2nd_FT1_tumor %>% arrange(nes) %>% head(30)
leadingedge_2nd_FT1_tumor_nes_DMSO_30th <- leadingedge_2nd_FT1_tumor_nes_DMSO_30th %>% arrange(desc(tag_percent))
#
order_leadingedge_2nd_FT1_tumor_nes_DMSO_30th <- c(leadingedge_2nd_FT1_tumor_nes_DMSO_30th$Pathway)
#
leadingedge_2nd_FT1_tumor_nes_DMSO_30th$Pathway <- factor(
leadingedge_2nd_FT1_tumor_nes_DMSO_30th$Pathway,
labels = rev(order_leadingedge_2nd_FT1_tumor_nes_DMSO_30th),
levels = rev(order_leadingedge_2nd_FT1_tumor_nes_DMSO_30th),
exclude = NA,
ordered = is.ordered(leadingedge_2nd_FT1_tumor_nes_DMSO_30th$Pathway),
nmax = NA)
# dot plot for DMSO
ggplot(leadingedge_2nd_FT1_tumor_nes_DMSO_30th, aes(x = tag_percent, y = Pathway)) +
geom_point(aes(size = nes, color = fdr_p_val)) +
scale_color_gradient(low = "red", high = "blue") +
labs(
x = "Ratio of enriched genes in a pathway",
y = "Pathway",
color = "FDR adjusted p-value",
size = "Net Enrichment Score"
) +
theme_minimal() +
theme(axis.text.y = element_text(size = 5, hjust = 1))
ggsave("~/output/output_figure/leadingedge_2nd_FT1_tumor_nes_DMSO_30th.png", bg = "white", dpi = 720, height = 10, width = 20, unit = "cm")RRのバージョン違いに注意すること
最後に要注意なことを書いておく。この記事に書いてあるRのバージョンは4.4.2である。そして職場のコンピューターにインストールされているRのバージョンは4.4.1である。もしかしたら、こういったバージョンの違いが計算結果の違いとして現れる可能性がある。そのためにも、一通りの解析が終わったら、以下のコマンドを流すべきである。これがあるだけで、論文のMaterials and MethodsやSupplemental Informationに解析方法や使ったパッケージとそのバージョンを書く必要がある場合には安心できる。似た別のパッケージなんかもあるみたいなので、これはちゃんと記録した方が良い。
sessionInfo()Rこれで一通りの解析は終了
以上がRNA-seqで一般的に行う解析である、と自分は考えている。他に出来ることと言えば、まずはImmune deconvolutionかと思う。これは、遺伝子発現プロファイルから、その検体中にどんな種類のっ免疫細胞が、どれだけ存在しているかも知れないかを解析する手法である。CIBERSORT(これはアカデミックの人しか利用できない)が有名である。ヒトの場合はCIBERSORTの他も色々ある。マウスではmMCPcounter一択だと思う(他にもあるかも。)他に思いつく解析としてはARACNeとVIPERを使ったハブとなる遺伝子の探索だろうと思う。これは転写因子の探索なんかに有効のようだ。個人的にはお気に入りである。この辺りも機会があれば書きたいと思っている。
今回はedgeRを使ったが、同じことがDESeq2でも出来るので、そっちが好きならDESeq2でも一向にかまわん。
コードがほしい場合
もしコードがほしいときはここから連絡をください。上記と全く同じものですが、Purchase Number(購入番号;Pから始まる番号)、もしくは、ニックネームと一緒に質問の内容を記載いただければ、可能な限りご回答し、問題も解決させて頂きます。サポートに関すること以外にメールアドレスなどの個人情報を利用することはありません。安心して連絡を頂ければ幸いです。質問などもあれば、同様に連絡をください。可能な限り回答させていただきます。