トップ > サポート > ご利用での Q&A > プログラミング編

プログラミング編

は Windows 版固有、 は UNIX 版固有、 は Linux 版固有、 は S-PLUS 2000 版固有の問題です。

質問 一覧

自作の関数のデバッグを行うには、どうすれば良いか。

UNIXやC言語を生んだベル研究所のS言語は、「より快適で生産的なプログラミング環境の追求」という理念のもとに開発されています。S言語の全てを受け継いだS-PLUSにも強力なデバッグ環境が付属しています。

  1. 最も初歩的なデバッグ方法は、関数の中にprint関数やcat関数を埋め込み、実行時に変数を出力させて確認する方法です。最も分かりやすい方法です。
  2. エラー終了直後に関数tracebackを実行すると、エラー時の関数呼出し状態を確認できます。
  3. 設定したいブレークポイントで関数browserを利用すると、その時点での変数の値を全て確認できます。
  4. 関数inspecは、1ステップごとの実行状態の確認や、任意の関数の実行のトレース、ローカルフレームの状態確認などが可能な本格的なデバッガです。(詳細は 「S-PLUS Programmer's Guide」 をご覧ください。)
Example :
> myfun <-function(x,n){
+ y <- x ^ n
+ browser()
+ plot(x,y) } 自作関数 myfun を定義
> myfun(1:3,3)
Called from: myfun(1:3, 3)
b(2)> ? 変数一覧を参照
1: y
2: x
3: x
4: n
b(2)> n nの値は?
[1] 3
b(2)> 0

計算させた値にある規則で自動的に名前をつけて保存したい。

反復構文 for を使います。例えば、正規乱数を毎回、10個発生させ、それぞれを sample1, sample2, ... , sample10 という名前で保存するには、

> for( i in 1:10)  assign(paste("sample", i, sep=""), rnorm(10))

とします。この場合、関数 assign を使って、付値させることがとても重要になります。for で繰り返したい作業が複数ある場合、{ } で括ってください。 ただし、for を使うと、メモリを大量に消費し、実行速度は遅くなります。上の例のようにやむを得ない場合以外、単なるベクトル演算で書き換えられないか、関数 lapply などが使えないか、もう1度みなおしてみましょう。

データフレームの各列(各変量)に同じ関数を当てはめたい。

反復構文 for なども使えますが、メモリを多量に消費しますので、関数 lapply のご利用をお勧めします。例えば test という名前のデータフレームのすべての列を因子にするには、関数 lapply と 関数 factor を組み合わせて、

> test2 <- lapply(test, factor)           # test のすべての列を因子にして、test2 という名前で付値する

とします。ここでは関数 factor をすべての変量に当てはめましたが、あてはめる関数はユーザー定義関数でもかまいません。

ある文字や単語で始まる名前のオブジェクトを一括して削除したい。

関数 objects の 引数 pattern を利用しますが、Windows 版と UNIX 版で指定法法が異なります。例えば、"test" で始まる名前のオブジェクトを検索するには

> objects(pattern = "^test")           # UNIX 版で test で始まるオブジェクトを一覧にする
> objects(pattern = "test*")          # Windows 版で同じ作業をする。

とします。これをオブジェクト削除のための関数 rm の引数 list に与えます。

> rm(list = objects(pattern = "^test") )          # UNIX 版で test で始まるオブジェクトを削除する
> rm(list = objects(pattern = "test*") )         # Windows 版で同じ作業をする。

とします。

タブや改行、バックスラッシュなど特殊文字の扱いは?

文字列データの中にタブや改行、バックスラッシュなどの特殊文字が入る場合は、注意が必要です。

  1. タブは「¥t」と表されます。
  2. 改行は「¥n」と表されます。
  3. バックスラッシュ自体は、「¥¥」と表されます。このため、Windowsなどディレクトリ区切りにバックスラッシュを利用するOSでは注意が必要です。例えば、C:¥TEMPというディレクトリを文字列としてS言語から示したい場合は、"C:¥¥TEMP"と記述が必要です。
  4. シングルクォート(')は「¥'」で、ダブルクォート(")は「¥"」で表されます。(ただし、文字列全体をダブルクォートで囲んだ場合はシングルクォートはそのままの記述で、文字列全体をシングルクォートで囲んだ場合はダブルクォートはそのままの記述でOKです。)

Note!

例え内部的に「¥」というデータが保存されていても、(入力時のみならず)表示時も上記のルールで表示されますので注意が必要です。すなわち、

> x <- "¥"" #オブジェクトxは「"」という文字データ
> x
{1] "¥"" #表示時にはこうなる
> x <- '"' #シングルクォートで囲んだので、やはりオブジェクトxは「"」という文字データ
> x
[1] "¥"" #同様に表示時はこうなる
Example :
> x <- matrix(1:4,ncol=2) # 2x2の行列データを作成
> write.table(x,"x.txt",sep="¥t") # タブ区切りで出力する

S-PLUS をバッチで実行するには?

次のような形式で実行可能です。

Windows のコマンド・プロンプトなどから

splus.exe /BATCH stdin [stdout[stderr]]

stdin には、S-PLUS に実行させたいコマンド・スクリプト・ファイルを指定します。たとえば、あるファイルを読み込んで、その内容を計算させて保存するという作業をバッチ処理させたいとします。stdin ファイルを d:¥temp¥stdin1.txt という名前のテキスト・ファイルとして、その中身は

input.data <- scan("ファイル名")
result <- 何らかの計算

とします(もちろん、このプログラムは何行になってもかまいません)。また、この例では、保存させることが目的でしたから、stdout は指定しません。呼び出しは

splus.exe /BATCH d:¥temp¥stdin1.txt   [stdout[stderr]]

となります。

Visual C などから S-PLUS をシステム関数でバッチ呼び出ししたが、S-PLUS の終了を認識しない。

基本的に子プロセスと親プロセスの同期の問題は、S-PLUSと関係なくCプログラミング一般の話となります。この前提をご理解いただいた上で、対処方法としては、子プロセス(S-PLUS バッチ処理)を実行する際に system の変わりに下記の psystem をお試しください。

psystem(cmd) 
char cmd[];
{
STARTUPINFO startUpInfo;
PROCESS_INFORMATION procInfo;
BOOL success;

GetStartupInfo(&startUpInfo);

startUpInfo.dwFlags = STARTF_USESHOWWINDOW;
startUpInfo.wShowWindow = SW_HIDE;

success = CreateProcess( 0, cmd, 0, 0, FALSE, CREATE_NEW_CONSOLE,
0, 0, &startUpInfo, &procInfo);
if( !success) {
printf("Error creating process: %s", GetLastError());
exit(1);
}

WaitForSingleObject(procInfo.hProcess, INFINITE);
}

この関数により、子(S-PLUS バッチ処理)が終わるまで親は待ちます。

for ループで繰り返し回数を増やすと、極端に遅くなる。

S-PLUS で for ループを使って処理を行う際、繰り返し回数が多くなると(例えば1万回など)、処理速度が極端に遅くなることがあります。これは、S 言語の性質上、やむをえないことです。

S 言語では、1つの関数がその関数のための新しいフレームを開き、その関数の処理が終了するとフレームは閉じられ、メモリが解放されます。そのため、メモリを有効に使うことができます。それに対して、for に限らず、ループ処理をする場合はカレント・フレームにてオブジェクトの処理などが展開されます。そのため、ループ処理が終わっても、解放されるメモリの量はわずかです。(カレント・フレームは閉じられません。また、ループ処理中に作成されたオブジェクトは永続オブジェクトになります。)

S-PLUS はループ中、アロケートされているメモリ内に使われるデータがあるかどうか、チェックします。もしあれば、メモリのほかの部分にコピーを作ります。このコピーがすむと、メモリが解放されます。繰り返しの回数が増えると、メモリ内のどのデータが使われるのか決定するのに時間がかかるため、処理が遅くなるのです。

ベクトル化計算、関数 apply、lapply の使用などによって、ループを避けるのがもっとも良いのですが、避けられない局面もままあります。その場合は次の点を検討してください。(以前、このページに「移動平均の計算は for ループが不可欠」と記載しておりましたが、関数 filter により、移動平均の計算も可能であることがわかりました。くわしくはこちらのページをご覧ください。下は例としてご参照ください。期間をずらして回帰をするような場合、以下が必要になることもあります)

  1. 繰り返しによって作成されるオブジェクトに「名前属性」をつけない
  2. 「成長するオブジェクト」を作らない
  3. 計算はできるだけ再利用する

次のような2つの関数を考えてみます。

悪い例
> time.trial
function()
{
    first <- proc.time()                        # スタート時間を記録
    moving.average <- NULL                # 初期値の設定
        for(i in 1:900) {
            average <- mean(data[i:(i + 100)])    
                                                     # 計算は下の関数の入れ子にすればいいのに・・・
            moving.average <- c(moving.average, average)
                                                     # データセットは成長しつづける
            names(moving.average) <- paste("kikan", 1:i, sep = "")
                                                     # 名前属性はループの外で
        }
proc.time() - first
}
良い例
> time.trial2
function()
{
       first <- proc.time()
       moving.average <- vector(length = 900)
                                                    # あらかじめ必要な大きさのデータを初期値として作成
           for(i in 1:900) {
           moving.average[i] <- mean(data[i:(i + 100)])
            }
        names(moving.average) <- paste("kikan", 1:900, sep = "")
proc.time() - first
}
> data <- rnorm(1000)
> time.trial()
[1] 3.22998
> time.trial2()
[1] 0.5899658

1000回以下の簡単なループでも、実行時間に差が出ます。

S-PLUS で移動平均を計算させたい。

関数 moving.ave を使う方法と、関数 filter を使う方法があります。

  1. moving.ave を使う

    例えば、期間が30の移動平均を求めるには

    > data <- rnorm(100) > m.average <- moving.ave(data, span = 30)
    # span は期間。返り値は平均とそのとき使ったデータ個数を要素に持つリストとなる。このうち、期間内にすべてのデータが揃っているものだけを取得するには
    > m.average$ave[m.average$sizes==30]
    # ただし、データ自体に欠損が含まれている場合は注意してください。

  2. filter を使う

    上記と同じことをするには

    > data <- rnorm(100) > m.average <- filter(data, filter=rep(1/30, 30), side=1)

    # filter は該当するデータに与える係数。上の場合では、side=1 と併せて、i 番目から、i+(30-1) 番目のデータを取得し、それに均等でかつ、合計すると 1 になる係数をかけて足す。ただし、この場合では、最初の 29 個には欠損が生ずるため、以下のコマンドにより、期間内にデータの揃っているものだけを取得。

    > m.average[30:100]

自作の関数名を一覧表示させたい。

関数 sapply を使うと便利です。sapply は第1引数で指定されたオブジェクトに対して、第2引数で指定された関数、あるいは関数定義を順に、あてはめます。ただし、次のコマンドで正しい結果が得られるのは、関数がクラス function を持つ、新しい S 言語仕様になっている S-PLUS のみです。UNIX 版は S-PLUS 5.1 以上、Windows 版は S-PLUS6.0 以上となります。

> index <- sapply(objects(), function(vec){class(get(vec))=="function"})

# objects() で得られるオブジェクト名を持つオブジェクトのクラスが "function" のものはどれか

> objects()[index]

# 関数名の表示

ちなみに、関数 sapply を使わず、for ループを使って記述すると次のようになります。

> index <- vector(mode="logical", length(objects()))
> for(i in seq(along=objects())){
    index[i] <- class(get(objects()[i]))=="function"
}
> objects()[index]

Windows 版 S-PLUS では、オブジェクト・エクスプローラを用いることも可能です(S-PLUS 2000 以上、S-PLUS4.0 , 4.5 では、オブジェクト・ブラウザをご利用ください)。

  1. オブジェクト・エクスプローラを開く(ツールバーにボタンがあります)
  2. オブジェクト・エクスプローラの左側の空白部分をクリック、メニューバー「挿入 / フォルダ」を選択
  3. すると、左側に「Folder1」という名前のフォルダができるので、このフォルダをクリックして右ボタン
  4. メニューから「Folder」を選択
  5. 「Data Objects」の「Function」にチェックを入れる

樹形モデルを行った結果をplotさせたいが、help(plot)としても、必要な情報が得られない。

S-PLUS のオブジェクトはすべて、クラス(class)を持ちます。そして、多くの関数は呼び出されると、与えられた引数のクラスに応じた関数を呼び出します。これをメソッドといいます。例えば、樹形モデルの結果ならば

> class(fit)
[1] "tree"

というクラスを持ちます。このオブジェクトが関数 plot に当てはめられると

> plot
function(x, ...)
UseMethod("plot")

実際には、plot.tree、つまり、「関数名.クラス名」という名前の関数に渡されます。ですから、詳しい引数の解説を見るには、help(plot.tree)としてください。

「そのような関数はない」というエラーが返った場合、クラスの継承(inheritance of class)が起こっています。

> oldClass(fit)

とすれば、継承されるクラスが分かりますから、「関数名.継承されるクラス」としてヘルプを参照してください。

自作の長いスクリプトの中で1箇所エラーが起こると、次に進まない。エラーの場合はスキップさせるには?

S-PLUS のスクリプト中実行中にエラーが発生すると、スクリプト実行はそこで停止し、それまでに計算された結果などはすべて破棄されます(これは、関数内でも同じです)。

for ループなどで試行錯誤的に行うスクリプトの場合、例えば、for ループ内で次元を変えながらARIMAモデルを実行するような場合、エラーが出た場合はその試行を無視して、他のケースを実行させたいことがあります。そのときは、関数 try で「エラーが起こってもスクリプトを止めない」対象の関数を括ってください。例えば、

fit <- try(arima.mle(x, model=list(order=c(i, j, k))))

とします。もし、このARIMAモデルが何らかの理由により、エラーになっても、その試行はスキップされます。また、エラーだったときの結果オブジェクト fit はクラスとして "Error" を持ちます。