· 

RのパッケージflowCoreを使ってFCSファイルからデータを抽出する

背景
一細胞ごとの蛍光強度を測定する機器としてフローサイトメーターがあります. これを使って細胞を測定すると測定結果としてFCSファイルが出力され, 当研究室ではCytExpertというソフトウェアを用いて解析を行っています. CytExpertはFCSファイルに特化しているだけあって, 散布図やヒストグラムを簡単に描くことができ, 任意の値の範囲内に含まれるデータのみを取り出すゲーティングも直感的に行うことができます. なので, 普通に使う分には不満はないそんなにないと思います. しかし, 自分の研究の中で, 異なる蛍光タンパク質(GFPとmCherryとか)が細胞ごとにどれくらい相関しているのかを調べる必要があり, これがCytExpertではできませんでした. どうしたものかと色々調べた結果, Rのパッケージを使えば実現できそうだったので, その時の試行錯誤を書きたいと思います.
不満は無いと言ったけど...
まず, CytExpertはWindows専用でMac OSでは動かない. これは地味に面倒ですね. Boot Camp アシスタントやVirtualBoxなどの仮想化ソフトを使う方法もありますが, Windowsのライセンスのお金はどこから出るんですかね? そしてグラフがイマイチ. 文字がギザギザしているし, 軸のメモリの数など細く設定できません. 発表用データとして使うにはIllustratorやPowerPointで上から線を引き直したり, 軸ラベルを書き直したりすることが必須なのではないでしょうか?にもかかわらず, グラフの出力はグラフ単位ではおこなえず(サイズをいじればできないことはないですが...), どちらかというとプリントアウトを前提としているため, 画像編集には向いていないという仕様... これはRを使うしかない!!
flowCoreを使ってみる
RのパッケージであるflowCoreはバイオ関係のデータを扱うためのパッケージを包括的にまとめたBioconductorで提供されています. インストールはBioconductorのflowCoreのページのInstllationと書かれたところに載っているコードをRに打ち込めばOKです. 同じページにマニュアルが載っているので詳しく知りたい方はそちらを参照してください(もちろん英語です).
インストールを終えたら, ひとまずFCSファイルを読み込んでみましょう. FCSファイルの読み込みにはread.FCS()を使います. ()の中にはFCSファイルのパスを指定します.
R
library(flowCore) #flowCoreの呼び出し
sample = read.FCS("XXXXX.fcs")  #XXXXXはファイル名
さっそくsampleの中身を見ていきたいと思います. まず素直にsampleを出力してみます.
R
sample

flowFrame object 'BY4741.fcs'
with 10671 cells and 22 observables:
          name         desc     range minRange  maxRange
$P1      FSC-H        FSC-H  16777215        0  16777214
$P2      FSC-A        FSC-A  16777215        0  16777214
$P3      SSC-H        SSC-H  16777215        0  16777214
$P4      SSC-A        SSC-A  16777215        0  16777214
$P5      FL1-H       FITC-H  16777215        0  16777214
$P6      FL1-A       FITC-A  16777215     -111  16777214
$P7      FL2-H      PerCP-H  16777215        0  16777214
$P8      FL2-A      PerCP-A  16777215     -111  16777214
$P9      FL3-H         PE-H  16777215        0  16777214
$P10     FL3-A         PE-A  16777215     -111  16777214
$P11     FL4-H        ECD-H  16777215        0  16777214
$P12     FL4-A        ECD-A  16777215     -111  16777214
$P13     FL5-H      PC5.5-H  16777215        0  16777214
$P14     FL5-A      PC5.5-A  16777215     -111  16777214
$P15     FL6-H        PC7-H  16777215        0  16777214
$P16     FL6-A        PC7-A  16777215     -111  16777214
$P17     FL7-H       DAPI-H  16777215        0  16777214
$P18     FL7-A       DAPI-A  16777215     -111  16777214
$P19     FL8-H HoechstRed-H  16777215        0  16777214
$P20     FL8-A HoechstRed-A  16777215     -111  16777214
$P21 SSC-Width    SSC-Width     10000        0      9999
$P22      Time         Time 900000000        0 899999999
440 keywords are stored in the 'description' slot

例として, BY4741.fcsというファイルを読み込んでいます. 結果を見てみると, 細胞数や蛍光の種類などが出力されています. 次にsummary()を使って基本統計量を見てみます.
R
summary(sample)

           FSC-H     FSC-A     SSC-H     SSC-A     FL1-H    FL1-A
Min.     56218.9   45745.8     182.2      99.8   198.000  -570.30
1st Qu. 244505.6  246539.8  176870.4  178636.2  1315.800  1186.80
Median  277527.7  296975.0  224108.5  240329.6  1677.400  1649.60
Mean    288193.9  320831.8  249568.7  275865.8  1849.468  1902.72
3rd Qu. 318508.1  366266.7  287560.1  321290.0  2142.850  2269.50
Max.    896167.2 2333654.2 2332447.5 9163624.0 11889.000 15146.20
             FL2-H      FL2-A     FL3-H      FL3-A     FL4-H
Min.      187.0000  -314.4000  129.1000 -544.40002    47.200
1st Qu.   490.0500   359.3000  323.2000  -45.90000  1018.850
Median    621.0000   535.2000  375.9000   58.70000  1398.600
Mean      706.7995   644.0544  385.8352   62.75811  1667.769
3rd Qu.   787.1500   770.2000  437.4000  164.60001  1900.000
Max.    22218.6992 25638.1992 1501.2000 1667.09998 38896.102
            FL4-A     FL5-H     FL5-A     FL6-H      FL6-A
Min.    -1492.000   65.2000 -655.4000   48.7000 -533.79999
1st Qu.   367.300  251.4000  -31.4000  232.2000  -78.20000
Median    971.900  299.1000   85.6000  275.2000   24.00000
Mean     1248.423  317.5798  102.8028  282.7895   29.12037
3rd Qu.  1680.150  352.0000  202.9500  320.7500  126.30000
Max.    45497.398 5861.7002 7058.7998 3844.7000 4545.00000
            FL7-H     FL7-A     FL8-H      FL8-A SSC-Width
Min.      298.300  -494.500   22.5000 -1032.4000  578.9091
1st Qu.   787.500   709.200  272.8000    29.0500 1027.5735
Median    988.200  1022.400  325.3000   158.9000 1082.1372
Mean     1089.395  1194.914  338.2478   171.1674 1100.4071
3rd Qu.  1236.350  1413.650  385.5000   290.5000 1154.9559
Max.    11108.000 42786.699 3159.1001  3637.2000 4852.2441
             Time
Min.          0.0
1st Qu.  463139.5
Median   904585.0
Mean     943792.8
3rd Qu. 1383440.0
Max.    1825126.0

蛍光ごとに結果が出力されます. 平均値を知りたいだけならここまでで十分ですね(もっともこれくらいはCytExpertでもできますけど). さらに詳しくsampleの中身を見ていきます.
R
str(sample)

Formal class 'flowFrame' [package "flowCore"] with 3 slots
  ..@ exprs      : num [1:10671, 1:22] 296358 291005 284861 216855 294244 ...
  .. ..- attr(*, "dimnames")=List of 2
  .. .. ..$ : NULL
  .. .. ..$ : Named chr [1:22] "FSC-H" "FSC-A" "SSC-H" "SSC-A" ...
  .. .. .. ..- attr(*, "names")= chr [1:22] "$P1N" "$P2N" "$P3N" "$P4N" ...
  .. ..- attr(*, "ranges")= num [1:22] 16777214 16777214 16777214 16777214 16777214 ...
  ..@ parameters :Formal class 'AnnotatedDataFrame' [package "Biobase"] with 4 slots
  .. .. ..@ varMetadata      :'data.frame':     5 obs. of  1 variable:
  .. .. .. ..$ labelDescription: chr [1:5] "Name of Parameter" "Description of Parameter" "Range of Parameter" "Minimum Parameter Value after Transforamtion" ...
  .. .. ..@ data             :'data.frame':     22 obs. of  5 variables:
  .. .. .. ..$ name    : 'AsIs' Named chr [1:22] "FSC-H" "FSC-A" "SSC-H" "SSC-A" ...
  .. .. .. .. ..- attr(*, "names")= chr [1:22] "$P1N" "$P2N" "$P3N" "$P4N" ...
  .. .. .. ..$ desc    : 'AsIs' Named chr [1:22] "FSC-H" "FSC-A" "SSC-H" "SSC-A" ...
  .. .. .. .. ..- attr(*, "names")= chr [1:22] "$P1S" "$P2S" "$P3S" "$P4S" ...
  .. .. .. ..$ range   : num [1:22] 16777215 16777215 16777215 16777215 16777215 ...
  .. .. .. ..$ minRange: num [1:22] 0 0 0 0 0 -111 0 -111 0 -111 ...
  .. .. .. ..$ maxRange: num [1:22] 16777214 16777214 16777214 16777214 16777214 ...
  .. .. ..@ dimLabels        : chr [1:2] "rowNames" "columnNames"
  .. .. ..@ .__classVersion__:Formal class 'Versions' [package "Biobase"] with 1 slot
  .. .. .. .. ..@ .Data:List of 1
  .. .. .. .. .. ..$ : int [1:3] 1 1 0
  ..@ description:List of 440
以下省略

長々と結果が表示されますが, 最初の方だけ見てみるとsampleがflowFrameというS4クラスの構造を持っていることが読み取れます. sampleはexprs, parameters, descriptionの3つのスロットを持ち, このうちexprsに生の蛍光値のデータが含まれています. スロットへのアクセスは@演算子を使います. そのまま出力すると細胞数x蛍光の種類分だけデータが出力されるのでhead()で先頭のデータだけを表示させます.
R
head(sample@exprs)

        FSC-H    FSC-A    SSC-H    SSC-A  FL1-H  FL1-A FL2-H FL2-A
[1,] 296357.7 338604.3 302511.8 323423.6 2076.3 2329.7 686.9 622.9
[2,] 291004.6 309358.6 230181.7 245796.2 1106.1  972.6 532.2 240.2
[3,] 284861.2 293847.6 216869.6 219008.9 1655.3 1276.1 497.7 267.2
[4,] 216855.4 228669.5 184996.2 192478.2 1784.9 1628.3 495.5 703.8
[5,] 294243.5 338679.2 233823.5 260693.1 1809.5 1944.5 863.6 478.4
[6,] 382794.6 421627.0 299554.3 323549.8 3040.0 2855.5 903.9 606.6
     FL3-H  FL3-A  FL4-H  FL4-A FL5-H  FL5-A FL6-H  FL6-A  FL7-H
[1,] 272.3 -104.7  820.1  -85.5 288.8  261.7 259.4   31.4 1296.8
[2,] 365.6  127.8 1476.9 1685.6 316.4  251.4 161.9 -175.5 1172.9
[3,] 336.7   15.5  693.4  597.3 149.2 -105.5 209.4  -41.9  886.6
[4,] 368.8  -35.9  667.4  275.3 248.1  -49.1 214.0 -120.7  854.2
[5,] 420.8  -91.5 1909.3 1096.9 334.9   87.5 265.4  190.2 1039.1
[6,] 526.4    8.3 1791.4 1697.1 322.1   93.9 382.4 -168.5 1232.9
      FL7-A FL8-H FL8-A SSC-Width Time
[1,] 1407.0 522.4 775.6  1094.812    0
[2,] 1487.8 426.5 869.5  1093.490   48
[3,] 1125.5 258.7 168.2  1034.124   99
[4,]  832.4 386.2 293.3  1065.435  327
[5,] 1109.8 371.1 281.8  1141.708  425
[6,] 1390.8 631.5 416.0  1106.034  603
 
蛍光の種類ごとに細胞一つ一つの蛍光値が行列として出力されました. 一応FSC-Aの平均値をmean()を使って計算してみます.
R
mean(sample@exprs[,"FSC-A"])

[1] 320831.8

先ほどのsummary()の結果と一致していますね.
ゲーティングを視覚的に行う
フローサイトメトリーではゲーティングを行うことによって死細胞を取り除いたり, 一定以上の蛍光値を持つ細胞の割合を調べたりします. flowCoreパッケージではゲーティングを行うための関数も用意されています. ゲーティングの形状によっていくつかの関数が用意されていますが, 主に使うのはrectangleGate()とpolygonGate()でしょうか. とりあえずFSC-AとSSC-Aを軸にとってplotしてみます.
R
plot(sample@exprs[, c("FSC-A", "SSC-A")], xlim = c(0, 1e+06), ylim = c(0, 2e+06))
ここで, 以下に示す図において赤線で囲まれている長方形の部分のデータをゲーティングしてみます. ゲーティングにはrectangleGate()を使います.
R
abline(v=c(1e+05, 6e+05), h=c(0, 7e+05), col="red")    #赤線を描画
R
rectGate = rectangleGate(filterId="Rect", "FSC-A"=c(1e+05, 6e+05), "SSC-A"=c(0, 7e+05))    #ゲーティングを作成してRectという名前をつける
result = flowCore::filter(sample, rectGate)    #flowCoreパッケージのfilter関数であることを明示的に記述
summary(result)

Rect+: 10197 of 10671 events (95.56%)

流れとしては, ゲーティングの枠を作って(1行目), それをfilter()でsampleに適応させる(2行目)イメージです. 結果を見てみると, "Rect"というゲーティングには全10671細胞のうち95.56%にあたる 10197細胞が含まれていることがわかります. rectangleGate()は長方形(2次元の場合. 多次元での指定も可能)のゲーティングですが, もう少し複雑な領域の指定を行いたい場合はpolygonGate()を用います. 例えば, 下図の赤線で囲まれた領域をゲーティングすることを考えます.
R
loci = data.frame(x = c(122599, 180626.3, 435623.5, 576054, 514038.5, 324890.5, 135691.1), y = c(64095.739, 3031.304, 123772.963, 490907.576, 
        778359.222, 535815.096, 172651.711))
plot(sample@exprs[, c("FSC-A", "SSC-A")], xlim = c(0, 1e+06), ylim = c(0, 2e+06))
points(loci, col = "red", pch = 20)
lines(loci, col = "red")
R
colnames(loci) = c("FSC-A", "SSC-A") #x軸とy軸に名前をつける
polyGate = polygonGate(filterId = "Poly", loci) #ゲーティングを作成してPolyという名前をつける
result = flowCore::filter(sample, polyGate)
summary(result)

Poly+: 10090 of 10671 events (94.56%) 

polygonGate()は座標の行列あるいはデータフレームを受け取り, その各座標で囲まれた部分のゲーティングとなります. この座標をグラフの見た目から決めるのは困難であるため, 視覚的に行えるように工夫します. 具体的には, グラフ上でクリックした位置の座標を返すlocator()を使います.
R
get.loci = function() {
        loci = matrix(NA, 1, 2)
        for (i in 1:20) {
                if (i == 1) { #一番最初の座標の場合
                        loci = as.data.frame(locator(1))
                        points(loci[i, ])
                } else {
                        locus = as.data.frame(locator(1))
                        if (sqrt(((locus - loci[i - 1, ])[1, 1])^2 + ((locus - loci[i - 1, ])[1, 2])^2) < 10) {
                                lines(x = c(loci[i - 1, 1], loci[1, 1]), y = c(loci[i - 1, 2], loci[1, 2]))
                                break #前回の座標と今回の座標の距離が10未満だった場合は座標の取得を終了する
                        }
                        loci = rbind(loci, locus) #lociに座標を追加していく
                        points(loci[i, ])
                        lines(x = c(loci[i - 1, 1], loci[i, 1]), y = c(loci[i - 1, 2], loci[i, 2]))
                }
        }
        return(loci)
}
get.loci関数を定義したので, さっそく使ってみましょう. get.loci()は, あらかじめplotされた図に対してクリックしたところの座標を取得しつつ, 前回取得した座標と今回取得した座標を結ぶ線分を描画していきます. 座標取得を終了するには, 同じ箇所(厳密には非常に近い位置)で2回クリックします.
R
plot(sample@exprs[, c(2, 4)], xlim = c(0, 1e+06), ylim = c(0, 2e+06))
loci = get.loci()
loci

           x           y
1  104843.52   -4040.746
2  294512.18  -27324.112
3  535055.54   55704.479
4  682037.74  319369.555
5  828802.49  688952.186
6  798527.15 1152457.084
7  627169.04 1172626.028
8  459621.81 1005263.237
9  331184.77  761971.105
10 187744.52  471649.839
11  96186.06  315425.527 

あとはcolnames()で名前をつけてploygonGate()に放り込めばゲーティングが作成できます. filter()は与えられたゲーティングに含まれる細胞数と割合を返してくれますが, 実際にデータが抽出されているわけではありません. データの抽出にはSubset()を使います. 使い方はfilter()と同じです. 返り値はsampleと同じflowFrameの形です.
R
colnames(loci) = c("FSC-A", "SSC-A")
polyGate = polygonGate(filterId = "Poly", loci) 
result = Subset(sample, polyGate)

plot(result@exprs[, c("FSC-A", "SSC-A")], xlim = c(0, 1e+06), ylim = c(0, 2e+06))
Subset()で得られたresultをplotしてみると, ゲーティングした領域のみになっていることがわかります.

ここで得られたresultをさらに別のゲーティングに持ち込むことで複数回のゲーティングを行うことも可能です. 例としてresultをDAPIの蛍光強度によってさらにゲーティングしてみたいと思います.
R
plot(result@exprs[, c("FSC-A", "FL7-A")], xlim = c(0, 7e+05), ylim = c(-500, 8000))    #"FL7-A"はDAPI-Aに相当
loci2 = get.loci()
colnames(loci2) = c("FSC-A", "FL7-A")
polyGate2 = polygonGate(filterId = "Poly2", loci2)
result2 = Subset(result, polyGate2)

plot(result2@exprs[, c("FSC-A", "FL7-A")], xlim = c(0, 7e+05), ylim = c(-500, 8000))