RのS3メソッドディスパッチ

RのS3メソッドディスパッチを確認します。

本ポストでは関数 breakpoints {strucchange} を例として、S3メソッドディスパッチ を確認します。

breakpoints {strucchange}については、こちらのポストを確認してください。

Rの関数:breakpoints {strucchange}
Rの関数から breakpoints {strucchange} を確認します。breakpoints関数についてbreakpoints関数は、線形回帰モデルにおける1つまたは複数の未知の構造変化点(ブレークポイント)を推定するための関数で...

1. ジェネリック関数とメソッド

始めに関数breakpointsのメソッドを確認します。

library(strucchange)
breakpoints
function (obj, ...) 
{
    UseMethod("breakpoints")
}
<bytecode: 0x0000023a0e537760>
<environment: namespace:strucchange>
methods(breakpoints)
[1] breakpoints.breakpointsfull* breakpoints.formula*         breakpoints.Fstats*         
see '?methods' for accessing help and source code

Rにおいて、breakpointsのような関数は「ジェネリック関数 (Generic Function)」と呼ばれます。ジェネリック関数は、それ自体が具体的な計算処理を持つわけではなく、いわば司令塔のような役割を果たします。

ジェネリック関数の主な仕事は、渡された引数(特に第一引数)のclass属性を調べ、そのクラスに対応する具体的な処理関数(メソッド)を探し出して実行を委任することです。

メソッドは、通常「ジェネリック関数名.クラス名」という命名規則に従っています。上記の methods(breakpoints)で表示された3つの関数が、breakpointsというジェネリック関数に対応するメソッド群です。

  • breakpoints.formula
  • breakpoints.breakpointsfull
  • breakpoints.Fstats

2. メソッドディスパッチの流れ

例えば、breakpoints関数を以下のように呼び出すとします。

# サンプルデータの作成
n <- 200
true_bp1 <- 80
true_bp2 <- 140
x <- 1:n
error <- rnorm(n, mean = 0, sd = 1)
y <- numeric(n)
y[1:true_bp1] <- 10 + 0.5 * x[1:true_bp1] + error[1:true_bp1]
start_val_regime2 <- 10 + 0.5 * true_bp1
y[(true_bp1 + 1):true_bp2] <- start_val_regime2 - 1.5 * (x[(true_bp1 + 1):true_bp2] - true_bp1) + error[(true_bp1 + 1):true_bp2]
start_val_regime3 <- start_val_regime2 - 1.5 * (true_bp2 - true_bp1)
y[(true_bp2 + 1):n] <- start_val_regime3 + 1.0 * (x[(true_bp2 + 1):n] - true_bp2) + error[(true_bp2 + 1):n]
sim_data <- data.frame(x = x, y = y)

# 関数breakpoints の実行
bp_model <- breakpoints(y ~ x, data = sim_data, breaks = 2, h = 0.1)
print(bp_model)

     Optimal 3-segment partition: 

Call:
breakpoints.formula(formula = y ~ x, h = 0.1, breaks = 2, data = sim_data)

Breakpoints at observation number:
80 139 

Corresponding to breakdates:
0.4 0.695 

このとき、Rの内部では次のような処理(メソッドディスパッチ)が行われています。

  1. ジェネリック関数 breakpoints() が呼び出されます。
  2. breakpoints()は、最初の引数である y ~ x のクラスを確認します。y ~ x のようにチルダ(~)を使って記述されたオブジェクトのクラスは formula です。
# y ~ x のクラスを確認
class(y ~ x)
[1] "formula"
  1. Rは、「breakpointsというジェネリック関数」と「formulaというクラス」を組み合わせた名前のメソッド、つまり breakpoints.formula を探します。
  2. breakpoints.formulaメソッドが見つかったため、Rはこのメソッドに引数を渡して実行します。

3. 他のメソッドが実行されるケース

ちなみに、他の2つのメソッドは以下のような場合に実行されます。

  • breakpoints.breakpointsfull: breakpoints()関数の実行結果(bp_modelなど)は、breakpointsfullというクラスを持っています。このオブジェクトに対して再度breakpoints()を適用した場合に、このメソッドが呼び出されます。これは、一度計算したRSS.tableを再利用して、異なる数の変化点を抽出したい場合などに利用されます。
# 関数breakpointsの実行結果のクラスを確認
class(bp_model)
[1] "breakpointsfull" "breakpoints"    
# breakpoints.breakpointsfullが呼び出されているか確認
# さらに、モデルを再利用し、今回は4つの構造変化点を抽出
breakpoints(bp_model, breaks = 4)

     Optimal 5-segment partition: 

Call:
breakpoints.breakpointsfull(obj = bp_model, breaks = 4)

Breakpoints at observation number:
23 46 79 139 

Corresponding to breakdates:
0.115 0.23 0.395 0.695 
  • breakpoints.Fstats: strucchangeパッケージには、F検定統計量を計算するFstats()という関数があります。このFstats()の実行結果(Fstatsクラスのオブジェクト)をbreakpoints()関数に渡すと、breakpoints.Fstatsメソッドが呼び出されます。

    なお、関数Fstats()の目的は、「データに構造変化が本当に存在するのか?」を検定することです。具体的には、「回帰モデルのパラメータは期間を通じて一定である(構造変化は無い)」という帰無仮説を、「パラメータがどこか1つの時点で変化した(構造変化が1つある)」という対立仮説に対して検定します。つまり、変化の有無を判断するためのツールです。

    関数の引数は以下の通りです。

# 関数Fstatsの引数を確認
args(Fstats)
function (formula, from = 0.15, to = NULL, data = list(), vcov. = NULL) 
NULL
  • formula:

    検定したいモデルの構造をy ~ xのようにformulaオブジェクトで指定します。breakpoints関数と同じです。

  • from:

    変化点を探索する期間の開始点を、データ全体の割合(0から1)で指定します。デフォルトは0.15です。これは、データ全体の最初の15%を探索範囲から除外することを意味します。なぜなら、あまりに端に近い点を変化点として検定しようとすると、分割された一方のセグメントのデータ数が少なすぎて、意味のある回帰分析や比較ができないためです。この除外する期間を「トリミング (trimming)」と呼びます。

  • to:

    変化点を探索する期間の終了点を、データ全体の割合で指定します。この引数がNULL(デフォルト)の場合、自動的に1 - fromの値が設定されます。つまり、from = 0.15の場合、to0.85となり、データの真ん中の70%(15%地点から85%地点まで)を対象に変化点を探索します。

  • data:

    formulaで使用する変数が含まれるデータフレームを指定します。

  • vcov.:

    残差の自己相関や不均一分散を考慮した頑健な分散共分散行列を計算するための関数を指定できます(例:sandwich::NeweyWest)。

# 関数Fstatsの実行結果のクラスを確認
fstats_obj <- Fstats(formula = y ~ x, data = sim_data, from = 0.15)
class(fstats_obj)
[1] "Fstats"
# breakpoints.Fstatsが呼び出されているか確認
breakpoints(fstats_obj)

     Optimal 2-segment partition: 

Call:
breakpoints.Fstats(obj = fstats_obj)

Breakpoints at observation number:
112 

Corresponding to breakdates:
0.555 

以上です。