data.frameを拡張するには 成功編(S4でふつうに)
前回までのあらすじ
(data.frameを拡張するには 失敗編(S3でゆるふわ) - Technically, technophobic.)
data.frame
と同じようにふるまうけど、スキーマ情報やリビジョン番号などのメタデータを持ってる、みたいなクラスをつくりたい- S3でふわっとやろうとしたけど無理だった
- ということでS4でやってみよう
(この実装を見てやり方を知りました:EML/R/data.set.R at master · ropensci/EML)
S4でやってみる
S4のクラス
S4では、スーパークラスを指定できます。
スーパークラスとしてdata.frame
を指定すれば、
data.frame
のような挙動をするクラスをつくることができます。
S4のクラス宣言はsetClass
を使います。
> args(setClass) function (Class, representation = list(), prototype = NULL, contains = character(), validity = NULL, access = list(), where = topenv(parent.frame()), version = .newExternalptr(), sealed = FALSE, package = getPackageName(where), S3methods = FALSE, slots)
contains
は、スーパークラス名を指定するパラメータです。
これにdata.frame
を指定します。
slots
は、クラスのプロパティ名とクラスを宣言します。
ここに、必要なメタデータを入れます。
> setClass("myclass", slots = c(url="character"), contains = "data.frame")
S4のインスタンス作成
S4のインスタンスは、new
でつくります。
> a <- new("myclass")
なんですが、data.frame
と同じようにmyclass(...)
という感じでインスタンスをつくりたいので、
S3っぽいコンストラクタを定義します。(参照元のコードはこのへん)
> myclass <- function(url, ...) { new("myclass", data.frame(...), url=url) }
これを使ってmyclass
のインスタンスをつくると、data.frame
と同じ挙動をします。
> a <- myclass(x=rnorm(10), "http://notchained.hatenablog.com") > a Object of class "myclass" x 1 -1.30937798 2 -0.45263021 3 -1.70143079 4 -0.22452977 5 0.71383416 6 -0.07953994 7 0.50595564 8 -2.90918912 9 -2.10720904 10 -0.09992661 Slot "url": [1] "http://notchained.hatenablog.com" > a[1,] [1] -1.309378 > a[1:5,] [1] -1.3093780 -0.4526302 -1.7014308 -0.2245298 0.7138342 > subset(a, x > 0) x 5 0.7138342 7 0.5059556
S4のメソッド
めんどくさい(し、あんまりちゃんと理解できていない)ので説明は端折りますが、 S4クラスのメソッド定義は、
という順序を踏むらしいです。
たとえば、fetch
というメソッドを定義したければ、
まずstandardGeneric
でfetch
のジェネリックを作成し、
それをsetGeneric
で宣言します。
以下の2つはどちらも同じことです。
> setGeneric("fetch", function(x) standardGeneric("fetch")) [1] "fetch"
> fetch <- function(x) standardGeneric("fetch") > setGeneric("fetch")
そして、setMethod
で、myclass
のfetch
を実装します。
> setMethod("fetch", "myclass", function(x) getURL(x@url))
これでfetch
が使えるようになりました。
まとめ
まあ要はdata.frame
を継承したクラスをつくりたくて、
それをするにはS3ではむりでS4でやりましょう、ということでした。
おしまい。