| Top Page | プログラミング | R 自動化 目次 | 索引 | 前へ | 次へ |

R でプログラミング:データの一括処理とグラフ描き

9. 補足:変数の寿命と有効範囲

変数,あるいはオブジェクトがいつ作られ,いつまで存在するのか,また, 存在するあいだ,どこから見ることができ,どこからは見えないか,という話を まとめました。自分で関数を定義しながらやや長いプログラムを書くのに必要な基礎知識です。 説明ばかりで退屈かもしれませんが,一通り目を通してください。

グローバルな世界,ローカルな世界

長いプログラムになると,どこでどんな名前の変数を使っているのか, だんだん分からなくなってきます。 こっちで x という変数の値を変更したら, あっちでも x という変数を使ってたので,プログラムの動作がめちゃくちゃになった ということもありそうです。

こういう問題を解決するために,多くのプログラミング言語では,変数名の有効範囲を 限定することができるようになっています。ここで x という変数名を使うとき, プログラムのほかで同じ名前の変数が使われてるかどうかを気にしなくてよいようにする しくみです。


関数の外はグローバル,関数のなかはローカル

まず,R の入力画面で yr01 と入力してみてください。もしこのような名前の変数がまだ 存在していなければ,そんなオブジェクトはないよというメッセージが表示されるはずです。 もし,たまたまそういう名前の変数を作っていたら,rm(yr01) で削除してください。

そのあと,以下のようなプログラムをファイルに保存して source() で実行してください。 関数 yr.func が登録されます。それから入力画面で yr.func() とすれば,この関数が 実行されて,画面には 関数中で yr01 に代入された値,2003 が表示されるはずです。

yr.func <- function()     #  関数を定義して, y.func という名前で記憶する。
{
    yr01 <- 2003       #  yr01 という変数を作り, 2003 という値を代入する。
    cat(yr01, "\n")    #  yr01 の値を表示する。
}

このあと,入力画面で yr01 とだけ入力するとどうなるでしょう? 2003 と表示されるでしょうか? いえ,そんなオブジェクトはないよというメッセージが 表示されます。はて,関数 y.func のなかでたしかに yr01 と作ったはずなのに,どうして でしょう?

R では,関数中であらたに作った変数は,関数のなかでだけ有効です。 関数を呼び出して実行しているあいだは存在しますが,動作が終了した時点で 消えていまいます。「関数の動作中」だけ存在しているのであって, 「関数が定義され,存在しているあいだ」ではないことに注意してください。 一度上の関数 yr.func を source() で読み込めば,いつでも yr.func を実行されることはできます。でも,何度繰り返して実行しても,yr01 は 実行終了時に消滅します。あとから yr01 の値を表示させようとしてもだめなのです。

このようにある範囲内(この場合,関数のなか)でだけ有効な変数はローカルな 変数と呼ばれます。関数内のローカルな変数に対し,関数の外で作った変数は グローバルな変数と呼ばれます。

こうしたしくみによって,関数のなかでどんな名前の変数が作られているかを 呼び出し側では気にしなくてよくなります。プログラム全体のどこでどんな名前の 変数を使っているのかを覚えている必要がありません。 これは,プログラムをいくつかの関数に分割して書くことのメリットのひとつです。


グローバルとローカルの変数名が重なるとき

関数のなかで,グローバル変数と同じ名前の変数になにかを代入すると, グローバル変数と同名だけど別物のローカルな変数が作られます。 それ以降,その名前はローカル変数のほうを指します。グローバル変数は 隠されてしまいます。

y.func <- function()     #  関数を定義して, y.func という名前で記憶する。
{
    yr01 <- 2003       #  yr01 というローカルな変数を作り, 2003 という値を代入する。
    cat(yr01, "\n")    #  ローカルな yr01 の値を表示する。
}

yr01 <- 1985           #  yr01 というグローバルな変数を作り, 1985 という値を代入する。
cat(yr01, "\n")        #  グローバルな変数 yr01 の値を表示する。
y.func()               #  最初に定義した関数 y.func を実行する。
cat(yr01, "\n")        #  グローバルな変数 yr01 の値を表示する。

このプログラムを実行すると,

となるはずです。関数のなかのローカルな変数 yr01 の値がどうなろうと, 同名のグローバルな変数 yr01 には影響がないことに注意してください。

このように,関数内のローカルな世界では,外のグローバルな世界でどういう 名前の変数が存在するのかを知らなくてよいのです。 前の項とあわせて整理すると,

ということになります。こうして,関数の中と外の世界とが意図せずして 相互に干渉してしまうことが避けられるようになっています。


変数の寿命

ここで,ちょっと話題が替わります。変数はいつからいつまで有効か,という話です。

存在しないものに代入すると,存在するようになる。

プログラム言語によって,こういう名前の変数を使いますよという宣言をまずしないと いけないもの,プログラム中に新しい名前の変数が登場したら,その段階でかってに 用意してくれるものがあります。それぞれにメリット,デメリットがあり,どちらが すぐれているというものではありません。

R は,もちろん後者のタイプであることは,これまでに明らかですね。 R では,存在しない変数になにかを代入すると,あらたに存在するようになります。 x という変数がない状態(objects() としても表示されない)で, x <- 3 とすれば, 自動的に新しい変数 x が作られますし,t <- read.table(....) と書けば, (もしまだ存在しないなら)t という変数が作られ,そこにデータフレームが 代入されます。

もしすでに x や t という変数が存在していたなら,上のように新たな値やオブジェクトを 代入すれば,もとの値は上書きされて消えてしまいます。


存在しないものの値を調べようとしても,存在するようにならない

存在しない名前の変数に値を代入しようとすると,あらたに作られたのに対し, 存在しない名前の変数の値を調べようとしても,そういうオブジェクトは存在しない と怒られるだけです。あらたに作られ,適当な値がしまわれる,ということはありません。

<練習>

関数のなかのローカルな変数と引数はその場かぎり

すでに書いたように,関数中でなにかを代入することであらたに作ったローカルな変数は, 関数の実行中だけ存在しています。関数の動作が終わったら,ただちに消えてしまいます。

関数に渡された引数も同様です。

y.func <- function(p, m)     #  関数を定義。二つの引数を受けとる
{
    p <- p * m
    cat(p)
}

この関数では,渡された1番目の引数 p に2番めの引数 m をかけたものを一度 p に 記録してから,これを表示させています。p はこの関数の実行中だけ有効な変数で, 関数の実行を終了してしまえば,消えてしまいます。


永遠の命:作業スペースの保存

ここまでの話とはちょっと次元が違う話題ですが,変数の寿命ということで触れておきます。

ファイルメニューから「終了」を選ぶと,作業スペースを保存するかどうか聞いてきます。 ここで保存することにすると,その時点の作業ディレクトリに .RData という ファイルが作られます。このファイルのなかには,そのとき有効な変数とその値や, 最近の入力の履歴が保存されます。これらの情報のセットを作業スペースと呼びます。

また,R を起動してからファイルメニューの「作業スペースの読み込み」を選択すると, 以前に保存した .RData から,変数や履歴を読み込みます。 どのような変数が読み込まれたかは, objects() 関数で確認することができます。

作業スペースの保存と読み込みによって,グローバルな変数は,R の起動と終了を 越えて生きつづけることができます。


グローバルな世界とローカルな世界のやりとり

グローバルな世界とローカルな世界の話に戻りましょう。

関数の中のローカルな世界は,外のグローバルな世界とは別の世界ですが, まったく情報が途絶しているのでは困ります。 当然ながら,情報のやりとりの手段は用意されています。 関数に外の世界の情報を伝えたり,関数の仕事の成果を受けとったりする方法を 整理してみます。

外から中へ,その1:引数として渡す

関数に情報を渡す方法の基本は,引数(ひきすう)として渡すことです。 関数のうしろの,かっこの中に渡したい値や変数の名前を書きます。 関数のがわでは,関数を定義するときに引数につけた名前を介して,渡された引数 の値を知ることができます。


※以下,「というわけで……」までは,めんどうだったら読みとばしてください。

※引数を渡すときの注意:値渡しと参照渡し

関数(言語によってはサブルーチン,プロシージャ,メソッドなどと呼ぶ)への 引数の渡しかたには,おおきくわけて2通りの方式があります。 値渡しと参照渡しです。 プログラミング言語によって,値渡し方式のものもあれば,参照渡し方式のものもあります。

これらがどう違うのかを説明するまえに,プログラムのなかでの変数というものについて, 簡単に解説します。プログラムが動作するとき,いろいろなデータをメモリーに記憶させます。 メモリー上のデータは,メモリーの番地(通し番号)で識別されます。でも, ただの数字の羅列である番地をつかってプログラムを書くのはとてもややこしそうです。 そこで,ほとんどのプログラム言語では,人間が区別しやすいように x だの height だの income2003 だのと,番地のかわりに好きな名前でデータを呼び出せる ようになっています。この名前が変数名です。 変数名を指定すると,それに対応する番地のメモリーのなかのデータを扱うことができるのです。 プログラムを書くとkに、どの変数名がどの番地に対応しているかを意識する必要はありません。

値渡しと参照渡しにもどります。値渡しの場合,関数の引数として変数名を書いたとき, その名前に対応するメモリー中に書き込まれたデータが関数に渡されます。それが メモリーのどこに書いてあるものかという情報は渡されません。だから,関数のなかでは その値を利用することはできても,もともとの変数(メモリー上のある場所)の内容を 書き換えることはできません。どこにその変数が存在しているのか分からないのだから、当然ですね。

いっぽう,参照渡しでは,引数に変数名を書いたとき,その変数の番地が関数に渡されます。 関数の側では,オリジナルの変数そのもののありかを知ることができるので,そこに 書かれたデータを読み出して利用することもできるし,内容を書き換えてしまうこともできます。

R では,引数は値渡しで関数に渡されます。 渡された変数の内容を,関数のなかでどう変更しようと,関数の外の世界には影響ありません。

というわけで……


R の関数の引数として変数を渡し,その値を関数内で変更しても,もともとの変数の内容は 変わりません。たとえばデータフレームを関数に渡し,その内容を変更 (新しい列を付け加えたり,列を削除したり,データを書き換えたり) しても,オリジナルのデータはもとのままです。

これを確認するために,以下のプログラムを実行してみましょう。

f.multi <- function(d.frame, mul) { # 関数を定義する。
    d.frame <- d.frame * mul  # d.frame のすべての要素を mul 倍したものを d.frame に上書きする。
    d.frame                   # 結果を表示。
}

t <- read.table('temperature.txt', header = TRUE)  # ファイルを読んでデータフレームを作る。
t <- t[1:5, ]   #  見やすいように,最初の 5行だけに縮める。
f.multi(t, 10)     #  関数 f.multi を呼ぶ。
t                  #  t を表示。

関数のなかでは各要素は10倍されていますが, 呼び出し後に t の内容を表示させてみると,もとのままなのが分かります。

なお,上のプログラム中で,関数の定義が最初にあることに注意してください。 これをファイルの最後に持ってくると, f.multi(t, 10) と関数 f.multi を呼ぶ時点ではまだ f.multi という関数はできていないのでエラーになります。 また,もしすでに f.multi を定義してあったとしたら,そちらの 関数が呼ばれてしまい,このプログラム中で定義した f.multi は呼ばれません。


外から中へ,その2:グローバルな変数の値を知る

関数のなかでも,グローバルな変数の値を知ることはできます。 これも,関数の外から中へ情報が渡されるしくみのひとつです。

y.func <- function()     #  関数を定義して, y.func という名前で記憶する。
{
    cat(x.test, "\n")    #  x.test の値を表示する。
}

x.test <- 1985           #  x.test というグローバルな変数を作り, 1985 という値を代入する。
cat(x.test, "\n")        #  グローバルな変数 x.test の値を表示する。
y.func()                 #  最初に定義した関数 y.func を実行する。
cat(x.test, "\n")        #  グローバルな変数 x.test の値を表示する。

このプログラムを実行すると,

となり,3回 1985 が表示されるはずです。

ところで、前に「関数のなかで,グローバル変数と同じ名前の変数になにかを代入すると, グローバル変数と同名だけど別物のローカルな変数が作られます」と書きました。 一見、上のプログラムの動作はこれと矛盾する気がしますが、そうではありません。 前の例は、グローバル変数と同じ名前の変数に「代入する」場合の話、 こんどの例はグローバル変数を「参照する」場合の話です。

この点をあらためて確認するために、以下のプログラムを実行してみましょう。

y.func <- function()     #  関数を定義して, y.func という名前で記憶する。
{
    cat(x.test, "\n")    #  グローバルな x.test の値を表示する。
    x.test <- 2003       #  代入により、ローカルな変数 x.test が新たに作られる。
    cat(x.test, "\n")    #  ローカルな x.test の値を表示する。
}

x.test <- 1985           #  x.test というグローバルな変数を作り, 1985 という値を代入する。
y.func()                 #  最初に定義した関数 y.func を実行する。
cat(x.test, "\n")        #  グローバルな変数 x.test の値を表示する。

関数の外でグローバルな x.test を作って 1985 が代入されたあと、 関数中で x.test を cat で表示すると、このグローバルな x.test の値 (1985) が表示されます。このときは値を参照しているだけです。 その次に、関数中で x.test への代入 (x.test <- 2003) が行われると、 あらたにローカルな x.test が作られ、その値は 2003 となります。 そのあとで cat で x.test の値を表示させると、ローカルな x.test の値(2003) が表示されます。 関数から戻ったあと、もう一度 x.test の値を表示してみると、グローバルな x.test の値は 変わらず、もとどおり 1985が表示されます。


中から外へ,その1:return で返す

すでに前の章でも出てきましたが,関数のなかで, return(...) という文があると, ... のところに書かれたもの(の値)が関数を呼び出した側に返されます。 変数 <- 関数 のように書けば,関数のなかで return で返している値が変数に代入されます。 これが,もっともふつうな関数の仕事の成果の受けとりかたでしょう。

関数のなかでデータフレームの内容を変更しても,外の世界ではその成果を受けとれないという 例を前に示しました。でも,関数が,変更したあとのデータフレームを return で返すようにすれば,仕事の成果を受けとれます。 そのように書き換えてみましょう。

f <- function(d.frame, mul) { # 関数を定義する。
    d.frame <- d.frame * mul  # d.frame のすべての要素を mul 倍したものを d.frame に上書きする。
    return (d.frame)          # 結果を返す。
}

t <- read.table('temperature.txt', header = TRUE)  # ファイルを読んでデータフレームを作る。
t <- t[1:5, ]   #  見やすいように,最初の 5行だけに縮める。
t               #  t を表示。
t <- f(t, 10)   #  各要素を 10 倍した結果を t に上書きする。
t               #  もういちど t を表示。

上のプログラムを実行して,動作を確かめてみてください。


中から外へ,その2:仕事の成果をファイルなどに残す

これまでに何度も使ってきたさまざまな作図関数は,値を返さずに絵を描きます。 画像ファイルに書き込むにしろ,描画画面に描かれるにしろ, 描かれた画像が作業の結果です。

画像に限らず,ファイルを作製してなんからのデータを書き込む関数は, 仕事の成果をファイルというかたちで関数の外の世界に残すことになります。 関数の実行が終了したからといって,作ったファイルが消えてしまったりはしません。


中から外へ,その3:グローバルな変数に値を代入する

関数のなかでも,グローバルな変数の値を知ることができました。 それだけでなく,グローバルな変数に値を設定することもできます。 <- という代入演算子のかわりに,<をふたつ続ける <<-という代入演算子 を使うと,グローバルな変数に代入することができます。 その名前のグローバル変数が存在しないなら,あらたに作られます。

ただ,こうしたグローバル変数の書き換えをいきあたりばったりにしていると, 意図せずにグローバル変数を書き換えてしまう危険があります。関数のなかでは 温度データが記録された t という名前のグローバルなデータフレームがあることを 前提に,その内容を書き換えている。いっぽう,グローバルな世界で,なにかの合計金額を 記録する変数として t というものを作っている。こんな情況では, 温度のグラフを描く関数を呼んだら収入の合計金額が変わってしまった, というわけのわからないことになります。

関数内でのグローバル変数の書き換えは,変数も関数もきちんと設計を考えて,どうしても 必要な場合に限ったほうがよさそうです。 ここでも例をあげて説明することはしません。


以上で,ようやっと準備が整いました。次の章から, たくさんのファイルに記録されたデータを順次読み込んで処理するという, この文書の本来のテーマに取りかかりましょう。



| Top Page | プログラミング | R 自動化 目次 | 索引 | 前へ | 次へ |