BioconductorにおけるS4クラス
Last updated on 2025-07-22 | Edit this page
Overview
Questions
- S4クラスシステムとは?
- BioconductorはS4クラスをどのように使用しますか?
- Bioconductorの
DataFrame
は、基本のdata.frame
とどう違うのですか?
Objectives
- S4クラス、ジェネリックおよびメソッドとは何かを説明してください。
- Bioconductorパッケージインフラストラクチャの中心にあるS4クラスを特定します。
- さまざまなS4オブジェクトを作成し、関連するS4メソッドを適用します。
パッケージのインストール
以下のセクションに進む前に、必要なBioconductorパッケージをいくつかインストールします。
最初に、BiocManagerパッケージがインストールされているかを確認し、それを使用しようとする前にインストールします。
次に、BiocManager::install()
関数を使用して必要なパッケージをインストールします。
R
if (!requireNamespace("BiocManager", quietly = TRUE))
install.packages("BiocManager")
BiocManager::install("S4Vectors")
講師のために
このエピソードの最初の部分は、少し理論的に見えるかもしれません。
S4クラスシステムの内部動作に関する詳細に深入りしないでください(例えば、new()
関数に言及する必要はありませんし、クラスを作成する具体的なコード例を示す必要もありません)。
代わりに、最初の図のキャプションでは、技術用語を単純な文で導入しながら、図を通じて段階的に説明し、S4クラスシステムの中心となるメソッドディスパッチの概念に至り、初心者ユーザーにとっての混乱の原因となることを示しています。
S4クラスとメソッド
メソッドパッケージ
S4クラスシステムは、基本パッケージmethodsに実装されています。 そのため、この概念はBioconductorプロジェクトに特有なものではなく、さまざまな独立したパッケージにも存在します。 この主題は、Hadley Wickhamによるオンライン書籍Advanced Rで詳しく文書化されています。 ほとんどのBioconductorユーザーは、S4クラスシステムの詳細を過度に熟知する必要がないでしょう。 むしろ、Bioconductorプロジェクトにおけるパッケージの効率的な使用の鍵は、S4クラスシステムを使用する重要な動機と、ユーザー向けの機能に関するベストプラクティス(クラス、ジェネリック、メソッドを含む)を十分に理解することにあります。 このエピソードの次のセクションでは、BioconductorプロジェクトにおけるS4クラスとメソッドの基本的な機能性とユーザーエクスペリエンスに焦点を当てます。
一方で、S4クラスは、Rセッション内の変数名に割り当てることができる計算オブジェクトにおいて、任意に複雑な情報を格納できるデータ構造を提供します。 もう一方で、S4ジェネリックおよびメソッドは、それらのオブジェクトを処理するために適用される関数を定義します。
これまでの数年で、BioconductorプロジェクトはS4クラスシステムを使用して、ほとんどの生物学的アッセイ(生アッセイデータや処理済みアッセイデータ、個別の特徴やサンプルに関する実験メタデータ、関連する他のアッセイ特有の情報を含む)のデータを格納および処理できるいくつかのクラスとメソッドを開発してきました。 Bioconductorパッケージ全体で一般的に使用される標準S4クラスに慣れることは、分析ワークフローを開発したいユーザーがベストプラクティスに従う自信を高めるための重要なステップです。
S4クラス、ジェネリック、およびメソッド。
左側には、S4Class1
とS4Class2
という2つの例のクラスが、継承の概念を示しています。
クラスS4Class1
には、データを格納するためのSlotName1
とSlotName2
という2つのスロットがあります。
これらの2つのスロットは、それぞれSlotType1
およびSlotType2
型のオブジェクトを格納するように制限されています。
このクラスは、オブジェクトが更新されるたびにデータの整合性を確認する有効性ルールも定義しています。
クラスS4Class2
は、S4Class1
からすべてのスロットと有効性ルールを継承し、新しいスロットSlotName3
と新しい有効性ルールを定義します。
例のコードは、それぞれのクラスのオブジェクトが通常、対応するクラスにちなんで命名されたコンストラクタ関数を使用してどのように作成されるかを示しています。
右側には、1つのジェネリック関数と2つのメソッドが多様性の概念とS4メソッドディスパッチのプロセスを示しています。
ジェネリック関数S4Generic1()
は、関数の名前とその引数を定義します。
ただし、その関数の実装は提供されていません。
代わりに、2つのメソッドが定義され、それぞれ特定のクラスの入力に対して単純な実装を提供します。
具体的には、最初のメソッドは、S4Class1
のオブジェクトが引数x
として与えられた場合のS4Generic1()
の実装を定義し、2番目のメソッドは、S4Class2
のオブジェクトが引数x
として与えられた場合のS4Generic1()
の別の実装を提供します。
ジェネリック関数S4Generic1()
が呼び出されると、メソッドディスパッチと呼ばれるプロセスが発生し、渡されたオブジェクトのクラスに応じてS4Generic1()
メソッドの適切な実装が呼び出されます。
スロットと有効性
S3クラスシステムは基本Rに直接用意されているのに対し(このレッスンでは説明されていません)、S4クラスシステムは、Rにおけるオブジェクト指向プログラミング(OOP)のためのクラスとメソッドのより厳格な定義を提供します。
OOPモデルを実装している多くのプログラミング言語と同様に、S4クラスは実世界のエンティティを、情報を*slots*
と呼ばれる1つ以上の内部コンポーネントに格納する計算オブジェクトとして表現するために使用されます。
クラスの定義は、各スロットに格納される可能性のあるデータのタイプを明示します。不適切なデータを格納しようとした場合にはエラーが発生します。
さらに、クラスの定義には、型を超えてオブジェクトに格納されるデータの有効性を確認するコードを含めることもできます。
例えば、numeric
型のスロットを使用して人の年齢を格納することはできますが、有効性メソッドは格納される値が実際に正であることを確認できます。
継承
OOPモデルの中核的な柱の1つは、既存のクラスの機能を継承して拡張できる新しいクラスを開発する可能性です。 S4クラスシステムはこのパラダイムを実装しています。
新しいS4クラスの定義は、継承する他のクラスの名前を宣言します。 新しいクラスには、親クラスのすべてのスロットが含まれ、新しいクラスの定義に追加される新しいスロットが含まれます。
新しいクラスの定義では、新しい有効性チェックも定義でき、親クラスの各クラスに実装された各有効性チェックに追加されます。
ジェネリックおよびメソッド
クラスが情報を格納するデータ構造を定義する一方で、ジェネリックおよびメソッドは、それらのクラスからインスタンス化されたオブジェクトに適用される関数を定義します。
S4ジェネリック関数は、いくつかの重要な引数に依存して、異なる振る舞いが期待される関数の名前を宣言するために使用されます。 その代わりに、S4メソッドは、各特定の入力の組み合わせに対してジェネリック関数の異なる実装を定義するために使用されます。
ジェネリック関数が呼び出され、S4オブジェクトが渡されると、メソッドディスパッチと呼ばれるプロセスが発生し、オブジェクトのクラスが実行すべき適切なメソッドを決定するために使用されます。
S4Vectorsパッケージ
S4Vectorsパッケージは、Vector
およびList
の仮想クラスと、Rの通常のベクトルおよびリストのセマンティクスを拡張する一連のジェネリック関数を定義しています。
S4クラスシステムを使用することで、パッケージ開発者はVector
やList
の具体的なサブクラスとして、ベクトルやリストに似たオブジェクトを簡単に実装できます。
仮想クラス(Vector
とList
など)は、オブジェクト自体としてインスタンス化することはできません。
むしろ、これらの仮想クラスは、それらから派生したすべての具体的なクラスが継承する基本機能を提供します。
代わりに、一般的に興味のあるいくつかの低レベルの具体的なサブクラス(例えば、DataFrame
、Rle
、Hits
)は、S4Vectorsパッケージ自体に実装されています。また、Bioconductorプロジェクト全体の他のパッケージ(例えば、IRanges)にも、多くの他のパッケージが実装されています。
次のように、パッケージを現在のRセッションにアタッチします。
R
library(S4Vectors)
注意
パッケージをセッションにアタッチした場合、コンソールに印刷されるパッケージ起動メッセージには、S4Vectorsパッケージが、セッションにアタッチされると、baseパッケージから数機能をマスクすることが記載されています。 これは、S4Vectorsパッケージが、これらの関数の実装を含んでいることを意味し、Rセッションにアタッチされた最新のパッケージであるため、その関数の独自の実装がR検索パス上で最初に見つかり、baseパッケージの元の実装ではなく、使用されます。
多くの場合、マスクされた関数は、特に問題なく以前のように使用できます。
時折、パッケージ名に加えて関数名を使ってマスクされた関数を呼び出す必要があるかもしれません。例:base::anyDuplicated()
。
DataFrameクラス
長方形データの概念への拡張
S4Vectorsパッケージで実装されたDataFrame
クラスは、基本Rのdata.frame
クラスやtidyverseのtibble
に慣れているユーザーに馴染みのある長方形データの概念を拡張しています。
具体的には、DataFrame
は、カラムとしてあらゆるタイプのオブジェクト(length
および[
メソッドを持つ)を格納することをサポートしています。
全体として、DataFrame
クラスは、構築、部分的選択、分割、結合などの観点から、非常に似た形で振る舞うS4クラスの正式な定義を提供します。
新しいオブジェクトを作成するには、DataFrame()
コンストラクタ関数を使用する必要があります。これは、基本Rのdata.frame()
に相当します。
その関数のヘルプページは、?DataFrame
でアクセスできますので、詳細情報を参照できます。
R
DF1 <- DataFrame(
Integers = c(1L, 2L, 3L),
Letters = c("A", "B", "C"),
Floats = c(1.2, 2.3, 3.4)
)
DF1
OUTPUT
DataFrame with 3 rows and 3 columns
Integers Letters Floats
<integer> <character> <numeric>
1 1 A 1.2
2 2 B 2.3
3 3 C 3.4
実際、DataFrame
オブジェクトは、同等のdata.frame
オブジェクトに簡単に変換できます。
R
df1 <- as.data.frame(DF1)
df1
OUTPUT
Integers Letters Floats
1 1 A 1.2
2 2 B 2.3
3 3 C 3.4
逆に、as()
関数を使用してdata.frame
オブジェクトをDataFrame
に変換することもできます。
R
as(df1, "DataFrame")
OUTPUT
DataFrame with 3 rows and 3 columns
Integers Letters Floats
<integer> <character> <numeric>
1 1 A 1.2
2 2 B 2.3
3 3 C 3.4
基本のdata.frameとの違い
最も注目すべき例外は、行名の処理に関するものです。
まず、行名はオプションです。
これは、行名がない場合、rownames(x)
がNULL
を返すことを意味します。
R
rownames(DF1)
OUTPUT
NULL
これは、data.frame
とは異なり、rownames(x)
がas.character(seq_len(nrow(x)))
の等価物を返すことになります。
R
rownames(df1)
OUTPUT
[1] "1" "2" "3"
ただし、NULL
を返すことは、例えば、組み合わせ関数に対して、行名が不要であることを通知します(大規模なデータを扱うときには、しばしば贅沢です)。
さらに、DataFrame
オブジェクトの行名は一意である必要はありません。これは、基本Rのdata.frame
とは対照的です。
行名は、長方形データで観測結果を一意に特定してインデックス付けするために使用されるため、しばしば議論の原因となります。
設定されている場合、行名は[
演算子を使用して長方形データを部分選択するために使用できます。
非一意の行名はその目的を妨げ、選択された各行名の最初の出現だけが抽出されるため、予期しない結果をもたらす可能性があります。
その代わりに、tidyverseのtibble
は、行名を完全に設定する機能を排除し、ユーザーに特定のカラムに情報を明示的にストアさせるように強制しています。また、行を効率的にフィルタリングするための関数を提供し、[
演算子を使用する必要はありません。
R
DF2 <- DataFrame(
Integers = c(1L, 2L, 3L),
Letters = c("A", "B", "C"),
Floats = c(1.2, 2.3, 3.4),
row.names = c("name1", "name1", "name2")
)
DF2
OUTPUT
DataFrame with 3 rows and 3 columns
Integers Letters Floats
<integer> <character> <numeric>
name1 1 A 1.2
name1 2 B 2.3
name2 3 C 3.4
チャレンジ
上記の例を使用して、DF2["name1", ]
は何を返しますか?
なぜですか?
> DF2["name1", ]
DataFrame with 1 row and 3 columns
Integers Letters Floats
<integer> <character> <numeric>
name1 1 A 1.2
行名name1
に一致した行の最初の出現のみが返されます。
この場合、行名には特別な意味がなく、それらの必要性を正当化することが難しいです。
代わりに、ユーザーは、DF2[rownames(DF2) == "name1", ]
のように、行名name1
に一致するすべての行をより明示的に抽出することができます。
ユーザーは、特定の状況で行名を使用する動機、何を表し、分析中にどのように使用されるべきかを意識するべきです。
最後に、DataFrame
の行名は、部分一致による選択をサポートしません。これは、基本のdata.frame
とは対照的です。
DataFrame
のより厳格な動作により、予期しない結果が生じることは、意図しないユーザーによって防止されます。
R
DF3 <- DataFrame(
Integers = c(1L, 2L, 3L),
Letters = c("A", "B", "C"),
Floats = c(1.2, 2.3, 3.4),
row.names = c("alpha", "beta", "gamma")
)
df3 <- as.data.frame(DF3)
チャレンジ
上記の例を使用して、DF3["a", ]
とdf3["a", ]
の出力は何ですか?
なぜ違うのですか?
> DF3["a", ]
DataFrame with 1 row and 3 columns
Integers Letters Floats
<integer> <character> <numeric>
<NA> NA NA NA
> df3["a", ]
Integers Letters Floats
alpha 1 A 1.2
DataFrame
オブジェクトは、部分一致による行名のマッチングを実行せず、そのため、行がマッチせず、NA
値のいっぱいのDataFrame
を返しました。
その代わりに、data.frame
オブジェクトは部分一致による行名マッチングを実行し、要求された"a"
を"alpha"
行名に一致させ、新しいdata.frame
オブジェクトとして該当行を返しました。
インデックス付け
通常のdata.frame
と同様に、カラムは$
、[
、および[[
を使用してアクセスできます。
各演算子には異なる目的があり、最も適切なものは、達成しようとしていることによく依存します。
例えば、ドル演算子$
は、名前で単一列を抽出するために使用できます。
それはしばしばベクターであることが多いですが、それはその列内のデータの性質によって異なる場合もあります。
この演算子は、インタラクティブなRセッションで非常に便利で、利用可能な列名の自動補完を提供します。
R
DF3$Integers
OUTPUT
[1] 1 2 3
同様に、ダブルブラケット演算子[[
も単一の列を抽出するために使用できます。
これは$
よりも柔軟性があり、文字列名と整数インデックスの両方を処理できます。
R
DF3[["Letters"]]
OUTPUT
[1] "A" "B" "C"
R
DF3[[2]]
OUTPUT
[1] "A" "B" "C"
演算子[
は、行と列の同時選択や、単一列の選択がDataFrame
またはvector
として返されるかどうかを制御する際に最も便利です。
R
DF3[2:3, "Letters", drop=FALSE]
OUTPUT
DataFrame with 2 rows and 1 column
Letters
<character>
beta B
gamma C
メタデータ列
DataFrame
における最も注目すべき新機能は、メタデータを別のDataFrame
でカラムに保持できる能力です。
メタデータ列。
メタデータ列は、DataFrame
オブジェクトの文脈で示されます。
左側には、A
とB
という名前の列を持つDataFrame
オブジェクトDF
が作成されます。
右側には、DF
のメタデータ列をmcols(DF)
を使ってアクセスします。
この例では、名前meta1
とmeta2
の2つのメタデータ列が作成されます。
メタデータ列は、親DataFrame
の各列に対して1行を含むDataFrame
として格納されます。
メタデータ列は、関数mcols()
を使用してアクセスされます。
メタデータ列が定義されていない場合、mcols()
は単にNULL
を返します。
R
DF4 <- DataFrame(
Integers = c(1L, 2L, 3L),
Letters = c("A", "B", "C"),
Floats = c(1.2, 2.3, 3.4),
row.names = c("alpha", "beta", "gamma")
)
mcols(DF4)
OUTPUT
NULL
関数mcols()
は、メタデータ列を追加、編集、または削除するためにも使用できます。
例えば、2つの列を持つDataFrame
として、メタデータ列を初期化できます。
- 対応する列に格納されている値のタイプを示す1つの列
- 対応する列で観察された異なる値の数を示す1つの列
R
mcols(DF4) <- DataFrame(
Type = sapply(DF4, typeof),
Distinct = sapply(DF4, function(x) { length(unique(x)) } )
)
mcols(DF4)
OUTPUT
DataFrame with 3 rows and 2 columns
Type Distinct
<character> <integer>
Integers integer 3
Letters character 3
Floats double 3
注意
メタデータ列の行名は、親DataFrame
の列名と一致するように自動的に設定され、列とメタデータのペアを明確に示します。
実行長エンコーディング (RLE)
ベクターの概念への拡張
S4Vectorsで実装されたDataFrame
クラスと同様に、Rle
クラスは基本パッケージのrle()
関数にS4の拡張を提供します。
具体的には、Rle
クラスは、原子ベクターを実行長エンコーディング形式で保存することをサポートしています。
実行長エンコーディング。 実行長エンコーディングの概念は、核酸の配列の例を使用してここに示されています。 エンコードの前に、配列の各位置にある各ヌクレオチドはメモリに明示的に保存されます。 エンコーディング中に、同一のヌクレオチドの連続したランは、ヌクレオチドの同一性とランの長さの2ビットの情報に圧縮されます。
実行長エンコーディングは、同一の情報の頻繁な連続が含まれるベクターのメモリ使用量を劇的に削減できます。
たとえば、実行長エンコーディングの有力な応用は、シーケンシング実験におけるゲノムカバレッジの表現です。ここで、大きなゲノム領域にマッピングされたリードが存在しない場合、長い0
の値の連続が発生します。
各個別の値を保存することは、メモリ使用量の観点から非常に非効率的です。
その代わりに、実行長エンコーディングプロセスは、冗長な情報のそのような連続を、同一の情報の任意の長い連続から2つの値、すなわち繰り返される値自体と、それが繰り返される回数に圧縮します。
R
v1 <- c(0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 2, 1, 0, 0, 0, 0, 0)
rle1 <- Rle(v1)
rle1
OUTPUT
numeric-Rle of length 17 with 7 runs
Lengths: 7 1 1 1 1 1 5
Values : 0 1 2 3 2 1 0
インデクシング
通常のvector
と同様に、Rle
オブジェクトは[
を使用してインデックス付けすることができます。
R
rle1[2:4]
OUTPUT
numeric-Rle of length 3 with 1 run
Lengths: 3
Values : 0
使用法
ベクターのようなオブジェクトとして、Rle
オブジェクトもDataFrame
オブジェクトの列として保存でき、他のベクター状のオブジェクトとともに格納できます。
R
v2 <- c(rep(1, 5), rep(2, 5))
rle2 <- Rle(v2)
DF5 <- DataFrame(
vector = v2,
rle = rle2,
equal = v2 == rle2
)
DF5
OUTPUT
DataFrame with 10 rows and 3 columns
vector rle equal
<numeric> <Rle> <Rle>
1 1 1 TRUE
2 1 1 TRUE
3 1 1 TRUE
4 1 1 TRUE
5 1 1 TRUE
6 2 2 TRUE
7 2 2 TRUE
8 2 2 TRUE
9 2 2 TRUE
10 2 2 TRUE
さらに進む
Rle
オブジェクトに関する多くの標準操作は、Rle
クラスのヘルプページに文書化されており、?Rle
としてアクセス可能で、S4Vectorsパッケージのビネットにおいても、browseVignettes("S4Vectors")
を使ってアクセスできます。
- S4クラスはスロットに情報を保存し、オブジェクトが更新されるたびに情報の有効性を確認します。
- S4オブジェクトの継続的な整合性を確保するために、ユーザーはスロットに直接アクセスせず、専用の関数を使用する必要があります。
- S4ジェネリックは、与えられたオブジェクトのクラスに応じてメソッドの異なる実装を呼び出します。
- S4クラス
DataFrame
は、data.frame
の基本機能を拡張しており、メタデータ列の各列に関する情報を保持できる能力を持っています。 - S4クラス
Rle
は、基本のvector
の機能を拡張しており、メモリ効率の良い形式で繰り返しベクターをエンコードする能力を持っています。