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")
Prerequisite

講師のために

このエピソードの最初の部分は、少し理論的に見えるかもしれません。 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クラス、ジェネリックおよびメソッド。

S4クラス、ジェネリック、およびメソッド。 左側には、S4Class1S4Class2という2つの例のクラスが、継承の概念を示しています。 クラスS4Class1には、データを格納するためのSlotName1SlotName2という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クラスシステムを使用することで、パッケージ開発者はVectorListの具体的なサブクラスとして、ベクトルやリストに似たオブジェクトを簡単に実装できます。

仮想クラス(VectorListなど)は、オブジェクト自体としてインスタンス化することはできません。 むしろ、これらの仮想クラスは、それらから派生したすべての具体的なクラスが継承する基本機能を提供します。

代わりに、一般的に興味のあるいくつかの低レベルの具体的なサブクラス(例えば、DataFrameRleHits)は、S4Vectorsパッケージ自体に実装されています。また、Bioconductorプロジェクト全体の他のパッケージ(例えば、IRanges)にも、多くの他のパッケージが実装されています。

次のように、パッケージを現在のRセッションにアタッチします。

R

library(S4Vectors)
Callout

注意

パッケージをセッションにアタッチした場合、コンソールに印刷されるパッケージ起動メッセージには、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
Challenge

チャレンジ

上記の例を使用して、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)
Challenge

チャレンジ

上記の例を使用して、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オブジェクトの文脈で示されます。 左側には、ABという名前の列を持つDataFrameオブジェクトDFが作成されます。 右側には、DFのメタデータ列をmcols(DF)を使ってアクセスします。 この例では、名前meta1meta2の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
Callout

注意

メタデータ列の行名は、親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
Callout

さらに進む

Rleオブジェクトに関する多くの標準操作は、Rleクラスのヘルプページに文書化されており、?Rleとしてアクセス可能で、S4Vectorsパッケージのビネットにおいても、browseVignettes("S4Vectors")を使ってアクセスできます。

Key Points
  • S4クラスはスロットに情報を保存し、オブジェクトが更新されるたびに情報の有効性を確認します。
  • S4オブジェクトの継続的な整合性を確保するために、ユーザーはスロットに直接アクセスせず、専用の関数を使用する必要があります。
  • S4ジェネリックは、与えられたオブジェクトのクラスに応じてメソッドの異なる実装を呼び出します。
  • S4クラスDataFrameは、data.frameの基本機能を拡張しており、メタデータ列の各列に関する情報を保持できる能力を持っています。
  • S4クラスRleは、基本のvectorの機能を拡張しており、メモリ効率の良い形式で繰り返しベクターをエンコードする能力を持っています。