R言語逆引きハンドブックで気になった点

今日、R言語逆引きハンドブックの誤植情報が更新されたようなんですが、 載っていなかったことで気になった点について触れようと思います。

seq の使い方について

seq は連続した数を生成する関数で、for 文でベクトル x の要素に順番にアクセスするための整数を生成する場合などに使われます。
seq を使わずによく見られるのは次のような書き方です。

for (i in 1:length(x)) {
    print(x[i])
}

この書き方は length(x) が 0 の場合を考慮できていません。

このことについて、「R言語逆引きハンドブック」では p. 315 の『「for」関数の利用』で

「x」が空のときに「for」は実行されないことを望む場合は、「seq」関数を使って「for (i in seq(x))」とするのが安全です。

とあります。
一方、「Rの基礎とプログラミング技法」の p. 55 には次のように書いてあります。

seq(along = x) ならばループの実行を避けられるが, 1:length(x) では, 予期に反して, 長さ 2 のベクトル c(1, 0) が生成されてしまうからである.

両者似ているようですが、前者では length(x) が 1 の場合が考慮されていません。
seq(x) のように引数が1つだけ与えられた場合 seq.default では次のコードが実行されます。from = x です。

        lf <- length(from)
        return(if (mode(from) == "numeric" && lf == 1L) 1L:from else if (lf) 1L:lf else integer())

見ての通り、length(x) が 1 の場合とそれ以外の場合で違う結果を返します。

具体的に実行してみると

> seq(c())
integer(0)
> seq(c(2, 5))
[1] 1 2
> seq(5)
[1] 1 2 3 4 5

となります。
以上のように length(x) が 2 以上の場合は 1:length(x) の結果を返す一方で length(x) が 1 の場合は 1:x の結果を返すので seq(along = x) や seq_along(x)、seq(length = length(x)) を使うのが安全だと思います。

ただ、これが誤植かと言われると x が空の場合に意図しない for 文の実行を避けるという目的を満たす意味では誤植ではないので今回「気になった点」として石田先生には連絡させていただきました。

グローバル変数への代入について

「R言語逆引きハンドブック」の p. 595 に「グローバル・オブジェクトに代入するには<<-演算子を使用する」とありますが、よくない表現だと思います。説明には

これに対して<<-演算子は関数のフレームを上に遡って代入を行います。

とあります。正確にはフレームではなく環境だと思いますが、これがまさに<<-演算子の定義であり、同時にグローバル変数への代入演算子ではないことを説明していると思います。

help(“<<-“) には<<-演算子の定義として次のように書いてあります。

The operators ‘<<-’ and ‘->>’ cause a search to made through the environment for an existing definition of the variable being assigned. If such a variable is found (and its binding is not locked) then its value is redefined, otherwise assignment takes place in the global environment.

要は、上の環境を探索していって、指定した変数が定義されていればそれに値を代入するし、見つからなければグローバル変数として定義するということかと思います。
※”上の環境”という言葉がない気もしますが・・・

どういうことかというと、次の例を見てもらえばわかると思います。

> x <- "this is global environment"
> f <- function() {
+     x <- "this is function f"
+     g <- function() {
+         x <- "this is function g"
+         x <<- "substitution using '<<-' operator"
+         cat("print x in function g\n")
+         print(x)
+     }
+     g()
+     cat("print x in function f\n")
+     print(x)
+ }
> f()
print x in function g
[1] "this is function g"
print x in function f
[1] "substitution using '<<-' operator"
> print(x)
[1] "this is global environment"

上の例では関数 g(親の環境は関数 f)で<<-演算子によって x に “substitution using ‘<<-‘ operator” を代入しています。
すると自分の環境にある変数 x ではなく、上の環境に変数 x を探しに行きます。1つ上の環境(関数 f)に変数 x が存在するので、関数 f の変数 x に代入しています。
途中で変数 x が見つかったので、グローバル変数の x は “this is global environment” のままです。

もしグローバル変数の x に代入したい場合は次のように<<-演算子ではなく assign を使うべきです。

> x <- "this is global environment"
> f <- function() {
+     x <- "this is function f"
+     g <- function() {
+         x <- "this is function g"
+         assign("x", "substitution using 'assign'", globalenv())
+         cat("print x in function g\n")
+         print(x)
+     }
+     g()
+     cat("print x in function f\n")
+     print(x)
+ }
> f()
print x in function g
[1] "this is function g"
print x in function f
[1] "this is function f"
> print(x)
[1] "substitution using 'assign'"

グローバル変数の x に代入されていますね。

この内容についても気になった点として連絡させていただきましたが、<<-がグローバル変数への代入という説明はいろいろなところでされているし、<<-演算子自体の説明は間違っていないので(”フレーム” を “環境” とすべきかもしれませんが)、初心者向けには難しい説明をするよりはグローバル変数への代入と簡単に説明する方がいいのかもしれません。

ちなみに<<-演算子を使う上でのポイントとして、探索するのは上の “フレーム” ではなく “環境” ということも挙げられると思うので、”フレーム” と “環境” の違いについても触れる必要がある気もしますが、その辺はTokyo.Lang.R #0で話すことになりそうなのでその時にでも。