Open3

R で print 関数や summary 関数を使って統計モデルの推定結果を表示できる理由

畳屋民也畳屋民也

疑問

print 関数は、変数の中身などの表示にも使う汎用的な関数である。
summary 関数も、R にデフォルトで入っている stats パッケージのモデルだけでなく、他の統計モデリングパッケージでもモデル結果の表示にもよく用いられる。

どうやって表示内容を制御しているのだろうか?

例: 線形回帰モデルの推定結果の表示方法

res <- lm(y~x1+x2, data=df)
print(res)
Call:
lm(formula = y ~ x1 + x2, data = df)

Coefficients:
(Intercept)           x1           x2  
     2.0057       0.3052           NA  
summary(res)
Call:
lm(formula = y ~ x1 + x2, data = df)

Residuals:
       1        2        3        4        5 
 0.01590 -0.06889  0.01476 -0.01382  0.05205 

Coefficients: (1 not defined because of singularities)
            Estimate Std. Error t value Pr(>|t|)    
(Intercept) 2.005712   0.065289   30.72 7.58e-05 ***
x1          0.305160   0.002259  135.06 8.95e-07 ***
x2                NA         NA      NA       NA    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.05202 on 3 degrees of freedom
Multiple R-squared:  0.9998,	Adjusted R-squared:  0.9998 
F-statistic: 1.824e+04 on 1 and 3 DF,  p-value: 8.949e-07
畳屋民也畳屋民也

S3 システムについて

S3 とは、R のクラスシステムである(AWS のオブジェクトストレージのことではない)。
S3 システムを利用すれば、オブジェクトのクラス属性に応じて関数を変えることが可能である。

例: print 関数

数値ベクトルは、デフォルトでは class は numeric であり、print 関数に入れるとベクトル成分が表示される。

x <- c(1, 2, 3)
class(x)
print(x)
[1] "numeric"
[1] 1 2 3

これを今、無理やり print 関数による表示内容を変えてみる。
そのためには、変数 x の class を適当な "sample_class" にしたうえで、print.sample_class という関数を定義すればよい。

class(x) <- "sample_class"

print.sample_class <- function(x) {
  print("Hi! print.sample_class() is called!")
  invisible(x)
}

class(x)
print(x)
[1] "sample_class"
[1] "Hi! print.sample_class() is called!"

この時、print.sample_class は、「print 関数のメソッド」と呼ばれる。
他の言語でよくあるように「sample_classprint メソッド」という呼び方はしないらしい。

なお、methods 関数を使うと、メソッドの一覧を表示できる。
試しにprint 関数のメソッドを表示してみると、先ほどの print.sample_class が含まれていることが確認できる:

methods(print)
  [1] print.acf*                                          
  [2] print.activeConcordance*                            
  [3] print.anova*                                        
  [4] print.aov*                                          
  [5] print.aovlist*                                      
(中略)
  [164] print.sample_class
(略)

メソッドを持つ関数を自分で作ってみる

先ほどの print 関数のようにデフォルトで用意されている関数以外にも、自分で作った関数にメソッドを持たせることができる。

例えば、以下のように UseMethod という関数を使って dosomothing という関数を定義する:

dosomething <- function(object, ...) UseMethod("dosomething")
dosomething
function(object, ...) UseMethod("dosomething")

この時点で先ほどの x を引数に取っても、エラーになる:

dosomething(x)
Error in UseMethod("dosomething") : 
  no applicable method for 'dosomething' applied to an object of class "sample_class"

しかし、 先ほどの sample_class に対応する dosomething メソッドとして dosomething.sample_class という関数を定義すると、dosomoething 関数が x を引数に取れるようになる:

dosomething.sample_class <- function(x) {
  print.default("Hi! I'm doing something!")
}

dosomething(x)
[1] "Hi! I'm doing something!"

参考文献

Garrett Grolemund 著(大橋真也 監訳、長尾高弘 訳)「RStudioではじめるRプログラミング入門」(O'REILLY, 2015)
https://amzn.asia/d/aE3Iuje

畳屋民也畳屋民也

lm 関数の print, summary はどのように定義されているか?

R にデフォルトで入っている stats パッケージの線形回帰関数 lm について、作成したモデルオブジェクトを printsummary の引数に取った時の挙動がどのように定義されているかを調べる。

https://www.rdocumentation.org/packages/stats/versions/3.6.2/topics/lm

なお、lm 関数のコードはこちらから読むことができる。

作成したモデルのクラス

lm 関数の定義自体はこちらである。

モデル推定結果は z という変数に入っており、これ最終的な返り値となる。

https://github.com/SurajGupta/r-source/blob/a28e609e72ed7c47f6ddfbb86c85279a0750f0b7/src/library/stats/R/lm.R#L68-L69

着目する部分は以下である:
https://github.com/SurajGupta/r-source/blob/a28e609e72ed7c47f6ddfbb86c85279a0750f0b7/src/library/stats/R/lm.R#L72

ここで、目的変数 y が1列のベクトルであれば、変数 z の class が lm に指定されている。

print.lm 関数

以下に、print.lm 関数が定義されている:
https://github.com/SurajGupta/r-source/blob/a28e609e72ed7c47f6ddfbb86c85279a0750f0b7/src/library/stats/R/lm.R#L248-L259

以前のノートで触れたように、
class が lm の変数 zprint 関数の引数にとると、この print.lm が呼ばれる。

summary 関数と summary.lm 関数

同様に summary 関数に lm 関数で得られた結果を渡した時に何が起きるかを見てみる。

summary 関数は、以下のように定義されている:
https://github.com/SurajGupta/r-source/blob/a28e609e72ed7c47f6ddfbb86c85279a0750f0b7/src/library/base/R/summary.R#L19

したがって、summary.lm が定義されていれば、lm 関数の返り値を summary に渡すと、summary.lmsummary のメソッドとして認識されて呼ばれる。

そして summary.lm は以下で定義されている:
https://github.com/SurajGupta/r-source/blob/a28e609e72ed7c47f6ddfbb86c85279a0750f0b7/src/library/stats/R/lm.R#L261